Merge pull request #2487 from bytecodealliance/pch/wasi_common_cap_std

rewrite wasi-common in terms of cap-std
This commit is contained in:
Pat Hickey
2021-02-04 18:02:45 -08:00
committed by GitHub
190 changed files with 6946 additions and 13418 deletions

306
Cargo.lock generated
View File

@@ -260,9 +260,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.4.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byteorder"
@@ -270,6 +270,71 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "cap-fs-ext"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209aca9ff6a807ff2df9f0b75634f5680cad4e86b1712516ed3aa82053e16466"
dependencies = [
"cap-primitives",
"cap-std",
"rustc_version",
"unsafe-io",
"winapi",
]
[[package]]
name = "cap-primitives"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6da225ce9df468bef6626bfe020c00269f8f2b54086aaef0605665762a77be9a"
dependencies = [
"errno",
"fs-set-times",
"ipnet",
"libc",
"maybe-owned",
"once_cell",
"posish",
"rustc_version",
"unsafe-io",
"winapi",
"winapi-util",
"winx",
]
[[package]]
name = "cap-rand"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7853689263850e5b446c252e4a0a7876ca8e739c791953c41566fea8204019"
dependencies = [
"rand 0.8.3",
]
[[package]]
name = "cap-std"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1304c7889d8a5f9f9fdf6df401c026ac95b3a628ded9d890058e802125d72fa2"
dependencies = [
"cap-primitives",
"rustc_version",
"unsafe-io",
]
[[package]]
name = "cap-time-ext"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e30af4a25ca95711d1aef0cd53e534c24e77f49907c35aa3465fb3f44431265"
dependencies = [
"cap-primitives",
"once_cell",
"posish",
"winx",
]
[[package]]
name = "capstone"
version = "0.7.0"
@@ -443,16 +508,6 @@ dependencies = [
"glob",
]
[[package]]
name = "cpu-time"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
@@ -815,9 +870,9 @@ dependencies = [
[[package]]
name = "derivative"
version = "2.1.3"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
@@ -1070,10 +1125,6 @@ dependencies = [
name = "example-fib-debug-wasm"
version = "0.0.0"
[[package]]
name = "example-wasi-fs-wasm"
version = "0.0.0"
[[package]]
name = "example-wasi-wasm"
version = "0.0.0"
@@ -1133,6 +1184,16 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fs-set-times"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0700f6e5d24b4556cc0807148ed978a5f5b12c942528aed44bd8f02967bc70c"
dependencies = [
"posish",
"winapi",
]
[[package]]
name = "fst"
version = "0.4.5"
@@ -1180,7 +1241,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.10.1+wasi-snapshot-preview1",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
@@ -1321,6 +1382,12 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "ipnet"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]]
name = "iter-enum"
version = "0.2.7"
@@ -1408,9 +1475,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
[[package]]
name = "libc"
version = "0.2.82"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
[[package]]
name = "libfuzzer-sys"
@@ -1473,11 +1540,11 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
]
[[package]]
@@ -1498,6 +1565,12 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "maybe-owned"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
[[package]]
name = "memchr"
version = "2.3.4"
@@ -1800,7 +1873,7 @@ dependencies = [
"peepmatic-test",
"peepmatic-test-operator",
"peepmatic-traits",
"rand 0.8.2",
"rand 0.8.3",
"serde",
"wast 32.0.0",
]
@@ -1878,6 +1951,15 @@ dependencies = [
"regex",
]
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pin-project-lite"
version = "0.2.4"
@@ -1916,6 +1998,19 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "posish"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0590fc1e852795610551182100874078cd243f6c43d1066338a6bdc38d7b187"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"errno",
"itoa",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@@ -2036,7 +2131,7 @@ checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger 0.8.2",
"log",
"rand 0.8.2",
"rand 0.8.3",
]
[[package]]
@@ -2069,9 +2164,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha 0.3.0",
@@ -2338,6 +2433,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
]
[[package]]
name = "rusty-fork"
version = "0.3.0"
@@ -2382,9 +2486,9 @@ dependencies = [
[[package]]
name = "scroll_derive"
version = "0.10.4"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12bd20b94c7cdfda8c7ba9b92ad0d9a56e3fa018c25fca83b51aa664c9b4c0d"
checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0"
dependencies = [
"proc-macro2",
"quote",
@@ -2392,19 +2496,37 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.120"
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.120"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775"
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [
"proc-macro2",
"quote",
@@ -2424,18 +2546,18 @@ dependencies = [
[[package]]
name = "serde_test"
version = "1.0.120"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dd7d96489b14fa2f4a89be299ac117c8023d1ead9aaee963a2dde72dad4d14b"
checksum = "38145a8510bdf71d9a8cceeb57664049538446e77f24648328bdbcf22dc7e169"
dependencies = [
"serde",
]
[[package]]
name = "sha2"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
@@ -2476,7 +2598,7 @@ checksum = "4ee9977fa98489d9006f4ab26fc5cbe2a139985baed09d2ec08dee6e506fc496"
dependencies = [
"cfg-if 1.0.0",
"libc",
"rand 0.8.2",
"rand 0.8.3",
"winapi",
]
@@ -2587,9 +2709,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.58"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [
"proc-macro2",
"quote",
@@ -2608,6 +2730,23 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "system-interface"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89794731b407bc2bd80dba457ff3bef6ddfcea77e3ec55340e4838f7b70657d"
dependencies = [
"atty",
"bitflags",
"cap-fs-ext",
"cap-std",
"posish",
"rustc_version",
"unsafe-io",
"winapi",
"winx",
]
[[package]]
name = "target-lexicon"
version = "0.11.1"
@@ -2622,7 +2761,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"libc",
"rand 0.8.2",
"rand 0.8.3",
"redox_syscall 0.2.4",
"remove_dir_all",
"winapi",
@@ -2662,11 +2801,13 @@ name = "test-programs"
version = "0.19.0"
dependencies = [
"anyhow",
"cap-std",
"cfg-if 1.0.0",
"os_pipe",
"pretty_env_logger",
"target-lexicon",
"tempfile",
"wasi-cap-std-sync",
"wasi-common",
"wasmtime",
"wasmtime-wasi",
@@ -2704,11 +2845,11 @@ dependencies = [
[[package]]
name = "thread_local"
version = "1.1.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915"
dependencies = [
"lazy_static",
"once_cell",
]
[[package]]
@@ -2827,6 +2968,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
@@ -2864,6 +3011,15 @@ dependencies = [
"traitobject",
]
[[package]]
name = "unsafe-io"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd35327a2b46b3350186b75a3a337d4517e5ca08118c4e54175d49d7832578d8"
dependencies = [
"rustc_version",
]
[[package]]
name = "vec_map"
version = "0.8.2"
@@ -2904,27 +3060,44 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.1+wasi-snapshot-preview1"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasi-cap-std-sync"
version = "0.22.0"
dependencies = [
"anyhow",
"bitflags",
"cap-fs-ext",
"cap-rand",
"cap-std",
"cap-time-ext",
"fs-set-times",
"lazy_static",
"libc",
"system-interface",
"tempfile",
"tracing",
"unsafe-io",
"wasi-common",
"winapi",
]
[[package]]
name = "wasi-common"
version = "0.22.0"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
"cpu-time",
"filetime",
"getrandom 0.2.2",
"lazy_static",
"bitflags",
"cap-rand",
"cap-std",
"libc",
"thiserror",
"tracing",
"wiggle",
"winapi",
"winx",
"yanix",
]
[[package]]
@@ -3035,6 +3208,7 @@ dependencies = [
"smallvec",
"target-lexicon",
"tempfile",
"wasi-cap-std-sync",
"wasmparser",
"wasmtime-cache",
"wasmtime-environ",
@@ -3051,8 +3225,9 @@ name = "wasmtime-bench-api"
version = "0.19.0"
dependencies = [
"anyhow",
"cap-std",
"shuffling-allocator",
"wasi-common",
"wasi-cap-std-sync",
"wasmtime",
"wasmtime-wasi",
"wat",
@@ -3063,8 +3238,10 @@ name = "wasmtime-c-api"
version = "0.19.0"
dependencies = [
"anyhow",
"cap-std",
"env_logger 0.8.2",
"once_cell",
"wasi-cap-std-sync",
"wasi-common",
"wasmtime",
"wasmtime-c-api-macros",
@@ -3109,6 +3286,7 @@ name = "wasmtime-cli"
version = "0.22.0"
dependencies = [
"anyhow",
"cap-std",
"env_logger 0.8.2",
"file-per-thread-logger",
"filecheck",
@@ -3124,6 +3302,7 @@ dependencies = [
"tempfile",
"test-programs",
"tracing-subscriber",
"wasi-cap-std-sync",
"wasi-common",
"wasmparser",
"wasmtime",
@@ -3336,10 +3515,8 @@ name = "wasmtime-wasi"
version = "0.22.0"
dependencies = [
"anyhow",
"tracing",
"wasi-common",
"wasmtime",
"wasmtime-runtime",
"wasmtime-wiggle",
"wiggle",
]
@@ -3529,6 +3706,8 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winx"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d35176e4c7e0daf9d8da18b7e9df81a8f2358fb3d6feea775ce810f974a512"
dependencies = [
"bitflags",
"cvt",
@@ -3564,17 +3743,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "yanix"
version = "0.22.0"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"filetime",
"libc",
"tracing",
]
[[package]]
name = "z3"
version = "0.7.1"

View File

@@ -33,6 +33,7 @@ wasmtime-wasi = { path = "crates/wasi", version = "0.22.0" }
wasmtime-wasi-crypto = { path = "crates/wasi-crypto", version = "0.22.0", optional = true }
wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "0.22.0", optional = true }
wasi-common = { path = "crates/wasi-common", version = "0.22.0" }
wasi-cap-std-sync = { path = "crates/wasi-common/cap-std-sync", version = "0.22.0" }
structopt = { version = "0.3.5", features = ["color", "suggestions"] }
object = { version = "0.23.0", default-features = false, features = ["write"] }
anyhow = "1.0.19"
@@ -45,6 +46,7 @@ log = "0.4.8"
rayon = "1.2.1"
humantime = "2.0.0"
wasmparser = "0.73.0"
cap-std = "0.13"
[dev-dependencies]
env_logger = "0.8.1"
@@ -73,9 +75,10 @@ members = [
"crates/misc/rust",
"crates/wiggle",
"crates/wiggle/wasmtime",
"crates/wasi-common",
"crates/wasi-common/cap-std-sync",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
"examples/wasi-fs/wasm",
"fuzz",
]

View File

@@ -19,7 +19,8 @@ anyhow = "1.0"
shuffling-allocator = { version = "1.1.1", optional = true }
wasmtime = { path = "../wasmtime", default-features = false }
wasmtime-wasi = { path = "../wasi" }
wasi-common = { path = "../wasi-common" }
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync" }
cap-std = "0.13"
[dev-dependencies]
wat = "1.0"

View File

@@ -83,7 +83,7 @@ use std::env;
use std::os::raw::{c_int, c_void};
use std::path::Path;
use std::slice;
use wasi_common::WasiCtxBuilder;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
use wasmtime_wasi::Wasi;
@@ -211,16 +211,16 @@ impl BenchState {
// Create a WASI environment.
let mut cx = WasiCtxBuilder::new();
cx.inherit_stdio();
cx = cx.inherit_stdio();
// Allow access to the working directory so that the benchmark can read
// its input workload(s).
let working_dir = wasi_common::preopen_dir(working_dir)
let working_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(working_dir) }
.context("failed to preopen the working directory")?;
cx.preopened_dir(working_dir, ".");
cx = cx.preopened_dir(working_dir, ".")?;
// Pass this env var along so that the benchmark program can use smaller
// input workload(s) if it has them and that has been requested.
if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
cx = cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val)?;
}
let cx = cx.build()?;

View File

@@ -28,11 +28,13 @@ wat = { version = "1.0.23", optional = true }
# Optional dependencies for the `wasi` feature
wasi-common = { path = "../wasi-common", optional = true }
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", optional = true }
wasmtime-wasi = { path = "../wasi", optional = true }
cap-std = { version = "0.13", optional = true }
[features]
default = ['jitdump', 'wat', 'wasi', 'cache']
lightbeam = ["wasmtime/lightbeam"]
jitdump = ["wasmtime/jitdump"]
cache = ["wasmtime/cache"]
wasi = ['wasi-common', 'wasmtime-wasi']
wasi = ['wasi-common', 'wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std']

View File

@@ -1,16 +1,22 @@
//! The WASI embedding API definitions for Wasmtime.
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t};
use anyhow::Result;
use cap_std::fs::Dir;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::slice;
use std::str;
use wasi_common::{preopen_dir, WasiCtx, WasiCtxBuilder};
use wasi_cap_std_sync::WasiCtxBuilder;
use wasi_common::WasiCtx;
use wasmtime::{Extern, Linker, Trap};
use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi as WasiPreview1};
use wasmtime_wasi::{
snapshots::preview_0::Wasi as WasiSnapshot0, snapshots::preview_1::Wasi as WasiPreview1,
};
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
CStr::from_ptr(path).to_str().map(Path::new).ok()
@@ -39,7 +45,7 @@ pub struct wasi_config_t {
stdin: Option<File>,
stdout: Option<File>,
stderr: Option<File>,
preopens: Vec<(File, PathBuf)>,
preopens: Vec<(Dir, PathBuf)>,
inherit_args: bool,
inherit_env: bool,
inherit_stdin: bool,
@@ -180,7 +186,7 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
};
let dir = match cstr_to_path(path) {
Some(p) => match preopen_dir(p) {
Some(p) => match cap_std::fs::Dir::open_ambient_dir(p) {
Ok(d) => d,
Err(_) => return false,
},
@@ -197,39 +203,57 @@ enum WasiInstance {
Snapshot0(WasiSnapshot0),
}
fn create_wasi_ctx(config: wasi_config_t) -> Result<WasiCtx> {
use std::convert::TryFrom;
use wasi_common::OsFile;
fn create_wasi_ctx(config: wasi_config_t) -> Result<Rc<RefCell<WasiCtx>>> {
let mut builder = WasiCtxBuilder::new();
if config.inherit_args {
builder.inherit_args();
builder = builder.inherit_args()?;
} else if !config.args.is_empty() {
builder.args(config.args);
let args = config
.args
.into_iter()
.map(|bytes| Ok(String::from_utf8(bytes)?))
.collect::<Result<Vec<String>>>()?;
builder = builder.args(&args)?;
}
if config.inherit_env {
builder.inherit_env();
builder = builder.inherit_env()?;
} else if !config.env.is_empty() {
builder.envs(config.env);
let env = config
.env
.into_iter()
.map(|(kbytes, vbytes)| {
let k = String::from_utf8(kbytes)?;
let v = String::from_utf8(vbytes)?;
Ok((k, v))
})
.collect::<Result<Vec<(String, String)>>>()?;
builder = builder.envs(&env)?;
}
if config.inherit_stdin {
builder.inherit_stdin();
builder = builder.inherit_stdin();
} else if let Some(file) = config.stdin {
builder.stdin(OsFile::try_from(file)?);
let file = unsafe { cap_std::fs::File::from_std(file) };
let file = wasi_cap_std_sync::file::File::from_cap_std(file);
builder = builder.stdin(Box::new(file));
}
if config.inherit_stdout {
builder.inherit_stdout();
builder = builder.inherit_stdout();
} else if let Some(file) = config.stdout {
builder.stdout(OsFile::try_from(file)?);
let file = unsafe { cap_std::fs::File::from_std(file) };
let file = wasi_cap_std_sync::file::File::from_cap_std(file);
builder = builder.stdout(Box::new(file));
}
if config.inherit_stderr {
builder.inherit_stderr();
builder = builder.inherit_stderr();
} else if let Some(file) = config.stderr {
builder.stderr(OsFile::try_from(file)?);
let file = unsafe { cap_std::fs::File::from_std(file) };
let file = wasi_cap_std_sync::file::File::from_cap_std(file);
builder = builder.stderr(Box::new(file));
}
for preopen in config.preopens {
builder.preopened_dir(preopen.0, preopen.1);
for (dir, path) in config.preopens {
builder = builder.preopened_dir(dir, path)?;
}
Ok(builder.build()?)
Ok(Rc::new(RefCell::new(builder.build()?)))
}
#[repr(C)]

View File

@@ -12,14 +12,16 @@ cfg-if = "1.0"
[dev-dependencies]
wasi-common = { path = "../wasi-common", version = "0.22.0" }
wasmtime-wasi = { path = "../wasi", version = "0.22.0" }
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.22.0" }
wasmtime = { path = "../wasmtime", version = "0.22.0" }
wasmtime-wasi = { path = "../wasi", version = "0.22.0" }
target-lexicon = "0.11.0"
pretty_env_logger = "0.4.0"
tempfile = "3.1.0"
os_pipe = "0.9"
anyhow = "1.0.19"
wat = "1.0.23"
cap-std = "0.13"
[features]
test_programs = []

View File

@@ -16,14 +16,6 @@ mod wasi_tests {
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[derive(Clone, Copy, Debug)]
enum PreopenType {
/// Preopens should be satisfied with real OS files.
OS,
/// Preopens should be satisfied with virtual files.
Virtual,
}
pub(super) 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
@@ -47,11 +39,13 @@ mod wasi_tests {
let mut out =
File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file");
build_tests("wasi-tests", &out_dir).expect("building tests");
test_directory(&mut out, "wasi-tests", &out_dir).expect("generating tests");
test_directory(&mut out, "wasi-cap-std-sync", "cap_std_sync", &out_dir)
.expect("generating tests");
}
fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
let mut cmd = Command::new("cargo");
cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "1");
cmd.args(&[
"build",
"--release",
@@ -75,7 +69,12 @@ mod wasi_tests {
Ok(())
}
fn test_directory(out: &mut File, testsuite: &str, out_dir: &Path) -> io::Result<()> {
fn test_directory(
out: &mut File,
testsuite: &str,
runtime: &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"))
@@ -110,33 +109,19 @@ mod wasi_tests {
.expect("testsuite filename should be representable as a string")
.replace("-", "_")
)?;
writeln!(out, " use super::{{runtime, utils, setup_log}};")?;
writeln!(out, " use runtime::PreopenType;")?;
writeln!(
out,
" use super::{{runtime::{} as runtime, utils, setup_log}};",
runtime
)?;
for dir_entry in dir_entries {
let test_path = dir_entry.path();
let stemstr = test_path
.file_stem()
.expect("file_stem")
.to_str()
.expect("to_str");
if no_preopens(testsuite, stemstr) {
write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
} else {
write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
write_testsuite_tests(out, &test_path, testsuite, PreopenType::Virtual)?;
}
write_testsuite_tests(out, &dir_entry.path(), testsuite)?;
}
writeln!(out, "}}")?;
Ok(())
}
fn write_testsuite_tests(
out: &mut File,
path: &Path,
testsuite: &str,
preopen_type: PreopenType,
) -> io::Result<()> {
fn write_testsuite_tests(out: &mut File, path: &Path, testsuite: &str) -> io::Result<()> {
let stemstr = path
.file_stem()
.expect("file_stem")
@@ -144,15 +129,7 @@ mod wasi_tests {
.expect("to_str");
writeln!(out, " #[test]")?;
let test_fn_name = format!(
"{}{}",
&stemstr.replace("-", "_"),
if let PreopenType::Virtual = preopen_type {
"_virtualfs"
} else {
""
}
);
let test_fn_name = stemstr.replace("-", "_");
if ignore(testsuite, &test_fn_name) {
writeln!(out, " #[ignore]")?;
}
@@ -171,109 +148,124 @@ mod wasi_tests {
let workspace = if no_preopens(testsuite, stemstr) {
"None"
} else {
match preopen_type {
PreopenType::OS => {
writeln!(
out,
" let workspace = utils::prepare_workspace(&bin_name)?;"
)?;
"Some(workspace.path())"
}
PreopenType::Virtual => "Some(std::path::Path::new(&bin_name))",
}
};
writeln!(
out,
" runtime::instantiate(&data, &bin_name, {}, {})",
" runtime::{}(&data, &bin_name, {})",
if inherit_stdio(testsuite, stemstr) {
"instantiate_inherit_stdio"
} else {
"instantiate"
},
workspace,
match preopen_type {
PreopenType::OS => "PreopenType::OS",
PreopenType::Virtual => "PreopenType::Virtual",
}
)?;
writeln!(out, " }}")?;
writeln!(out)?;
Ok(())
}
cfg_if::cfg_if! {
if #[cfg(not(windows))] {
/// Ignore tests that aren't supported yet.
fn ignore(testsuite: &str, name: &str) -> bool {
if testsuite == "wasi-tests" {
match name {
// TODO: virtfs files cannot be poll_oneoff'd yet
"poll_oneoff_virtualfs" => true,
// TODO: virtfs does not support filetimes yet.
"path_filestat_virtualfs" |
"fd_filestat_set_virtualfs" => true,
// TODO: virtfs does not support symlinks yet.
"nofollow_errors_virtualfs" |
"path_link_virtualfs" |
"readlink_virtualfs" |
"readlink_no_buffer_virtualfs" |
"dangling_symlink_virtualfs" |
"symlink_loop_virtualfs" |
"path_symlink_trailing_slashes_virtualfs" => true,
// TODO: virtfs does not support rename yet.
"path_rename_trailing_slashes_virtualfs" |
"path_rename_virtualfs" => true,
// TODO: virtfs does not support truncation yet.
"file_truncation_virtualfs" => true,
_ => false,
}
} else {
unreachable!()
match testsuite {
"wasi-cap-std-sync" => cap_std_sync_ignore(name),
"wasi-virtfs" => virtfs_ignore(name),
_ => panic!("unknown test suite: {}", testsuite),
}
}
} else {
#[cfg(not(windows))]
/// Ignore tests that aren't supported yet.
fn ignore(testsuite: &str, name: &str) -> bool {
if testsuite == "wasi-tests" {
match name {
"readlink_no_buffer" => true,
"dangling_symlink" => true,
"symlink_loop" => true,
"truncation_rights" => true,
"dangling_fd" => true,
// TODO: virtfs files cannot be poll_oneoff'd yet
"poll_oneoff_virtualfs" => true,
// TODO: virtfs does not support filetimes yet.
"path_filestat_virtualfs" |
"fd_filestat_set_virtualfs" => true,
// TODO: virtfs does not support symlinks yet.
"nofollow_errors_virtualfs" |
"path_link_virtualfs" |
"readlink_virtualfs" |
"readlink_no_buffer_virtualfs" |
"dangling_symlink_virtualfs" |
"symlink_loop_virtualfs" |
"path_symlink_trailing_slashes_virtualfs" => true,
// TODO: virtfs does not support rename yet.
"path_rename_trailing_slashes_virtualfs" |
"path_rename_virtualfs" => true,
// TODO: virtfs does not support truncation yet.
"file_truncation_virtualfs" => true,
_ => false,
}
} else {
unreachable!()
}
fn cap_std_sync_ignore(name: &str) -> bool {
[
// Trailing slash related bugs:
"path_rename_file_trailing_slashes",
"remove_directory_trailing_slashes",
]
.contains(&name)
}
#[cfg(windows)]
/// Ignore tests that aren't supported yet.
fn cap_std_sync_ignore(name: &str) -> bool {
[
// Trailing slash related bugs
"interesting_paths",
"path_rename_file_trailing_slashes",
"remove_directory_trailing_slashes",
]
.contains(&name)
}
/// Virtfs barely works at all and is not suitable for any purpose
fn virtfs_ignore(name: &str) -> bool {
[
"dangling_fd",
"dangling_symlink",
"directory_seek",
"fd_advise",
"fd_filestat_set",
"fd_flags_set",
"fd_readdir",
"file_allocate",
"file_pread_pwrite",
"file_seek_tell",
"file_truncation",
"file_unbuffered_write",
"interesting_paths",
"isatty",
"nofollow_errors",
"path_filestat",
"path_link",
"path_open_create_existing",
"path_open_dirfd_not_dir",
"path_open_read_without_rights",
"path_rename",
"path_rename_dir_trailing_slashes",
"path_rename_file_trailing_slashes",
"path_symlink_trailing_slashes",
"poll_oneoff",
"poll_oneoff_stdio",
"readlink",
"remove_directory_trailing_slashes",
"remove_nonempty_directory",
"renumber",
"symlink_create",
"symlink_filestat",
"symlink_loop",
"truncation_rights",
"unlink_file_trailing_slashes",
]
.contains(&name)
}
/// Mark tests which do not require preopens
fn no_preopens(testsuite: &str, name: &str) -> bool {
if testsuite == "wasi-tests" {
if testsuite.starts_with("wasi-") {
match name {
"big_random_buf" => true,
"clock_time_get" => true,
"sched_yield" => true,
"poll_oneoff_stdio" => true,
_ => false,
}
} else {
unreachable!()
panic!("unknown test suite {}", testsuite)
}
}
/// Mark tests which require inheriting parent process stdio
fn inherit_stdio(testsuite: &str, name: &str) -> bool {
match testsuite {
"wasi-cap-std-sync" => match name {
"poll_oneoff_stdio" => true,
_ => false,
},
"wasi-virtfs" => false,
_ => panic!("unknown test suite {}", testsuite),
}
}
}

View File

@@ -1,79 +0,0 @@
use anyhow::Context;
use std::convert::TryFrom;
use std::fs::File;
use std::path::Path;
use wasi_common::{OsOther, VirtualDirEntry};
use wasmtime::{Linker, Module, Store};
#[derive(Clone, Copy, Debug)]
pub enum PreopenType {
/// Preopens should be satisfied with real OS files.
OS,
/// Preopens should be satisfied with virtual files.
Virtual,
}
pub fn instantiate(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
preopen_type: PreopenType,
) -> anyhow::Result<()> {
let store = Store::default();
// Create our wasi context with pretty standard arguments/inheritance/etc.
// Additionally register any preopened directories if we have them.
let mut builder = wasi_common::WasiCtxBuilder::new();
builder.arg(bin_name).arg(".").inherit_stdio();
if let Some(workspace) = workspace {
match preopen_type {
PreopenType::OS => {
let preopen_dir = wasi_common::preopen_dir(workspace)
.context(format!("error while preopening {:?}", workspace))?;
builder.preopened_dir(preopen_dir, ".");
}
PreopenType::Virtual => {
// we can ignore the workspace path for virtual preopens because virtual preopens
// don't exist in the filesystem anyway - no name conflict concerns.
builder.preopened_virt(VirtualDirEntry::empty_directory(), ".");
}
}
}
// The nonstandard thing we do with `WasiCtxBuilder` is to ensure that
// `stdin` is always an unreadable pipe. This is expected in the test suite
// where `stdin` is never ready to be read. In some CI systems, however,
// stdin is closed which causes tests to fail.
let (reader, _writer) = os_pipe::pipe()?;
let file = reader_to_file(reader);
let handle = OsOther::try_from(file).context("failed to create OsOther from PipeReader")?;
builder.stdin(handle);
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let mut linker = Linker::new(&store);
snapshot1.add_to_linker(&mut linker)?;
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
linker
.module("", &module)
.and_then(|m| m.get_default(""))
.and_then(|f| f.get0::<()>())
.and_then(|f| f().map_err(Into::into))
.context(format!("error while testing Wasm module '{}'", bin_name,))
}
#[cfg(unix)]
fn reader_to_file(reader: os_pipe::PipeReader) -> File {
use std::os::unix::prelude::*;
unsafe { File::from_raw_fd(reader.into_raw_fd()) }
}
#[cfg(windows)]
fn reader_to_file(reader: os_pipe::PipeReader) -> File {
use std::os::windows::prelude::*;
unsafe { File::from_raw_handle(reader.into_raw_handle()) }
}

View File

@@ -0,0 +1,125 @@
use anyhow::Context;
use std::path::Path;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasi_common::pipe::{ReadPipe, WritePipe};
use wasmtime::{Linker, Module, Store};
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> {
let stdout = WritePipe::new_in_memory();
let stderr = WritePipe::new_in_memory();
let r = {
let store = Store::default();
// Create our wasi context.
// Additionally register any preopened directories if we have them.
let mut builder = WasiCtxBuilder::new();
builder = builder
.arg(bin_name)?
.arg(".")?
.stdin(Box::new(ReadPipe::from(Vec::new())))
.stdout(Box::new(stdout.clone()))
.stderr(Box::new(stderr.clone()));
if let Some(workspace) = workspace {
println!("preopen: {:?}", workspace);
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?;
builder = builder.preopened_dir(preopen_dir, ".")?;
}
#[cfg(windows)]
{
builder = builder
.env("ERRNO_MODE_WINDOWS", "1")?
.env("NO_DANGLING_FILESYSTEM", "1")?
.env("NO_FD_ALLOCATE", "1")?
.env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")?
}
#[cfg(all(unix, not(target_os = "macos")))]
{
builder = builder.env("ERRNO_MODE_UNIX", "1")?;
}
#[cfg(target_os = "macos")]
{
builder = builder
.env("ERRNO_MODE_MACOS", "1")?
.env("NO_FD_ALLOCATE", "1")?;
}
// cap-std-sync does not yet support the sync family of fdflags
builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?;
let wasi = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let mut linker = Linker::new(&store);
wasi.add_to_linker(&mut linker)?;
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap();
let with_type = start.get0::<()>()?;
with_type().map_err(anyhow::Error::from)
};
match r {
Ok(()) => Ok(()),
Err(trap) => {
let stdout = stdout
.try_into_inner()
.expect("sole ref to stdout")
.into_inner();
if !stdout.is_empty() {
println!("guest stdout:\n{}\n===", String::from_utf8_lossy(&stdout));
}
let stderr = stderr
.try_into_inner()
.expect("sole ref to stderr")
.into_inner();
if !stderr.is_empty() {
println!("guest stderr:\n{}\n===", String::from_utf8_lossy(&stderr));
}
Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,)))
}
}
}
pub fn instantiate_inherit_stdio(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
) -> anyhow::Result<()> {
let r = {
let store = Store::default();
// Create our wasi context.
// Additionally register any preopened directories if we have them.
let mut builder = WasiCtxBuilder::new();
builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio();
if let Some(workspace) = workspace {
println!("preopen: {:?}", workspace);
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?;
builder = builder.preopened_dir(preopen_dir, ".")?;
}
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let mut linker = Linker::new(&store);
snapshot1.add_to_linker(&mut linker)?;
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap();
let with_type = start.get0::<()>()?;
with_type().map_err(anyhow::Error::from)
};
match r {
Ok(()) => Ok(()),
Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))),
}
}

View File

@@ -0,0 +1 @@
pub mod cap_std_sync;

View File

@@ -1,30 +1,35 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
[[package]]
name = "more-asserts"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasi-tests"
version = "0.19.0"
dependencies = [
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"more-asserts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static",
"libc",
"more-asserts",
"wasi",
]
[metadata]
"checksum libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
"checksum more-asserts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"

View File

@@ -8,8 +8,9 @@ publish = false
[dependencies]
libc = "0.2.65"
wasi = "0.10.0"
wasi = "0.10.2"
more-asserts = "0.2.1"
lazy_static = "1.4"
# This crate is built with the wasm32-wasi target, so it's separate
# from the main Wasmtime build, so use this directive to exclude it

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd;
@@ -8,21 +8,19 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
assert_gt!(dir_fd, pre_fd, "dir_fd number");
// Try to close a preopened directory handle.
assert_eq!(
assert_errno!(
wasi::fd_close(pre_fd)
.expect_err("closing a preopened file descriptor")
.raw_error(),
wasi::ERRNO_NOTSUP,
"errno should ERRNO_NOTSUP",
wasi::ERRNO_NOTSUP
);
// Try to renumber over a preopened directory handle.
assert_eq!(
assert_errno!(
wasi::fd_renumber(dir_fd, pre_fd)
.expect_err("renumbering over a preopened file descriptor")
.raw_error(),
wasi::ERRNO_NOTSUP,
"errno should be ERRNO_NOTSUP",
wasi::ERRNO_NOTSUP
);
// Ensure that dir_fd is still open.
@@ -34,12 +32,11 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
);
// Try to renumber a preopened directory handle.
assert_eq!(
assert_errno!(
wasi::fd_renumber(pre_fd, dir_fd)
.expect_err("renumbering over a preopened file descriptor")
.raw_error(),
wasi::ERRNO_NOTSUP,
"errno should be ERRNO_NOTSUP",
wasi::ERRNO_NOTSUP
);
// Ensure that dir_fd is still open.

View File

@@ -1,8 +1,9 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{open_scratch_directory, TESTCONFIG};
unsafe fn test_dangling_fd(dir_fd: wasi::Fd) {
if TESTCONFIG.support_dangling_filesystem() {
// Create a file, open it, delete it without closing the handle,
// and then try creating it again
let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap();
@@ -29,6 +30,7 @@ unsafe fn test_dangling_fd(dir_fd: wasi::Fd) {
wasi::path_remove_directory(dir_fd, "subdir").expect("failed to remove dir 2");
wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir 2");
}
}
fn main() {
let mut args = env::args();

View File

@@ -1,22 +1,32 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory, TESTCONFIG};
unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) {
if TESTCONFIG.support_dangling_filesystem() {
// First create a dangling symlink.
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Try to open it as a directory with O_NOFOLLOW.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
.expect_err("opening a dangling symlink as a directory")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_NOTDIR,
wasi::ERRNO_LOOP
);
// Try to open it as a file with O_NOFOLLOW.
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
.expect_err("opening a dangling symlink as a file")
.raw_error(),
wasi::ERRNO_LOOP
);
// Clean up.
wasi::path_unlink_file(dir_fd, "symlink").expect("failed to remove file");
}
}
fn main() {
let mut args = env::args();

View File

@@ -1,13 +1,21 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
// Create a directory in the scratch directory.
wasi::path_create_directory(dir_fd, "dir").expect("failed to make directory");
// Open the directory and attempt to request rights for seeking.
let fd = wasi::path_open(dir_fd, 0, "dir", 0, wasi::RIGHTS_FD_SEEK, 0, 0)
let fd = wasi::path_open(
dir_fd,
0,
"dir",
wasi::OFLAGS_DIRECTORY,
wasi::RIGHTS_FD_SEEK,
0,
0,
)
.expect("failed to open file");
assert_gt!(
fd,
@@ -16,12 +24,11 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
);
// Attempt to seek.
assert_eq!(
assert_errno!(
wasi::fd_seek(fd, 0, wasi::WHENCE_CUR)
.expect_err("seek on a directory")
.raw_error(),
wasi::ERRNO_NOTCAPABLE,
"errno should be ERRNO_NOTCAPABLE"
wasi::ERRNO_BADF
);
// Check if we obtained the right to seek.
@@ -34,7 +41,7 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
assert_eq!(
(fdstat.fs_rights_base & wasi::RIGHTS_FD_SEEK),
0,
"directory has the seek right",
"directory does NOT have the seek right",
);
// Clean up.

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{open_scratch_directory, TESTCONFIG};
unsafe fn test_fd_advise(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
@@ -13,6 +13,7 @@ unsafe fn test_fd_advise(dir_fd: wasi::Fd) {
| wasi::RIGHTS_FD_WRITE
| wasi::RIGHTS_FD_ADVISE
| wasi::RIGHTS_FD_FILESTAT_GET
| wasi::RIGHTS_FD_FILESTAT_SET_SIZE
| wasi::RIGHTS_FD_ALLOCATE,
0,
0,
@@ -28,8 +29,8 @@ unsafe fn test_fd_advise(dir_fd: wasi::Fd) {
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat");
assert_eq!(stat.size, 0, "file size should be 0");
// Allocate some size
wasi::fd_allocate(file_fd, 0, 100).expect("allocating size");
// set_size it bigger
wasi::fd_filestat_set_size(file_fd, 100).expect("setting size");
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 2");
assert_eq!(stat.size, 100, "file size should be 100");
@@ -37,6 +38,18 @@ unsafe fn test_fd_advise(dir_fd: wasi::Fd) {
// Advise the kernel
wasi::fd_advise(file_fd, 10, 50, wasi::ADVICE_NORMAL).expect("failed advise");
// Advise shouldnt change size
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 3");
assert_eq!(stat.size, 100, "file size should be 100");
if TESTCONFIG.support_fd_allocate() {
// Use fd_allocate to expand size to 200:
wasi::fd_allocate(file_fd, 100, 100).expect("allocating size");
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 3");
assert_eq!(stat.size, 200, "file size should be 200");
}
wasi::fd_close(file_fd).expect("failed to close");
wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink");
}

View File

@@ -58,7 +58,6 @@ unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> (Vec<DirEntr
let mut buf: [u8; BUF_LEN] = [0; BUF_LEN];
let bufused =
wasi::fd_readdir(fd, buf.as_mut_ptr(), BUF_LEN, cookie).expect("failed fd_readdir");
assert!(bufused <= BUF_LEN);
let sl = slice::from_raw_parts(buf.as_ptr(), bufused);

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{open_scratch_directory, TESTCONFIG};
unsafe fn test_file_allocate(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
@@ -27,6 +27,7 @@ unsafe fn test_file_allocate(dir_fd: wasi::Fd) {
let mut stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
assert_eq!(stat.size, 0, "file size should be 0");
if TESTCONFIG.support_fd_allocate() {
// Allocate some size
wasi::fd_allocate(file_fd, 0, 100).expect("allocating size");
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
@@ -41,7 +42,7 @@ unsafe fn test_file_allocate(dir_fd: wasi::Fd) {
wasi::fd_allocate(file_fd, 90, 20).expect("allocating size larger than current size");
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
assert_eq!(stat.size, 110, "file size should increase from 100 to 110");
}
wasi::fd_close(file_fd).expect("closing a file");
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
}

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_file_seek_tell(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
@@ -57,16 +57,16 @@ unsafe fn test_file_seek_tell(dir_fd: wasi::Fd) {
wasi::fd_seek(file_fd, 1000, wasi::WHENCE_CUR).expect("seeking beyond the end of the file");
// Seek before byte 0 is an error though
assert_eq!(
assert_errno!(
wasi::fd_seek(file_fd, -2000, wasi::WHENCE_CUR)
.expect_err("seeking before byte 0 should be an error")
.raw_error(),
wasi::ERRNO_INVAL,
"errno should be ERRNO_INVAL",
wasi::ERRNO_INVAL
);
// Check that fd_read properly updates the file offset
wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking to the beginning of the file again");
wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET)
.expect("seeking to the beginning of the file again");
let buffer = &mut [0u8; 100];
let iovec = wasi::Iovec {

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
// Create a directory in the scratch directory.
@@ -13,12 +13,11 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
create_file(dir_fd, "dir/nested/file");
// Now open it with an absolute path.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "/dir/nested/file", 0, 0, 0, 0)
.expect_err("opening a file with an absolute path")
.raw_error(),
wasi::ERRNO_NOTCAPABLE,
"errno should be ERRNO_NOTCAPABLE"
wasi::ERRNO_PERM
);
// Now open it with a path containing "..".
@@ -40,30 +39,29 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
wasi::fd_close(file_fd).expect("closing a file");
// Now open it with a trailing NUL.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "dir/nested/file\0", 0, 0, 0, 0)
.expect_err("opening a file with a trailing NUL")
.raw_error(),
wasi::ERRNO_ILSEQ,
"errno should be ERRNO_ILSEQ",
wasi::ERRNO_ILSEQ
);
// Now open it with a trailing slash.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "dir/nested/file/", 0, 0, 0, 0)
.expect_err("opening a file with a trailing slash should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR",
wasi::ERRNO_NOENT
);
// Now open it with trailing slashes.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "dir/nested/file///", 0, 0, 0, 0)
.expect_err("opening a file with trailing slashes should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR",
wasi::ERRNO_NOENT
);
// Now open the directory with a trailing slash.
@@ -88,12 +86,11 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
// Now open it with a path containing too many ".."s.
let bad_path = format!("dir/nested/../../../{}/dir/nested/file", arg);
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, &bad_path, 0, 0, 0, 0)
.expect_err("opening a file with too many \"..\"s in the path should fail")
.raw_error(),
wasi::ERRNO_NOTCAPABLE,
"errno should be ERRNO_NOTCAPABLE",
wasi::ERRNO_PERM
);
wasi::path_unlink_file(dir_fd, "dir/nested/file")
.expect("unlink_file on a symlink should succeed");

View File

@@ -1,7 +1,7 @@
use libc;
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
// Create a directory for the symlink to point to.
@@ -11,21 +11,21 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Try to open it as a directory with O_NOFOLLOW again.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
.expect_err("opening a directory symlink as a directory should fail")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_NOTDIR
);
// Try to open it with just O_NOFOLLOW.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
.expect_err("opening a symlink with O_NOFOLLOW should fail")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_ACCES
);
// Try to open it as a directory without O_NOFOLLOW.
@@ -57,25 +57,24 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Try to open it as a directory with O_NOFOLLOW again.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
.expect_err("opening a directory symlink as a directory should fail")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_NOTDIR
);
// Try to open it with just O_NOFOLLOW.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
.expect_err("opening a symlink with NOFOLLOW should fail")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_LOOP
);
// Try to open it as a directory without O_NOFOLLOW.
assert_eq!(
assert_errno!(
wasi::path_open(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
@@ -87,8 +86,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
)
.expect_err("opening a symlink to a file as a directory")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR",
wasi::ERRNO_NOTDIR
);
// Clean up.

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory, TESTCONFIG};
unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
let mut fdstat = wasi::fd_fdstat_get(dir_fd).expect("fd_fdstat_get");
@@ -9,11 +9,12 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
0,
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
);
assert_ne!(
fdstat.fs_rights_inheriting & wasi::RIGHTS_PATH_FILESTAT_GET,
0,
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
);
let fdflags = if TESTCONFIG.support_fdflags_sync() {
wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC
} else {
wasi::FDFLAGS_APPEND
};
// Create a file in the scratch directory.
let file_fd = wasi::path_open(
@@ -24,7 +25,7 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET,
0,
// Pass some flags for later retrieval
wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC,
fdflags,
)
.expect("opening a file");
assert_gt!(
@@ -44,26 +45,50 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
0,
"files shouldn't have rights for path_* syscalls even if manually given",
);
assert_ne!(
fdstat.fs_flags & (wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC),
0,
"file should have the same flags used to create the file"
assert_eq!(
fdstat.fs_flags & wasi::FDFLAGS_APPEND,
wasi::FDFLAGS_APPEND,
"file should have the APPEND fdflag used to create the file"
);
if TESTCONFIG.support_fdflags_sync() {
assert_eq!(
fdstat.fs_flags & wasi::FDFLAGS_SYNC,
wasi::FDFLAGS_SYNC,
"file should have the SYNC fdflag used to create the file"
);
}
if !TESTCONFIG.support_fdflags_sync() {
assert_errno!(
wasi::path_open(
dir_fd,
0,
"file",
0,
wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET,
0,
wasi::FDFLAGS_SYNC,
)
.expect_err("FDFLAGS_SYNC not supported by platform")
.raw_error(),
wasi::ERRNO_NOTSUP
);
}
// Check file size
let mut stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
assert_eq!(stat.size, 0, "file size should be 0");
let file_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
assert_eq!(file_stat.size, 0, "file size should be 0");
// Check path_filestat_set_times
let new_mtim = stat.mtim - 100;
let new_mtim = file_stat.mtim - 100;
wasi::path_filestat_set_times(dir_fd, 0, "file", 0, new_mtim, wasi::FSTFLAGS_MTIM)
.expect("path_filestat_set_times should succeed");
stat = wasi::path_filestat_get(dir_fd, 0, "file")
let modified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(stat.mtim, new_mtim, "mtim should change");
assert_eq!(modified_file_stat.mtim, new_mtim, "mtim should change");
assert_eq!(
assert_errno!(
wasi::path_filestat_set_times(
dir_fd,
0,
@@ -74,16 +99,19 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
)
.expect_err("MTIM and MTIM_NOW can't both be set")
.raw_error(),
wasi::ERRNO_INVAL,
"errno should be ERRNO_INVAL"
wasi::ERRNO_INVAL
);
// check if the times were untouched
stat = wasi::path_filestat_get(dir_fd, 0, "file")
let unmodified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file")
.expect("reading file stats after ERRNO_INVAL fd_filestat_set_times");
assert_eq!(stat.mtim, new_mtim, "mtim should not change");
assert_eq!(
unmodified_file_stat.mtim, new_mtim,
"mtim should not change"
);
// Invalid arguments to set_times:
assert_errno!(
wasi::path_filestat_set_times(
dir_fd,
0,
@@ -94,50 +122,11 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
)
.expect_err("ATIM & ATIM_NOW can't both be set")
.raw_error(),
wasi::ERRNO_INVAL,
"errno should be ERRNO_INVAL"
wasi::ERRNO_INVAL
);
// Create a symlink
wasi::path_symlink("file", dir_fd, "symlink").expect("creating symlink to a file");
// Check path_filestat_set_times on the symlink itself
let mut sym_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
let sym_new_mtim = sym_stat.mtim - 200;
wasi::path_filestat_set_times(dir_fd, 0, "symlink", 0, sym_new_mtim, wasi::FSTFLAGS_MTIM)
.expect("path_filestat_set_times should succeed on symlink");
sym_stat = wasi::path_filestat_get(dir_fd, 0, "symlink")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(sym_stat.mtim, sym_new_mtim, "mtim should change");
// Now, dereference the symlink
sym_stat = wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "symlink")
.expect("reading file stats on the dereferenced symlink");
assert_eq!(
sym_stat.mtim, stat.mtim,
"symlink mtim should be equal to pointee's when dereferenced"
);
// Finally, change stat of the original file by dereferencing the symlink
wasi::path_filestat_set_times(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
0,
sym_stat.mtim,
wasi::FSTFLAGS_MTIM,
)
.expect("path_filestat_set_times should succeed on setting stat on original file");
stat = wasi::path_filestat_get(dir_fd, 0, "file")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(stat.mtim, sym_stat.mtim, "mtim should change");
wasi::fd_close(file_fd).expect("closing a file");
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
}
fn main() {
let mut args = env::args();

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory, TESTCONFIG};
const TEST_RIGHTS: wasi::Rights = wasi::RIGHTS_FD_READ
| wasi::RIGHTS_PATH_LINK_SOURCE
@@ -102,58 +102,56 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
// Create a link to a path that already exists
create_file(dir_fd, "link");
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
.expect_err("creating a link to existing path should fail")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
wasi::ERRNO_EXIST
);
wasi::path_unlink_file(dir_fd, "link").expect("removing a file");
// Create a link to itself
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "file", dir_fd, "file")
.expect_err("creating a link to itself should fail")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
wasi::ERRNO_EXIST
);
// Create a link where target is a directory
wasi::path_create_directory(dir_fd, "link").expect("creating a dir");
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
.expect_err("creating a link where target is a directory should fail")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
wasi::ERRNO_EXIST
);
wasi::path_remove_directory(dir_fd, "link").expect("removing a dir");
// Create a link to a directory
wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link")
.expect_err("creating a link to a directory should fail")
.raw_error(),
wasi::ERRNO_PERM,
"errno should be ERRNO_PERM"
wasi::ERRNO_ACCES
);
wasi::fd_close(subdir_fd).expect("close subdir before deleting it");
wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory");
// Create a link to a file with trailing slash
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "file", dir_fd, "link/")
.expect_err("creating a link to a file with trailing slash should fail")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
if TESTCONFIG.support_dangling_filesystem() {
// Create a link to a dangling symlink
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
@@ -174,35 +172,19 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
// Create a link where target is a dangling symlink
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
assert_eq!(
assert_errno!(
wasi::path_link(dir_fd, 0, "file", dir_fd, "symlink")
.expect_err("creating a link where target is a dangling symlink")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
wasi::ERRNO_EXIST
);
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
// Create a link to a file following symlinks
wasi::path_symlink("file", dir_fd, "symlink").expect("creating a valid symlink");
wasi::path_link(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
dir_fd,
"link",
)
.expect("creating a link to a file following symlinks");
let link_fd = open_link(dir_fd, "link");
check_rights(file_fd, link_fd);
wasi::path_unlink_file(dir_fd, "link").expect("removing a link");
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
// Create a link where target is a dangling symlink following symlinks
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
// If we do follow symlinks, this should fail
assert_eq!(
// Symlink following with path_link is rejected
assert_errno!(
wasi::path_link(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
@@ -210,34 +192,15 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
dir_fd,
"link",
)
.expect_err("creating a link to a dangling symlink following symlinks should fail")
.expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_INVAL
);
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
// Create a link to a symlink loop following symlinks
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink loop");
assert_eq!(
wasi::path_link(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
dir_fd,
"link",
)
.expect_err("creating a link to a symlink loop following symlinks")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP"
);
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
// Clean up.
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
}
}
fn main() {
let mut args = env::args();

View File

@@ -1,9 +1,9 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_path_open_create_existing(dir_fd: wasi::Fd) {
create_file(dir_fd, "file");
assert_eq!(
assert_errno!(
wasi::path_open(
dir_fd,
0,
@@ -15,8 +15,7 @@ unsafe fn test_path_open_create_existing(dir_fd: wasi::Fd) {
)
.expect_err("trying to create a file that already exists")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
wasi::ERRNO_EXIST
);
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
}

View File

@@ -1,17 +1,16 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_dirfd_not_dir(dir_fd: wasi::Fd) {
// Open a file.
let file_fd =
wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("opening a file");
// Now try to open a file underneath it as if it were a directory.
assert_eq!(
assert_errno!(
wasi::path_open(file_fd, 0, "foo", wasi::OFLAGS_CREAT, 0, 0, 0)
.expect_err("non-directory base fd should get ERRNO_NOTDIR")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
wasi::fd_close(file_fd).expect("closing a file");
}

View File

@@ -1,21 +1,15 @@
use std::{env, process};
use wasi_tests::{open_scratch_directory};
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_path_open_missing(dir_fd: wasi::Fd) {
assert_eq!(
assert_errno!(
wasi::path_open(
dir_fd,
0,
"file",
0, // not passing O_CREAT here
0,
0,
0,
dir_fd, 0, "file", 0, // not passing O_CREAT here
0, 0, 0,
)
.expect_err("trying to open a file that doesn't exist")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
}

View File

@@ -1,6 +1,5 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{create_file, drop_rights, fd_get_rights};
use wasi_tests::{assert_errno, create_file, drop_rights, fd_get_rights, open_scratch_directory};
const TEST_FILENAME: &'static str = "file";
@@ -27,12 +26,11 @@ unsafe fn try_read_file(dir_fd: wasi::Fd) {
};
// Since we no longer have the right to fd_read, trying to read a file
// should be an error.
assert_eq!(
assert_errno!(
wasi::fd_read(fd, &[iovec])
.expect_err("reading bytes from file should fail")
.raw_error(),
wasi::ERRNO_NOTCAPABLE,
"the errno should be ENOTCAPABLE"
wasi::ERRNO_NOTCAPABLE
);
}

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory, TESTCONFIG};
unsafe fn test_path_rename(dir_fd: wasi::Fd) {
// First, try renaming a dir to nonexistent path
@@ -11,12 +11,11 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory");
// Check that source directory doesn't exist anymore
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
.expect_err("opening a nonexistent path as a directory should fail")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
// Check that target directory exists
@@ -31,18 +30,22 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
wasi::fd_close(fd).expect("closing a file");
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
// Yes, renaming a dir to an empty dir is a property guaranteed by rename(2)
// and its fairly important that it is atomic.
// But, we haven't found a way to emulate it on windows. So, sometimes this
// behavior is just hosed. Sorry.
if TESTCONFIG.support_rename_dir_to_empty_dir() {
// Now, try renaming renaming a dir to existing empty dir
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory");
// Check that source directory doesn't exist anymore
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
.expect_err("opening a nonexistent path as a directory")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
// Check that target directory exists
@@ -56,44 +59,60 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
wasi::fd_close(fd).expect("closing a file");
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
} else {
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
wasi::path_rename(dir_fd, "source", dir_fd, "target")
.expect_err("windows does not support renaming a directory to an empty directory");
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
wasi::path_remove_directory(dir_fd, "source").expect("removing a directory");
}
// Now, try renaming a dir to existing non-empty dir
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
create_file(dir_fd, "target/file");
assert_eq!(
assert_errno!(
wasi::path_rename(dir_fd, "source", dir_fd, "target")
.expect_err("renaming directory to a nonempty directory")
.raw_error(),
wasi::ERRNO_NOTEMPTY,
"errno should be ERRNO_NOTEMPTY"
windows => wasi::ERRNO_ACCES,
unix => wasi::ERRNO_NOTEMPTY
);
// This is technically a different property, but the root of these divergent behaviors is in
// the semantics that windows gives us around renaming directories. So, it lives under the same
// flag.
if TESTCONFIG.support_rename_dir_to_empty_dir() {
// Try renaming dir to a file
assert_eq!(
assert_errno!(
wasi::path_rename(dir_fd, "source", dir_fd, "target/file")
.expect_err("renaming a directory to a file")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
wasi::path_unlink_file(dir_fd, "target/file").expect("removing a file");
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
wasi::path_remove_directory(dir_fd, "source").expect("removing a directory");
} else {
// Windows will let you erase a file by renaming a directory to it.
// WASI users can't depend on this error getting caught to prevent data loss.
wasi::path_rename(dir_fd, "source", dir_fd, "target/file")
.expect("windows happens to support renaming a directory to a file");
wasi::path_remove_directory(dir_fd, "target/file").expect("removing a file");
}
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
// Now, try renaming a file to a nonexistent path
create_file(dir_fd, "source");
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a file");
// Check that source file doesn't exist anymore
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
.expect_err("opening a nonexistent path should fail")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
// Check that target file exists
@@ -115,12 +134,11 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
.expect("renaming file to another existing file");
// Check that source file doesn't exist anymore
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
.expect_err("opening a nonexistent path")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
// Check that target file exists
@@ -138,12 +156,12 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
create_file(dir_fd, "source");
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
assert_eq!(
assert_errno!(
wasi::path_rename(dir_fd, "source", dir_fd, "target")
.expect_err("renaming a file to existing directory should fail")
.raw_error(),
wasi::ERRNO_ISDIR,
"errno should be ERRNO_ISDIR"
windows => wasi::ERRNO_ACCES,
unix => wasi::ERRNO_ISDIR
);
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");

View File

@@ -0,0 +1,39 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi::Fd) {
// Test renaming a directory with a trailing slash in the name.
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
wasi::path_rename(dir_fd, "source/", dir_fd, "target")
.expect("renaming a directory with a trailing slash in the source name");
wasi::path_rename(dir_fd, "target", dir_fd, "source/")
.expect("renaming a directory with a trailing slash in the destination name");
wasi::path_rename(dir_fd, "source/", dir_fd, "target/")
.expect("renaming a directory with a trailing slash in the source and destination names");
wasi::path_rename(dir_fd, "target", dir_fd, "source")
.expect("renaming a directory with no trailing slashes at all should work");
wasi::path_remove_directory(dir_fd, "source").expect("removing the 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_path_rename_trailing_slashes(dir_fd) }
}

View File

@@ -1,42 +1,33 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi::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")
.expect("no trailing slashes rename works");
wasi::path_rename(dir_fd, "target", dir_fd, "source").expect("rename it back to source");
assert_errno!(
wasi::path_rename(dir_fd, "source/", dir_fd, "target")
.expect_err("renaming a file with a trailing slash in the source name should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
assert_eq!(
assert_errno!(
wasi::path_rename(dir_fd, "source", dir_fd, "target/")
.expect_err("renaming a file with a trailing slash in the destination name should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
assert_eq!(
assert_errno!(
wasi::path_rename(dir_fd, "source/", dir_fd, "target/")
.expect_err("renaming a file with a trailing slash in the source and destination names should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
wasi::path_unlink_file(dir_fd, "source").expect("removing a file");
// Test renaming a directory with a trailing slash in the name.
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
wasi::path_rename(dir_fd, "source/", dir_fd, "target")
.expect("renaming a directory with a trailing slash in the source name");
wasi::path_rename(dir_fd, "target", dir_fd, "source/")
.expect("renaming a directory with a trailing slash in the destination name");
wasi::path_rename(dir_fd, "source/", dir_fd, "target/")
.expect("renaming a directory with a trailing slash in the source and destination names");
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
}
fn main() {

View File

@@ -1,63 +1,65 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory, TESTCONFIG};
unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
// Link destination shouldn't end with a slash.
assert_eq!(
if TESTCONFIG.support_dangling_filesystem() {
// Dangling symlink: Link destination shouldn't end with a slash.
assert_errno!(
wasi::path_symlink("source", dir_fd, "target/")
.expect_err("link destination ending with a slash should fail")
.raw_error(),
wasi::ERRNO_NOENT,
"errno should be ERRNO_NOENT"
wasi::ERRNO_NOENT
);
// Without the trailing slash, this should succeed.
wasi::path_symlink("source", dir_fd, "target").expect("link destination ending with a slash");
// Dangling symlink: Without the trailing slash, this should succeed.
wasi::path_symlink("source", dir_fd, "target")
.expect("link destination ending with a slash");
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
}
// Link destination already exists, target has trailing slash.
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
assert_eq!(
assert_errno!(
wasi::path_symlink("source", dir_fd, "target/")
.expect_err("link destination already exists")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
unix => wasi::ERRNO_EXIST,
windows => wasi::ERRNO_NOENT
);
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
// Link destination already exists, target has no trailing slash.
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
assert_eq!(
assert_errno!(
wasi::path_symlink("source", dir_fd, "target")
.expect_err("link destination already exists")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
unix => wasi::ERRNO_EXIST,
windows => wasi::ERRNO_NOENT
);
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
// Link destination already exists, target has trailing slash.
create_file(dir_fd, "target");
assert_eq!(
assert_errno!(
wasi::path_symlink("source", dir_fd, "target/")
.expect_err("link destination already exists")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
unix => wasi::ERRNO_NOTDIR,
windows => wasi::ERRNO_NOENT
);
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
// Link destination already exists, target has no trailing slash.
create_file(dir_fd, "target");
assert_eq!(
assert_errno!(
wasi::path_symlink("source", dir_fd, "target")
.expect_err("link destination already exists")
.raw_error(),
wasi::ERRNO_EXIST,
"errno should be ERRNO_EXIST"
unix => wasi::ERRNO_EXIST,
windows => wasi::ERRNO_NOENT
);
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
}

View File

@@ -1,33 +1,27 @@
use more_asserts::assert_gt;
use std::{env, mem::MaybeUninit, process};
use wasi_tests::{open_scratch_directory, STDERR_FD, STDIN_FD, STDOUT_FD};
use wasi_tests::{assert_errno, open_scratch_directory};
const CLOCK_ID: wasi::Userdata = 0x0123_45678;
unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription], nexpected: usize) -> Vec<wasi::Event> {
unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Error> {
let mut out: Vec<wasi::Event> = Vec::new();
out.resize_with(r#in.len(), || {
MaybeUninit::<wasi::Event>::zeroed().assume_init()
});
let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
.expect("poll_oneoff should succeed");
assert_eq!(
size, nexpected,
"poll_oneoff should return {} events",
nexpected
);
out
let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?;
out.truncate(size);
Ok(out)
}
unsafe fn test_empty_poll() {
let r#in = [];
let mut out: Vec<wasi::Event> = Vec::new();
let error = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
.expect_err("empty poll_oneoff should fail");
assert_eq!(
error.raw_error(),
wasi::ERRNO_INVAL,
"error should be EINVAL"
assert_errno!(
wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
.expect_err("empty poll_oneoff should fail")
.raw_error(),
wasi::ERRNO_INVAL
);
}
@@ -45,13 +39,10 @@ unsafe fn test_timeout() {
u: wasi::SubscriptionUU { clock },
},
}];
let out = poll_oneoff_impl(&r#in, 1);
let out = poll_oneoff_impl(&r#in).unwrap();
assert_eq!(out.len(), 1, "should return 1 event");
let event = &out[0];
assert_eq!(
event.error,
wasi::ERRNO_SUCCESS,
"the event.error should be set to ESUCCESS"
);
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
assert_eq!(
event.r#type,
wasi::EVENTTYPE_CLOCK,
@@ -63,111 +54,6 @@ unsafe fn test_timeout() {
);
}
unsafe fn test_stdin_read() {
let clock = wasi::SubscriptionClock {
id: wasi::CLOCKID_MONOTONIC,
timeout: 5_000_000u64, // 5 milliseconds
precision: 0,
flags: 0,
};
let fd_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDIN_FD,
};
let r#in = [
wasi::Subscription {
userdata: CLOCK_ID,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_CLOCK,
u: wasi::SubscriptionUU { clock },
},
},
// Make sure that timeout is returned only once even if there are multiple read events
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_READ,
u: wasi::SubscriptionUU {
fd_read: fd_readwrite,
},
},
},
];
let out = poll_oneoff_impl(&r#in, 1);
let event = &out[0];
assert_eq!(
event.error,
wasi::ERRNO_SUCCESS,
"the event.error should be set to ESUCCESS"
);
assert_eq!(
event.r#type,
wasi::EVENTTYPE_CLOCK,
"the event.type should equal clock"
);
assert_eq!(
event.userdata, CLOCK_ID,
"the event.userdata should contain clock_id specified by the user"
);
}
unsafe fn test_stdout_stderr_write() {
let stdout_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDOUT_FD,
};
let stderr_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDERR_FD,
};
let r#in = [
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stdout_readwrite,
},
},
},
wasi::Subscription {
userdata: 2,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stderr_readwrite,
},
},
},
];
let out = poll_oneoff_impl(&r#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::ERRNO_SUCCESS,
"the event.error should be set to ERRNO_SUCCESS",
);
assert_eq!(
out[0].r#type,
wasi::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::ERRNO_SUCCESS,
"the event.error should be set to ERRNO_SUCCESS",
);
assert_eq!(
out[1].r#type,
wasi::EVENTTYPE_FD_WRITE,
"the event.type should equal FD_WRITE"
);
}
unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
let fd_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: fd,
@@ -192,16 +78,13 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
},
},
];
let out = poll_oneoff_impl(&r#in, 2);
let out = poll_oneoff_impl(&r#in).unwrap();
assert_eq!(out.len(), 2, "should return 2 events");
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_errno!(out[0].error, error_code);
assert_eq!(
out[0].r#type,
wasi::EVENTTYPE_FD_READ,
@@ -211,11 +94,7 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
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_errno!(out[1].error, error_code);
assert_eq!(
out[1].r#type,
wasi::EVENTTYPE_FD_WRITE,
@@ -248,16 +127,36 @@ unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasi::Fd) {
}
unsafe fn test_fd_readwrite_invalid_fd() {
test_fd_readwrite(wasi::Fd::max_value(), wasi::ERRNO_BADF)
let fd_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: wasi::Fd::max_value(),
};
let r#in = [
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_READ,
u: wasi::SubscriptionUU {
fd_read: fd_readwrite,
},
},
},
wasi::Subscription {
userdata: 2,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: fd_readwrite,
},
},
},
];
let err = poll_oneoff_impl(&r#in).unwrap_err();
assert_eq!(err.raw_error(), wasi::ERRNO_BADF)
}
unsafe fn test_poll_oneoff(dir_fd: wasi::Fd) {
test_timeout();
test_empty_poll();
// 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();
}

View File

@@ -0,0 +1,128 @@
use std::mem::MaybeUninit;
use wasi_tests::{assert_errno, STDERR_FD, STDIN_FD, STDOUT_FD};
const CLOCK_ID: wasi::Userdata = 0x0123_45678;
const STDIN_ID: wasi::Userdata = 0x8765_43210;
unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Error> {
let mut out: Vec<wasi::Event> = Vec::new();
out.resize_with(r#in.len(), || {
MaybeUninit::<wasi::Event>::zeroed().assume_init()
});
let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?;
out.truncate(size);
Ok(out)
}
unsafe fn test_stdin_read() {
let clock = wasi::SubscriptionClock {
id: wasi::CLOCKID_MONOTONIC,
timeout: 5_000_000u64, // 5 milliseconds
precision: 0,
flags: 0,
};
let fd_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDIN_FD,
};
// Either stdin can be ready for reading, or this poll can timeout.
let r#in = [
wasi::Subscription {
userdata: CLOCK_ID,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_CLOCK,
u: wasi::SubscriptionUU { clock },
},
},
wasi::Subscription {
userdata: STDIN_ID,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_READ,
u: wasi::SubscriptionUU {
fd_read: fd_readwrite,
},
},
},
];
let out = poll_oneoff_impl(&r#in).unwrap();
// The result should be either a timeout, or that stdin is ready for reading.
// Both are valid behaviors that depend on the test environment.
assert_eq!(out.len(), 1, "should return 1 event");
let event = &out[0];
if event.r#type == wasi::EVENTTYPE_CLOCK {
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
assert_eq!(
event.userdata, CLOCK_ID,
"the event.userdata should contain CLOCK_ID",
);
} else if event.r#type == wasi::EVENTTYPE_FD_READ {
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
assert_eq!(
event.userdata, STDIN_ID,
"the event.userdata should contain STDIN_ID",
);
} else {
panic!("unexpected event type {}", event.r#type);
}
}
unsafe fn test_stdout_stderr_write() {
let stdout_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDOUT_FD,
};
let stderr_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDERR_FD,
};
let r#in = [
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stdout_readwrite,
},
},
},
wasi::Subscription {
userdata: 2,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stderr_readwrite,
},
},
},
];
let out = poll_oneoff_impl(&r#in).unwrap();
assert_eq!(out.len(), 2, "should return 2 events");
assert_eq!(
out[0].userdata, 1,
"the event.userdata should contain fd userdata specified by the user"
);
assert_errno!(out[0].error, wasi::ERRNO_SUCCESS);
assert_eq!(
out[0].r#type,
wasi::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_errno!(out[1].error, wasi::ERRNO_SUCCESS);
assert_eq!(
out[1].r#type,
wasi::EVENTTYPE_FD_WRITE,
"the event.type should equal FD_WRITE"
);
}
unsafe fn test_poll_oneoff() {
// 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();
}
fn main() {
// Run the tests.
unsafe { test_poll_oneoff() }
}

View File

@@ -1,5 +1,5 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_readlink(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
@@ -10,7 +10,7 @@ unsafe fn test_readlink(dir_fd: wasi::Fd) {
// Read link into the buffer
let buf = &mut [0u8; 10];
let mut bufused = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
let bufused = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
.expect("readlink should succeed");
assert_eq!(bufused, 6, "should use 6 bytes of the buffer");
assert_eq!(&buf[..6], b"target", "buffer should contain 'target'");
@@ -22,10 +22,10 @@ unsafe fn test_readlink(dir_fd: wasi::Fd) {
// Read link into smaller buffer than the actual link's length
let buf = &mut [0u8; 4];
bufused = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
.expect("readlink should succeed");
assert_eq!(bufused, 4, "should use all 4 bytes of the buffer");
assert_eq!(buf, b"targ", "buffer should contain 'targ'");
let err = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
.err()
.expect("readlink with too-small buffer should fail");
assert_errno!(err.raw_error(), wasi::ERRNO_RANGE);
// Clean up.
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");

View File

@@ -1,41 +0,0 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
unsafe fn test_readlink_no_buffer(dir_fd: wasi::Fd) {
// First create a dangling symlink.
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Readlink it into a non-existent buffer.
let bufused = wasi::path_readlink(dir_fd, "symlink", (&mut []).as_mut_ptr(), 0)
.expect("readlink with a 0-sized buffer should succeed");
assert_eq!(
bufused, 0,
"readlink with a 0-sized buffer should return 'bufused' 0"
);
// Clean up.
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a 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_readlink_no_buffer(dir_fd) }
}

View File

@@ -1,5 +1,5 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
// Create a directory in the scratch directory.
@@ -19,21 +19,20 @@ unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
create_file(dir_fd, "file");
// Test that removing it with no trailing slash fails.
assert_eq!(
assert_errno!(
wasi::path_remove_directory(dir_fd, "file")
.expect_err("remove_directory without a trailing slash on a file should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
wasi::ERRNO_NOTDIR
);
// Test that removing it with a trailing slash fails.
assert_eq!(
assert_errno!(
wasi::path_remove_directory(dir_fd, "file/")
.expect_err("remove_directory with a trailing slash on a file should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
unix => wasi::ERRNO_NOTDIR,
windows => wasi::ERRNO_NOENT
);
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");

View File

@@ -1,5 +1,5 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_remove_nonempty_directory(dir_fd: wasi::Fd) {
// Create a directory in the scratch directory.
@@ -9,12 +9,11 @@ unsafe fn test_remove_nonempty_directory(dir_fd: wasi::Fd) {
wasi::path_create_directory(dir_fd, "dir/nested").expect("creating a subdirectory");
// Test that attempting to unlink the first directory returns the expected error code.
assert_eq!(
assert_errno!(
wasi::path_remove_directory(dir_fd, "dir")
.expect_err("remove_directory on a directory should return ENOTEMPTY")
.raw_error(),
wasi::ERRNO_NOTEMPTY,
"errno should be ERRNO_NOTEMPTY",
wasi::ERRNO_NOTEMPTY
);
// Removing the directories.

View File

@@ -1,6 +1,6 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory};
unsafe fn test_renumber(dir_fd: wasi::Fd) {
let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd;
@@ -49,12 +49,11 @@ unsafe fn test_renumber(dir_fd: wasi::Fd) {
wasi::fd_renumber(fd_from, fd_to).expect("renumbering two descriptors");
// Ensure that fd_from is closed
assert_eq!(
assert_errno!(
wasi::fd_close(fd_from)
.expect_err("closing already closed file descriptor")
.raw_error(),
wasi::ERRNO_BADF,
"errno should be ERRNO_BADF"
wasi::ERRNO_BADF
);
// Ensure that fd_to is still open.

View File

@@ -0,0 +1,93 @@
use libc;
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
unsafe fn create_symlink_to_file(dir_fd: wasi::Fd) {
// Create a directory for the symlink to point to.
let target_fd =
wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_CREAT, 0, 0, 0).expect("creating a file");
wasi::fd_close(target_fd).expect("closing file");
// Create a symlink.
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Try to open it as a directory without O_NOFOLLOW.
let target_file_via_symlink = wasi::path_open(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
0,
0,
0,
0,
)
.expect("opening a symlink as a directory");
assert_gt!(
target_file_via_symlink,
libc::STDERR_FILENO as wasi::Fd,
"file descriptor range check",
);
wasi::fd_close(target_file_via_symlink).expect("close the symlink file");
// Replace the target directory with a file.
wasi::path_unlink_file(dir_fd, "symlink").expect("removing the symlink");
wasi::path_unlink_file(dir_fd, "target").expect("removing the target file");
}
unsafe fn create_symlink_to_directory(dir_fd: wasi::Fd) {
// Create a directory for the symlink to point to.
wasi::path_create_directory(dir_fd, "target").expect("creating a dir");
// Create a symlink.
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
// Try to open it as a directory without O_NOFOLLOW.
let target_dir_via_symlink = wasi::path_open(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
wasi::OFLAGS_DIRECTORY,
0,
0,
0,
)
.expect("opening a symlink as a directory");
assert_gt!(
target_dir_via_symlink,
libc::STDERR_FILENO as wasi::Fd,
"file descriptor range check",
);
wasi::fd_close(target_dir_via_symlink).expect("closing a file");
// Replace the target directory with a file.
wasi::path_unlink_file(dir_fd, "symlink").expect("remove symlink to directory");
wasi::path_remove_directory(dir_fd, "target")
.expect("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 {
create_symlink_to_file(dir_fd);
create_symlink_to_directory(dir_fd);
}
}

View File

@@ -0,0 +1,110 @@
use more_asserts::assert_gt;
use std::{env, process};
use wasi_tests::open_scratch_directory;
unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
let fdstat = wasi::fd_fdstat_get(dir_fd).expect("fd_fdstat_get");
assert_ne!(
fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_GET,
0,
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
);
// Create a file in the scratch directory.
let file_fd = wasi::path_open(
dir_fd,
0,
"file",
wasi::OFLAGS_CREAT,
wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET,
0,
0,
)
.expect("opening a file");
assert_gt!(
file_fd,
libc::STDERR_FILENO as wasi::Fd,
"file descriptor range check",
);
// Check file size
let file_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
assert_eq!(file_stat.size, 0, "file size should be 0");
// Create a symlink
wasi::path_symlink("file", dir_fd, "symlink").expect("creating symlink to a file");
// Check path_filestat_set_times on the symlink itself
let sym_stat = wasi::path_filestat_get(dir_fd, 0, "symlink").expect("reading symlink stats");
// Modify mtim of symlink
let sym_new_mtim = sym_stat.mtim - 200;
wasi::path_filestat_set_times(dir_fd, 0, "symlink", 0, sym_new_mtim, wasi::FSTFLAGS_MTIM)
.expect("path_filestat_set_times should succeed on symlink");
// Check that symlink mtim motification worked
let modified_sym_stat = wasi::path_filestat_get(dir_fd, 0, "symlink")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(
modified_sym_stat.mtim, sym_new_mtim,
"symlink mtim should change"
);
// Check that pointee mtim is not modified
let unmodified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(
unmodified_file_stat.mtim, file_stat.mtim,
"file mtim should not change"
);
// Now, dereference the symlink
let deref_sym_stat =
wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "symlink")
.expect("reading file stats on the dereferenced symlink");
assert_eq!(
deref_sym_stat.mtim, file_stat.mtim,
"symlink mtim should be equal to pointee's when dereferenced"
);
// Finally, change stat of the original file by dereferencing the symlink
wasi::path_filestat_set_times(
dir_fd,
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
"symlink",
0,
sym_stat.mtim,
wasi::FSTFLAGS_MTIM,
)
.expect("path_filestat_set_times should succeed on setting stat on original file");
let new_file_stat = wasi::path_filestat_get(dir_fd, 0, "file")
.expect("reading file stats after path_filestat_set_times");
assert_eq!(new_file_stat.mtim, sym_stat.mtim, "mtim should change");
wasi::fd_close(file_fd).expect("closing a file");
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
wasi::path_unlink_file(dir_fd, "file").expect("removing a 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) }
}

View File

@@ -1,22 +1,23 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
use wasi_tests::{assert_errno, open_scratch_directory, TESTCONFIG};
unsafe fn test_symlink_loop(dir_fd: wasi::Fd) {
if TESTCONFIG.support_dangling_filesystem() {
// Create a self-referencing symlink.
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink");
// Try to open it.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
.expect_err("opening a self-referencing symlink")
.raw_error(),
wasi::ERRNO_LOOP,
"errno should be ERRNO_LOOP",
wasi::ERRNO_LOOP
);
// Clean up.
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file");
}
}
fn main() {
let mut args = env::args();

View File

@@ -1,5 +1,5 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_truncation_rights(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
@@ -64,12 +64,11 @@ unsafe fn test_truncation_rights(dir_fd: wasi::Fd) {
// Test that we can't truncate the file without the
// wasi_unstable::RIGHT_PATH_FILESTAT_SET_SIZE right.
assert_eq!(
assert_errno!(
wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0)
.expect_err("truncating a file without path_filestat_set_size right")
.raw_error(),
wasi::ERRNO_NOTCAPABLE,
"errno should be ERRNO_NOTCAPABLE",
wasi::ERRNO_NOTCAPABLE
);
}

View File

@@ -1,26 +1,28 @@
use std::{env, process};
use wasi_tests::{create_file, open_scratch_directory};
use wasi_tests::{assert_errno, create_file, open_scratch_directory};
unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
// Create a directory in the scratch directory.
wasi::path_create_directory(dir_fd, "dir").expect("creating a directory");
// Test that unlinking it fails.
assert_eq!(
assert_errno!(
wasi::path_unlink_file(dir_fd, "dir")
.expect_err("unlink_file on a directory should fail")
.raw_error(),
wasi::ERRNO_ISDIR,
"errno should be ERRNO_ISDIR"
macos => wasi::ERRNO_PERM,
unix => wasi::ERRNO_ISDIR,
windows => wasi::ERRNO_ACCES
);
// Test that unlinking it with a trailing flash fails.
assert_eq!(
assert_errno!(
wasi::path_unlink_file(dir_fd, "dir/")
.expect_err("unlink_file on a directory should fail")
.raw_error(),
wasi::ERRNO_ISDIR,
"errno should be ERRNO_ISDIR"
macos => wasi::ERRNO_PERM,
unix => wasi::ERRNO_ISDIR,
windows => wasi::ERRNO_ACCES
);
// Clean up.
@@ -30,12 +32,12 @@ unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
create_file(dir_fd, "file");
// Test that unlinking it with a trailing flash fails.
assert_eq!(
assert_errno!(
wasi::path_unlink_file(dir_fd, "file/")
.expect_err("unlink_file with a trailing slash should fail")
.raw_error(),
wasi::ERRNO_NOTDIR,
"errno should be ERRNO_NOTDIR"
unix => wasi::ERRNO_NOTDIR,
windows => wasi::ERRNO_NOENT
);
// Test that unlinking it with no trailing flash succeeds.

View File

@@ -0,0 +1,69 @@
pub struct TestConfig {
errno_mode: ErrnoMode,
no_dangling_filesystem: bool,
no_fd_allocate: bool,
no_rename_dir_to_empty_dir: bool,
no_fdflags_sync_support: bool,
}
enum ErrnoMode {
Unix,
MacOS,
Windows,
Permissive,
}
impl TestConfig {
pub fn from_env() -> Self {
let errno_mode = if std::env::var("ERRNO_MODE_UNIX").is_ok() {
ErrnoMode::Unix
} else if std::env::var("ERRNO_MODE_MACOS").is_ok() {
ErrnoMode::MacOS
} else if std::env::var("ERRNO_MODE_WINDOWS").is_ok() {
ErrnoMode::Windows
} else {
ErrnoMode::Permissive
};
let no_dangling_filesystem = std::env::var("NO_DANGLING_FILESYSTEM").is_ok();
let no_fd_allocate = std::env::var("NO_FD_ALLOCATE").is_ok();
let no_rename_dir_to_empty_dir = std::env::var("NO_RENAME_DIR_TO_EMPTY_DIR").is_ok();
let no_fdflags_sync_support = std::env::var("NO_FDFLAGS_SYNC_SUPPORT").is_ok();
TestConfig {
errno_mode,
no_dangling_filesystem,
no_fd_allocate,
no_rename_dir_to_empty_dir,
no_fdflags_sync_support,
}
}
pub fn errno_expect_unix(&self) -> bool {
match self.errno_mode {
ErrnoMode::Unix | ErrnoMode::MacOS => true,
_ => false,
}
}
pub fn errno_expect_macos(&self) -> bool {
match self.errno_mode {
ErrnoMode::MacOS => true,
_ => false,
}
}
pub fn errno_expect_windows(&self) -> bool {
match self.errno_mode {
ErrnoMode::Windows => true,
_ => false,
}
}
pub fn support_dangling_filesystem(&self) -> bool {
!self.no_dangling_filesystem
}
pub fn support_fd_allocate(&self) -> bool {
!self.no_fd_allocate
}
pub fn support_rename_dir_to_empty_dir(&self) -> bool {
!self.no_rename_dir_to_empty_dir
}
pub fn support_fdflags_sync(&self) -> bool {
!self.no_fdflags_sync_support
}
}

View File

@@ -1,4 +1,9 @@
use more_asserts::assert_gt;
pub mod config;
lazy_static::lazy_static! {
pub static ref TESTCONFIG: config::TestConfig = config::TestConfig::from_env();
}
// The `wasi` crate version 0.9.0 and beyond, doesn't
// seem to define these constants, so we do it ourselves.
@@ -61,3 +66,68 @@ pub unsafe fn drop_rights(fd: wasi::Fd, drop_base: wasi::Rights, drop_inheriting
wasi::fd_fdstat_set_rights(fd, new_base, new_inheriting).expect("dropping fd rights");
}
#[macro_export]
macro_rules! assert_errno {
($s:expr, windows => $i:expr, $( $rest:tt )+) => {
let e = $s;
if $crate::TESTCONFIG.errno_expect_windows() {
assert_errno!(e, $i);
} else {
assert_errno!(e, $($rest)+, $i);
}
};
($s:expr, macos => $i:expr, $( $rest:tt )+) => {
let e = $s;
if $crate::TESTCONFIG.errno_expect_macos() {
assert_errno!(e, $i);
} else {
assert_errno!(e, $($rest)+, $i);
}
};
($s:expr, unix => $i:expr, $( $rest:tt )+) => {
let e = $s;
if $crate::TESTCONFIG.errno_expect_unix() {
assert_errno!(e, $i);
} else {
assert_errno!(e, $($rest)+, $i);
}
};
($s:expr, $( $i:expr ),+) => {
let e = $s;
{
// Pretty printing infrastructure
struct Alt<'a>(&'a [&'static str]);
impl<'a> std::fmt::Display for Alt<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let l = self.0.len();
if l == 0 {
unreachable!()
} else if l == 1 {
f.write_str(self.0[0])
} else if l == 2 {
f.write_str(self.0[0])?;
f.write_str(" or ")?;
f.write_str(self.0[1])
} else {
for (ix, s) in self.0.iter().enumerate() {
if ix == l - 1 {
f.write_str("or ")?;
f.write_str(s)?;
} else {
f.write_str(s)?;
f.write_str(", ")?;
}
}
Ok(())
}
}
}
assert!( $( e == $i || )+ false,
"expected errno {}; got {}",
Alt(&[ $( wasi::errno_name($i) ),+ ]),
wasi::errno_name(e),
)
}
};
}

View File

@@ -9,7 +9,8 @@ keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition = "2018"
include = ["src/**/*", "LICENSE", "WASI/phases", "build.rs"]
include = ["src/**/*", "WASI/phases/**/*", "LICENSE", "build.rs"]
build = "build.rs"
# This doesn't actually link to a native library, but it allows us to set env
# vars like `DEP_WASI_COMMON_19_*` for crates that have build scripts and depend
@@ -19,21 +20,17 @@ links = "wasi-common-19"
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
libc = "0.2"
getrandom = { version = "0.2.0", features = ["std"] }
cfg-if = "1.0"
filetime = "0.2.7"
lazy_static = "1.4.0"
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
tracing = "0.1.19"
cap-std = "0.13"
cap-rand = "0.13"
bitflags = "1.2"
[target.'cfg(unix)'.dependencies]
yanix = { path = "yanix", version = "0.22.0" }
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winx = { path = "winx", version = "0.22.0" }
winapi = "0.3"
cpu-time = "1.0"
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -1,24 +0,0 @@
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.

View File

@@ -66,7 +66,3 @@ Now, you should be able to run the integration testsuite by running `cargo test`
cargo test --features test-programs/test_programs --package test-programs
```
## 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.

View File

@@ -3,6 +3,8 @@
fn main() {
let cwd = std::env::current_dir().unwrap();
let wasi = cwd.join("WASI");
// this will be available to dependent crates via the DEP_WASI_COMMON_19_WASI env var:
println!("cargo:wasi={}", wasi.display());
// and available to our own crate as WASI_ROOT:
println!("cargo:rustc-env=WASI_ROOT={}", wasi.display());
}

View File

@@ -0,0 +1,35 @@
[package]
name = "wasi-cap-std-sync"
version = "0.22.0"
authors = ["The Wasmtime Project Developers"]
description = "WASI implementation in Rust"
license = "Apache-2.0 WITH LLVM-exception"
categories = ["wasm"]
keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition = "2018"
include = ["src/**/*", "LICENSE" ]
[dependencies]
wasi-common = { path = "../", version = "0.22.0" }
anyhow = "1.0"
cap-std = "0.13"
cap-fs-ext = "0.13"
cap-time-ext = "0.13"
cap-rand = "0.13"
fs-set-times = "0.2.2"
unsafe-io = "0.3"
system-interface = { version = "0.6.0", features = ["cap_std_impls"] }
tracing = "0.1.19"
bitflags = "1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
lazy_static = "1.4"
[dev-dependencies]
tempfile = "3.1.0"

View File

@@ -0,0 +1,46 @@
use cap_std::time::{Duration, Instant, SystemTime};
use cap_time_ext::{MonotonicClockExt, SystemClockExt};
use wasi_common::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock};
pub struct SystemClock(cap_std::time::SystemClock);
impl SystemClock {
pub unsafe fn new() -> Self {
SystemClock(cap_std::time::SystemClock::new())
}
}
impl WasiSystemClock for SystemClock {
fn resolution(&self) -> Duration {
self.0.resolution()
}
fn now(&self, precision: Duration) -> SystemTime {
self.0.now_with(precision)
}
}
pub struct MonotonicClock(cap_std::time::MonotonicClock);
impl MonotonicClock {
pub unsafe fn new() -> Self {
MonotonicClock(cap_std::time::MonotonicClock::new())
}
}
impl WasiMonotonicClock for MonotonicClock {
fn resolution(&self) -> Duration {
self.0.resolution()
}
fn now(&self, precision: Duration) -> Instant {
self.0.now_with(precision)
}
}
pub fn clocks_ctx() -> WasiClocks {
let system = Box::new(unsafe { SystemClock::new() });
let monotonic = unsafe { cap_std::time::MonotonicClock::new() };
let creation_time = monotonic.now();
let monotonic = Box::new(MonotonicClock(monotonic));
WasiClocks {
system,
monotonic,
creation_time,
}
}

View File

@@ -0,0 +1,332 @@
use crate::file::{filetype_from, File};
use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, SystemTimeSpec};
use std::any::Any;
use std::path::{Path, PathBuf};
use system_interface::fs::GetSetFdFlags;
use wasi_common::{
dir::{ReaddirCursor, ReaddirEntity, WasiDir},
file::{FdFlags, FileType, Filestat, OFlags, WasiFile},
Error, ErrorExt,
};
pub struct Dir(cap_std::fs::Dir);
impl Dir {
pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
Dir(dir)
}
}
impl WasiDir for Dir {
fn as_any(&self) -> &dyn Any {
self
}
fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error> {
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
let mut opts = cap_std::fs::OpenOptions::new();
if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) {
opts.create_new(true);
opts.write(true);
} else if oflags.contains(OFlags::CREATE) {
opts.create(true);
opts.write(true);
}
if oflags.contains(OFlags::TRUNCATE) {
opts.truncate(true);
}
if read {
opts.read(true);
}
if write {
opts.write(true);
} else {
// If not opened write, open read. This way the OS lets us open the file.
// If FileCaps::READ is not set, read calls will be rejected at the
// get_cap check.
opts.read(true);
}
if fdflags.contains(FdFlags::APPEND) {
opts.append(true);
}
if symlink_follow {
opts.follow(FollowSymlinks::Yes);
} else {
opts.follow(FollowSymlinks::No);
}
// the DSYNC, SYNC, and RSYNC flags are ignored! We do not
// have support for them in cap-std yet.
// ideally OpenOptions would just support this though:
// https://github.com/bytecodealliance/cap-std/issues/146
if fdflags.intersects(
wasi_common::file::FdFlags::DSYNC
| wasi_common::file::FdFlags::SYNC
| wasi_common::file::FdFlags::RSYNC,
) {
return Err(Error::not_supported().context("SYNC family of FdFlags"));
}
let mut f = self.0.open_with(Path::new(path), &opts)?;
// NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags:
if fdflags.contains(wasi_common::file::FdFlags::NONBLOCK) {
f.set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?;
}
Ok(Box::new(File::from_cap_std(f)))
}
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error> {
let d = if symlink_follow {
self.0.open_dir(Path::new(path))?
} else {
self.0.open_dir_nofollow(Path::new(path))?
};
Ok(Box::new(Dir::from_cap_std(d)))
}
fn create_dir(&self, path: &str) -> Result<(), Error> {
self.0.create_dir(Path::new(path))?;
Ok(())
}
fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>>>, Error> {
// cap_std's read_dir does not include . and .., we should prepend these.
// Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't
// have enough info to make a ReaddirEntity yet.
let dir_meta = self.0.dir_metadata()?;
let rd = vec![
{
let name = ".".to_owned();
Ok((FileType::Directory, dir_meta.ino(), name))
},
{
let name = "..".to_owned();
Ok((FileType::Directory, dir_meta.ino(), name))
},
]
.into_iter()
.chain(
// Now process the `DirEntry`s:
self.0.entries()?.map(|entry| {
let entry = entry?;
let meta = entry.full_metadata()?;
let inode = meta.ino();
let filetype = filetype_from(&meta.file_type());
let name = entry
.file_name()
.into_string()
.map_err(|_| Error::illegal_byte_sequence().context("filename"))?;
Ok((filetype, inode, name))
}),
)
// Enumeration of the iterator makes it possible to define the ReaddirCursor
.enumerate()
.map(|(ix, r)| match r {
Ok((filetype, inode, name)) => Ok(ReaddirEntity {
next: ReaddirCursor::from(ix as u64 + 1),
filetype,
inode,
name,
}),
Err(e) => Err(e),
})
.skip(u64::from(cursor) as usize);
Ok(Box::new(rd))
}
fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
self.0.symlink(src_path, dest_path)?;
Ok(())
}
fn remove_dir(&self, path: &str) -> Result<(), Error> {
self.0.remove_dir(Path::new(path))?;
Ok(())
}
fn unlink_file(&self, path: &str) -> Result<(), Error> {
self.0.remove_file_or_symlink(Path::new(path))?;
Ok(())
}
fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
let link = self.0.read_link(Path::new(path))?;
Ok(link)
}
fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.dir_metadata()?;
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error> {
let meta = if follow_symlinks {
self.0.metadata(Path::new(path))?
} else {
self.0.symlink_metadata(Path::new(path))?
};
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> {
let dest_dir = dest_dir
.as_any()
.downcast_ref::<Self>()
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
self.0
.rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?;
Ok(())
}
fn hard_link(
&self,
src_path: &str,
target_dir: &dyn WasiDir,
target_path: &str,
) -> Result<(), Error> {
let target_dir = target_dir
.as_any()
.downcast_ref::<Self>()
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
let src_path = Path::new(src_path);
let target_path = Path::new(target_path);
self.0.hard_link(src_path, &target_dir.0, target_path)?;
Ok(())
}
fn set_times(
&self,
path: &str,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error> {
if follow_symlinks {
self.0.set_times(
Path::new(path),
convert_systimespec(atime),
convert_systimespec(mtime),
)?;
} else {
self.0.set_symlink_times(
Path::new(path),
convert_systimespec(atime),
convert_systimespec(mtime),
)?;
}
Ok(())
}
}
fn convert_systimespec(t: Option<wasi_common::SystemTimeSpec>) -> Option<SystemTimeSpec> {
match t {
Some(wasi_common::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
None => None,
}
}
#[cfg(test)]
mod test {
use super::Dir;
#[test]
fn scratch_dir() {
let tempdir = tempfile::Builder::new()
.prefix("cap-std-sync")
.tempdir()
.expect("create temporary dir");
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) }
.expect("open ambient temporary dir");
let preopen_dir = Dir::from_cap_std(preopen_dir);
wasi_common::WasiDir::open_dir(&preopen_dir, false, ".")
.expect("open the same directory via WasiDir abstraction");
}
// Readdir does not work on windows, so we won't test it there.
#[cfg(not(windows))]
#[test]
fn readdir() {
use std::collections::HashMap;
use wasi_common::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
use wasi_common::file::{FdFlags, FileType, OFlags};
fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
let mut out = HashMap::new();
for readdir_result in dir
.readdir(ReaddirCursor::from(0))
.expect("readdir succeeds")
{
let entity = readdir_result.expect("readdir entry is valid");
out.insert(entity.name.clone(), entity);
}
out
}
let tempdir = tempfile::Builder::new()
.prefix("cap-std-sync")
.tempdir()
.expect("create temporary dir");
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) }
.expect("open ambient temporary dir");
let preopen_dir = Dir::from_cap_std(preopen_dir);
let entities = readdir_into_map(&preopen_dir);
assert_eq!(
entities.len(),
2,
"should just be . and .. in empty dir: {:?}",
entities
);
assert!(entities.get(".").is_some());
assert!(entities.get("..").is_some());
preopen_dir
.open_file(
false,
"file1",
OFlags::CREATE,
true,
false,
FdFlags::empty(),
)
.expect("create file1");
let entities = readdir_into_map(&preopen_dir);
assert_eq!(entities.len(), 3, "should be ., .., file1 {:?}", entities);
assert_eq!(
entities.get(".").expect(". entry").filetype,
FileType::Directory
);
assert_eq!(
entities.get("..").expect(".. entry").filetype,
FileType::Directory
);
assert_eq!(
entities.get("file1").expect("file1 entry").filetype,
FileType::RegularFile
);
}
}

View File

@@ -0,0 +1,212 @@
use cap_fs_ext::MetadataExt;
use fs_set_times::{SetTimes, SystemTimeSpec};
use std::any::Any;
use std::convert::TryInto;
use std::io;
use system_interface::{
fs::{FileIoExt, GetSetFdFlags},
io::ReadReady,
};
use wasi_common::{
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt,
};
pub struct File(cap_std::fs::File);
impl File {
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
File(file)
}
}
impl WasiFile for File {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
self.0.sync_data()?;
Ok(())
}
fn sync(&self) -> Result<(), Error> {
self.0.sync_all()?;
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
let meta = self.0.metadata()?;
Ok(filetype_from(&meta.file_type()))
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
let fdflags = self.0.get_fd_flags()?;
Ok(from_sysif_fdflags(fdflags))
}
fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
if fdflags.intersects(
wasi_common::file::FdFlags::DSYNC
| wasi_common::file::FdFlags::SYNC
| wasi_common::file::FdFlags::RSYNC,
) {
return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag"));
}
Ok(self.0.set_fd_flags(to_sysif_fdflags(fdflags))?)
}
fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.metadata()?;
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
self.0.set_len(size)?;
Ok(())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
self.0.advise(offset, len, convert_advice(advice))?;
Ok(())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
self.0.allocate(offset, len)?;
Ok(())
}
fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
let n = self.0.read_vectored(bufs)?;
Ok(n.try_into()?)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
let n = self.0.read_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
let n = self.0.write_vectored(bufs)?;
Ok(n.try_into()?)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
let n = self.0.write_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Ok(self.0.seek(pos)?)
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
let n = self.0.peek(buf)?;
Ok(n.try_into()?)
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
}
pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {
use cap_fs_ext::FileTypeExt;
if ft.is_dir() {
FileType::Directory
} else if ft.is_symlink() {
FileType::SymbolicLink
} else if ft.is_socket() {
if ft.is_block_device() {
FileType::SocketDgram
} else {
FileType::SocketStream
}
} else if ft.is_block_device() {
FileType::BlockDevice
} else if ft.is_char_device() {
FileType::CharacterDevice
} else if ft.is_file() {
FileType::RegularFile
} else {
FileType::Unknown
}
}
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
#[cfg(windows)]
impl AsRawHandle for File {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(unix)]
impl AsRawFd for File {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
pub fn convert_systimespec(t: Option<wasi_common::SystemTimeSpec>) -> Option<SystemTimeSpec> {
match t {
Some(wasi_common::SystemTimeSpec::Absolute(t)) => {
Some(SystemTimeSpec::Absolute(t.into_std()))
}
Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
None => None,
}
}
pub fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interface::fs::FdFlags {
let mut out = system_interface::fs::FdFlags::empty();
if f.contains(wasi_common::file::FdFlags::APPEND) {
out |= system_interface::fs::FdFlags::APPEND;
}
if f.contains(wasi_common::file::FdFlags::DSYNC) {
out |= system_interface::fs::FdFlags::DSYNC;
}
if f.contains(wasi_common::file::FdFlags::NONBLOCK) {
out |= system_interface::fs::FdFlags::NONBLOCK;
}
if f.contains(wasi_common::file::FdFlags::RSYNC) {
out |= system_interface::fs::FdFlags::RSYNC;
}
if f.contains(wasi_common::file::FdFlags::SYNC) {
out |= system_interface::fs::FdFlags::SYNC;
}
out
}
pub fn from_sysif_fdflags(f: system_interface::fs::FdFlags) -> wasi_common::file::FdFlags {
let mut out = wasi_common::file::FdFlags::empty();
if f.contains(system_interface::fs::FdFlags::APPEND) {
out |= wasi_common::file::FdFlags::APPEND;
}
if f.contains(system_interface::fs::FdFlags::DSYNC) {
out |= wasi_common::file::FdFlags::DSYNC;
}
if f.contains(system_interface::fs::FdFlags::NONBLOCK) {
out |= wasi_common::file::FdFlags::NONBLOCK;
}
if f.contains(system_interface::fs::FdFlags::RSYNC) {
out |= wasi_common::file::FdFlags::RSYNC;
}
if f.contains(system_interface::fs::FdFlags::SYNC) {
out |= wasi_common::file::FdFlags::SYNC;
}
out
}
pub fn convert_advice(advice: Advice) -> system_interface::fs::Advice {
match advice {
Advice::Normal => system_interface::fs::Advice::Normal,
Advice::Sequential => system_interface::fs::Advice::Sequential,
Advice::Random => system_interface::fs::Advice::Random,
Advice::WillNeed => system_interface::fs::Advice::WillNeed,
Advice::DontNeed => system_interface::fs::Advice::DontNeed,
Advice::NoReuse => system_interface::fs::Advice::NoReuse,
}
}

View File

@@ -0,0 +1,128 @@
//! The `wasi-cap-std-sync` crate provides impl of `WasiFile` and `WasiDir` in
//! terms of `cap_std::fs::{File, Dir}`. These types provide sandboxed access
//! to the local filesystem on both Unix and Windows.
//!
//! All syscalls are hidden behind the `cap-std` hierarchy, with the lone
//! exception of the `sched` implementation, which is provided for both unix
//! and windows in separate modules.
//!
//! Any `wasi_common::{WasiCtx, WasiCtxBuilder}` is interoperable with the
//! `wasi-cap-std-sync` crate. However, for convenience, `wasi-cap-std-sync`
//! provides its own `WasiCtxBuilder` that hooks up to all of the crate's
//! components, i.e. it fills in all of the arguments to
//! `WasiCtx::builder(...)`, presents `preopen_dir` in terms of
//! `cap_std::fs::Dir`, and provides convenience methods for inheriting the
//! parent process's stdio, args, and env.
//!
//! The only place we expect to run into long-term compatibility issues
//! between `wasi-cap-std-sync` and the other impl crates that will come later
//! is in the `Sched` abstraction. Once we can build an async scheduler based
//! on Rust `Future`s, async impls will be able to interoperate, but the
//! synchronous scheduler depends on downcasting the `WasiFile` type down to
//! concrete types it knows about (which in turn impl `AsRawFd` for passing to
//! unix `poll`, or the analogous traits on windows).
//!
//! Why is this impl suffixed with `-sync`? Because `async` is coming soon!
//! The async impl may end up depending on tokio or other relatively heavy
//! deps, so we will retain a sync implementation so that wasi-common users
//! have an option of not pulling in an async runtime.
pub mod clocks;
pub mod dir;
pub mod file;
pub mod sched;
pub mod stdio;
pub use clocks::clocks_ctx;
pub use sched::sched_ctx;
use cap_rand::RngCore;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use wasi_common::{table::Table, Error, WasiCtx, WasiFile};
pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder);
impl WasiCtxBuilder {
pub fn new() -> Self {
WasiCtxBuilder(WasiCtx::builder(
random_ctx(),
clocks_ctx(),
sched_ctx(),
Rc::new(RefCell::new(Table::new())),
))
}
pub fn env(self, var: &str, value: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.env(var, value)?;
Ok(WasiCtxBuilder(s))
}
pub fn envs(self, env: &[(String, String)]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for (k, v) in env {
s = s.env(k, v)?;
}
Ok(s)
}
pub fn inherit_env(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for (key, value) in std::env::vars() {
s = s.env(&key, &value)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn arg(self, arg: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.arg(arg)?;
Ok(WasiCtxBuilder(s))
}
pub fn args(self, arg: &[String]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for a in arg {
s = s.arg(&a)?;
}
Ok(s)
}
pub fn inherit_args(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for arg in std::env::args() {
s = s.arg(&arg)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdin(f))
}
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdout(f))
}
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stderr(f))
}
pub fn inherit_stdin(self) -> Self {
self.stdin(Box::new(crate::stdio::stdin()))
}
pub fn inherit_stdout(self) -> Self {
self.stdout(Box::new(crate::stdio::stdout()))
}
pub fn inherit_stderr(self) -> Self {
self.stderr(Box::new(crate::stdio::stderr()))
}
pub fn inherit_stdio(self) -> Self {
self.inherit_stdin().inherit_stdout().inherit_stderr()
}
pub fn preopened_dir(
self,
dir: cap_std::fs::Dir,
path: impl AsRef<Path>,
) -> Result<Self, Error> {
let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
Ok(WasiCtxBuilder(self.0.preopened_dir(dir, path)?))
}
pub fn build(self) -> Result<WasiCtx, Error> {
self.0.build()
}
}
pub fn random_ctx() -> RefCell<Box<dyn RngCore>> {
RefCell::new(Box::new(unsafe { cap_rand::rngs::OsRng::default() }))
}

View File

@@ -0,0 +1,15 @@
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::*;
use wasi_common::sched::WasiSched;
pub fn sched_ctx() -> Box<dyn WasiSched> {
Box::new(SyncSched::new())
}

View File

@@ -0,0 +1,190 @@
use cap_std::time::Duration;
use std::convert::TryInto;
use std::ops::Deref;
use std::os::unix::io::{AsRawFd, RawFd};
use wasi_common::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
},
Error, ErrorExt,
};
use poll::{PollFd, PollFlags};
pub struct SyncSched;
impl SyncSched {
pub fn new() -> Self {
SyncSched
}
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
let mut pollfds = Vec::new();
let timeout = poll.earliest_clock_deadline();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(f) => {
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
Error::invalid_argument().context("read subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) });
}
Subscription::Write(f) => {
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
Error::invalid_argument().context("write subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) });
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
let ready = loop {
let poll_timeout = if let Some(t) = timeout {
let duration = t.duration_until().unwrap_or(Duration::from_secs(0));
(duration.as_millis() + 1) // XXX try always rounding up?
.try_into()
.map_err(|_| Error::overflow().context("poll timeout"))?
} else {
libc::c_int::max_value()
};
tracing::debug!(
poll_timeout = tracing::field::debug(poll_timeout),
poll_fds = tracing::field::debug(&pollfds),
"poll"
);
match poll::poll(&mut pollfds, poll_timeout) {
Ok(ready) => break ready,
Err(_) => {
let last_err = std::io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
} else {
return Err(last_err.into());
}
}
}
};
if ready > 0 {
for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) {
if let Some(revents) = pollfd.revents() {
let (nbytes, rwsub) = match rwsub {
Subscription::Read(sub) => {
let ready = sub.file.num_ready_bytes()?;
(std::cmp::max(ready, 1), sub)
}
Subscription::Write(sub) => (0, sub),
_ => unreachable!(),
};
if revents.contains(PollFlags::POLLNVAL) {
rwsub.error(Error::badf());
} else if revents.contains(PollFlags::POLLERR) {
rwsub.error(Error::io());
} else if revents.contains(PollFlags::POLLHUP) {
rwsub.complete(nbytes, RwEventFlags::HANGUP);
} else {
rwsub.complete(nbytes, RwEventFlags::empty());
};
}
}
} else {
timeout
.expect("timed out")
.result()
.expect("timer deadline is past")
.unwrap()
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
std::thread::yield_now();
Ok(())
}
}
fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option<RawFd> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(a.downcast_ref::<crate::file::File>().unwrap().as_raw_fd())
} else if a.is::<crate::stdio::Stdin>() {
Some(a.downcast_ref::<crate::stdio::Stdin>().unwrap().as_raw_fd())
} else if a.is::<crate::stdio::Stdout>() {
Some(
a.downcast_ref::<crate::stdio::Stdout>()
.unwrap()
.as_raw_fd(),
)
} else if a.is::<crate::stdio::Stderr>() {
Some(
a.downcast_ref::<crate::stdio::Stderr>()
.unwrap()
.as_raw_fd(),
)
} else {
None
}
}
mod poll {
use bitflags::bitflags;
use std::convert::TryInto;
use std::os::unix::io::RawFd;
bitflags! {
pub struct PollFlags: libc::c_short {
const POLLIN = libc::POLLIN;
const POLLPRI = libc::POLLPRI;
const POLLOUT = libc::POLLOUT;
const POLLRDNORM = libc::POLLRDNORM;
const POLLWRNORM = libc::POLLWRNORM;
const POLLRDBAND = libc::POLLRDBAND;
const POLLWRBAND = libc::POLLWRBAND;
const POLLERR = libc::POLLERR;
const POLLHUP = libc::POLLHUP;
const POLLNVAL = libc::POLLNVAL;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PollFd(libc::pollfd);
impl PollFd {
pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self {
Self(libc::pollfd {
fd,
events: events.bits(),
revents: PollFlags::empty().bits(),
})
}
pub fn revents(self) -> Option<PollFlags> {
PollFlags::from_bits(self.0.revents)
}
}
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<usize, std::io::Error> {
let nready = unsafe {
libc::poll(
fds.as_mut_ptr() as *mut libc::pollfd,
fds.len() as libc::nfds_t,
timeout,
)
};
if nready == -1 {
Err(std::io::Error::last_os_error())
} else {
// When poll doesn't fail, its return value is a non-negative int, which will
// always be convertable to usize, so we can unwrap() here.
Ok(nready.try_into().unwrap())
}
}
}

View File

@@ -0,0 +1,256 @@
use anyhow::Context;
use std::ops::Deref;
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use wasi_common::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
},
Error, ErrorExt,
};
pub struct SyncSched {}
impl SyncSched {
pub fn new() -> Self {
Self {}
}
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
let mut ready = false;
let timeout = poll.earliest_clock_deadline();
let mut stdin_read_subs = Vec::new();
let mut immediate_subs = Vec::new();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(r) if r.file.as_any().is::<crate::stdio::Stdin>() => {
stdin_read_subs.push(r);
}
Subscription::Read(rw) | Subscription::Write(rw) => {
if wasi_file_raw_handle(rw.file.deref()).is_some() {
immediate_subs.push(s);
} else {
return Err(Error::invalid_argument()
.context("read/write subscription fd downcast failed"));
}
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !stdin_read_subs.is_empty() {
let waitmode = if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
WaitMode::Timeout(duration)
} else {
WaitMode::Immediate
}
} else {
if ready {
WaitMode::Immediate
} else {
WaitMode::Infinite
}
};
let state = STDIN_POLL
.lock()
.map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))?
.poll(waitmode)?;
for readsub in stdin_read_subs.into_iter() {
match state {
PollState::Ready => {
readsub.complete(1, RwEventFlags::empty());
ready = true;
}
PollState::NotReady | PollState::TimedOut => {}
PollState::Error(ref e) => {
// Unfortunately, we need to deliver the Error to each of the
// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the
// kind, and then back to std::io::Error, and finally to anyhow::Error.
// When its time to turn this into an errno elsewhere, the error kind will
// be inspected.
let ekind = e.kind();
let ioerror = std::io::Error::from(ekind);
readsub.error(ioerror.into());
ready = true;
}
}
}
}
for sub in immediate_subs {
match sub {
Subscription::Read(r) => {
// XXX This doesnt strictly preserve the behavior in the earlier
// implementation, which would always do complete(0) for reads from
// stdout/err.
match r.file.num_ready_bytes() {
Ok(ready_bytes) => {
r.complete(ready_bytes, RwEventFlags::empty());
ready = true;
}
Err(e) => {
r.error(e);
ready = true;
}
}
}
Subscription::Write(w) => {
// Everything is always ready for writing, apparently?
w.complete(0, RwEventFlags::empty());
ready = true;
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !ready {
if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
thread::sleep(duration);
}
}
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
thread::yield_now();
Ok(())
}
}
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(
a.downcast_ref::<crate::file::File>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdin>() {
Some(
a.downcast_ref::<crate::stdio::Stdin>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdout>() {
Some(
a.downcast_ref::<crate::stdio::Stdout>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stderr>() {
Some(
a.downcast_ref::<crate::stdio::Stderr>()
.unwrap()
.as_raw_handle(),
)
} else {
None
}
}
enum PollState {
Ready,
NotReady, // Not ready, but did not wait
TimedOut, // Not ready, waited until timeout
Error(std::io::Error),
}
enum WaitMode {
Timeout(Duration),
Infinite,
Immediate,
}
struct StdinPoll {
request_tx: Sender<()>,
notify_rx: Receiver<PollState>,
}
lazy_static::lazy_static! {
static ref STDIN_POLL: Mutex<StdinPoll> = StdinPoll::new();
}
impl StdinPoll {
pub fn new() -> Mutex<Self> {
let (request_tx, request_rx) = mpsc::channel();
let (notify_tx, notify_rx) = mpsc::channel();
thread::spawn(move || Self::event_loop(request_rx, notify_tx));
Mutex::new(StdinPoll {
request_tx,
notify_rx,
})
}
// This function should not be used directly.
// Correctness of this function crucially depends on the fact that
// mpsc::Receiver is !Sync.
fn poll(&self, wait_mode: WaitMode) -> Result<PollState, Error> {
match self.notify_rx.try_recv() {
// Clean up possibly unread result from previous poll.
Ok(_) | Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => {
return Err(Error::trap("StdinPoll notify_rx channel closed"))
}
}
// Notify the worker thread to poll stdin
self.request_tx
.send(())
.context("request_tx channel closed")?;
// Wait for the worker thread to send a readiness notification
match wait_mode {
WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {
Ok(r) => Ok(r),
Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),
Err(RecvTimeoutError::Disconnected) => {
Err(Error::trap("StdinPoll notify_rx channel closed"))
}
},
WaitMode::Infinite => self
.notify_rx
.recv()
.context("StdinPoll notify_rx channel closed"),
WaitMode::Immediate => match self.notify_rx.try_recv() {
Ok(r) => Ok(r),
Err(TryRecvError::Empty) => Ok(PollState::NotReady),
Err(TryRecvError::Disconnected) => {
Err(Error::trap("StdinPoll notify_rx channel closed"))
}
},
}
}
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
use std::io::BufRead;
loop {
// Wait on a request:
request_rx.recv().expect("request_rx channel");
// Wait for data to appear in stdin. If fill_buf returns any slice, it means
// that either:
// (a) there is some data in stdin, if non-empty,
// (b) EOF was recieved, if its empty
// Linux returns `POLLIN` in both cases, so we imitate this behavior.
let resp = match std::io::stdin().lock().fill_buf() {
Ok(_) => PollState::Ready,
Err(e) => PollState::Error(e),
};
// Notify about data in stdin. If the read on this channel has timed out, the
// next poller will have to clean the channel.
notify_tx.send(resp).expect("notify_tx channel");
}
}
}

View File

@@ -0,0 +1,217 @@
use crate::file::convert_systimespec;
use fs_set_times::SetTimes;
use std::any::Any;
use std::convert::TryInto;
use std::io;
use std::io::{Read, Write};
use system_interface::io::ReadReady;
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use unsafe_io::AsUnsafeFile;
use wasi_common::{
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt,
};
pub struct Stdin(std::io::Stdin);
pub fn stdin() -> Stdin {
Stdin(std::io::stdin())
}
impl WasiFile for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.as_file_view().metadata()?;
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
mtim: meta.modified().ok(),
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
let n = self.0.as_file_view().read_vectored(bufs)?;
Ok(n.try_into().map_err(|_| Error::range())?)
}
fn read_vectored_at(&self, _bufs: &mut [io::IoSliceMut], _offset: u64) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn write_vectored(&self, _bufs: &[io::IoSlice]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
}
#[cfg(windows)]
impl AsRawHandle for Stdin {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
impl AsRawFd for Stdin {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
macro_rules! wasi_file_write_impl {
($ty:ty) => {
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.as_file_view().metadata()?;
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
mtim: meta.modified().ok(),
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, _bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
Err(Error::badf())
}
fn read_vectored_at(
&self,
_bufs: &mut [io::IoSliceMut],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
let n = self.0.as_file_view().write_vectored(bufs)?;
Ok(n.try_into().map_err(|c| Error::range().context(c))?)
}
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}
#[cfg(windows)]
impl AsRawHandle for $ty {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
impl AsRawFd for $ty {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
};
}
pub struct Stdout(std::io::Stdout);
pub fn stdout() -> Stdout {
Stdout(std::io::stdout())
}
wasi_file_write_impl!(Stdout);
pub struct Stderr(std::io::Stderr);
pub fn stderr() -> Stderr {
Stderr(std::io::stderr())
}
wasi_file_write_impl!(Stderr);

View File

@@ -0,0 +1,22 @@
use cap_std::time::{Duration, Instant, SystemTime};
pub enum SystemTimeSpec {
SymbolicNow,
Absolute(SystemTime),
}
pub trait WasiSystemClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> SystemTime;
}
pub trait WasiMonotonicClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> Instant;
}
pub struct WasiClocks {
pub system: Box<dyn WasiSystemClock>,
pub monotonic: Box<dyn WasiMonotonicClock>,
pub creation_time: cap_std::time::Instant,
}

View File

@@ -1,433 +1,133 @@
use crate::entry::{Entry, EntryHandle};
use crate::fdpool::FdPool;
use crate::handle::Handle;
use crate::string_array::{PendingString, StringArray, StringArrayError};
use crate::sys::osdir::OsDir;
use crate::sys::stdio::NullDevice;
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
use crate::virtfs::{VirtualDir, VirtualDirEntry};
use crate::wasi::types::Fd;
use crate::clocks::WasiClocks;
use crate::dir::{DirCaps, DirEntry, WasiDir};
use crate::file::{FileCaps, FileEntry, WasiFile};
use crate::sched::WasiSched;
use crate::string_array::{StringArray, StringArrayError};
use crate::table::Table;
use crate::Error;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fs::File;
use cap_rand::RngCore;
use std::cell::{RefCell, RefMut};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{env, io};
/// Possible errors when `WasiCtxBuilder` fails building
/// `WasiCtx`.
#[derive(Debug, thiserror::Error)]
pub enum WasiCtxBuilderError {
/// General I/O error was encountered.
#[error("general I/O error encountered: {0}")]
Io(#[from] io::Error),
/// Error constructing arguments
#[error("while constructing arguments: {0}")]
Args(#[source] StringArrayError),
/// Error constructing environment
#[error("while constructing environment: {0}")]
Env(#[source] StringArrayError),
/// The root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory.
#[error("the root of a VirtualDirEntry tree at {} must be a VirtualDirEntry::Directory", .0.display())]
VirtualDirEntryRootNotADirectory(PathBuf),
/// `WasiCtx` has too many opened files.
#[error("context object has too many opened files")]
TooManyFilesOpen,
}
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
enum PendingEntry {
Thunk(fn() -> io::Result<Box<dyn Handle>>),
Handle(Box<dyn Handle>),
}
impl std::fmt::Debug for PendingEntry {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Thunk(f) => write!(
fmt,
"PendingEntry::Thunk({:p})",
f as *const fn() -> io::Result<Box<dyn Handle>>
),
Self::Handle(handle) => write!(fmt, "PendingEntry::Handle({:p})", handle),
}
}
}
struct PendingPreopen(Box<dyn FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>>>);
impl PendingPreopen {
fn new<F>(f: F) -> Self
where
F: FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>> + 'static,
{
Self(Box::new(f))
}
fn into(self) -> WasiCtxBuilderResult<Box<dyn Handle>> {
self.0()
}
}
/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
stdin: Option<PendingEntry>,
stdout: Option<PendingEntry>,
stderr: Option<PendingEntry>,
preopens: Option<Vec<(PathBuf, PendingPreopen)>>,
args: Option<Vec<PendingString>>,
env: Option<HashMap<PendingString, PendingString>>,
}
impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`.
pub fn new() -> Self {
let stdin = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
let stdout = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
let stderr = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
Self {
stdin,
stdout,
stderr,
preopens: Some(Vec::new()),
args: Some(Vec::new()),
env: Some(HashMap::new()),
}
}
/// Add arguments to the command-line arguments list.
///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args
.as_mut()
.unwrap()
.extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
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.
pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args
.as_mut()
.unwrap()
.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.
pub fn inherit_args(&mut self) -> &mut Self {
let args = self.args.as_mut().unwrap();
args.clear();
args.extend(env::args_os().map(PendingString::OsString));
self
}
/// Inherit stdin from the host process.
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self
}
/// Inherit stderr from the host process.
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr = Some(PendingEntry::Thunk(Stderr::stderr));
self
}
/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self.stderr = Some(PendingEntry::Thunk(Stderr::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.
pub fn inherit_env(&mut self) -> &mut Self {
let env = self.env.as_mut().unwrap();
env.clear();
env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
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.
pub fn env<S: AsRef<[u8]>>(&mut self, k: S, v: S) -> &mut Self {
self.env
.as_mut()
.unwrap()
.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.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
&mut self,
envs: impl IntoIterator<Item = T>,
) -> &mut Self {
self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
let (k, v) = t.borrow();
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
}));
self
}
/// Provide a `Handle` to use as stdin
pub fn stdin<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stdin = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Provide a `Handle` to use as stdout
pub fn stdout<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stdout = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Provide a `Handle` to use as stderr
pub fn stderr<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stderr = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
let preopen = PendingPreopen::new(move || {
let dir = OsDir::try_from(dir).map_err(WasiCtxBuilderError::from)?;
Ok(Box::new(dir))
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), preopen));
self
}
/// Add a preopened virtual directory.
pub fn preopened_virt<P: AsRef<Path>>(
&mut self,
dir: VirtualDirEntry,
guest_path: P,
) -> &mut Self {
fn populate_directory(virtentry: HashMap<String, VirtualDirEntry>, dir: &mut VirtualDir) {
for (path, entry) in virtentry.into_iter() {
match entry {
VirtualDirEntry::Directory(dir_entries) => {
let mut subdir = VirtualDir::new(true);
populate_directory(dir_entries, &mut subdir);
dir.add_dir(subdir, path);
}
VirtualDirEntry::File(content) => {
dir.add_file(content, path);
}
}
}
}
let guest_path_owned = guest_path.as_ref().to_owned();
let preopen = PendingPreopen::new(move || {
if let VirtualDirEntry::Directory(entries) = dir {
let mut dir = VirtualDir::new(true);
populate_directory(entries, &mut dir);
Ok(Box::new(dir))
} else {
Err(WasiCtxBuilderError::VirtualDirEntryRootNotADirectory(
guest_path_owned,
))
}
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), preopen));
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 will fail.
pub fn build(&mut self) -> WasiCtxBuilderResult<WasiCtx> {
// Process arguments and environment variables into `String`s, failing quickly if they
// contain any NUL bytes, or if conversion from `OsString` fails.
let args =
StringArray::from_pending_vec(self.args.take().expect("WasiCtxBuilder has args"))
.map_err(WasiCtxBuilderError::Args)?;
let env = StringArray::from_pending_map(self.env.take().expect("WasiCtxBuilder has env"))
.map_err(WasiCtxBuilderError::Env)?;
let mut entries = EntryTable::new();
// Populate the non-preopen entries.
for pending in vec![
self.stdin.take().unwrap(),
self.stdout.take().unwrap(),
self.stderr.take().unwrap(),
] {
tracing::debug!(
pending = tracing::field::debug(&pending),
"WasiCtx inserting entry"
);
let fd = match pending {
PendingEntry::Thunk(f) => {
let handle = EntryHandle::from(f()?);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
}
PendingEntry::Handle(handle) => {
let handle = EntryHandle::from(handle);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
}
};
tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted");
}
// Then add the preopen entries.
for (guest_path, preopen) in self.preopens.take().unwrap() {
let handle = EntryHandle::from(preopen.into()?);
let mut entry = Entry::new(handle);
entry.preopen_path = Some(guest_path);
let fd = entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted",);
}
Ok(WasiCtx {
args,
env,
entries: RefCell::new(entries),
})
}
}
struct EntryTable {
fd_pool: FdPool,
entries: HashMap<Fd, Rc<Entry>>,
}
impl EntryTable {
fn new() -> Self {
Self {
fd_pool: FdPool::new(),
entries: HashMap::new(),
}
}
fn contains(&self, fd: &Fd) -> bool {
self.entries.contains_key(fd)
}
fn insert(&mut self, entry: Entry) -> Option<Fd> {
let fd = self.fd_pool.allocate()?;
self.entries.insert(fd, Rc::new(entry));
Some(fd)
}
fn insert_at(&mut self, fd: &Fd, entry: Rc<Entry>) {
self.entries.insert(*fd, entry);
}
fn get(&self, fd: &Fd) -> Option<Rc<Entry>> {
self.entries.get(fd).map(Rc::clone)
}
fn remove(&mut self, fd: Fd) -> Option<Rc<Entry>> {
let entry = self.entries.remove(&fd)?;
self.fd_pool.deallocate(fd);
Some(entry)
}
}
pub struct WasiCtx {
entries: RefCell<EntryTable>,
pub(crate) args: StringArray,
pub(crate) env: StringArray,
pub args: StringArray,
pub env: StringArray,
pub random: RefCell<Box<dyn RngCore>>,
pub clocks: WasiClocks,
pub sched: Box<dyn WasiSched>,
pub table: Rc<RefCell<Table>>,
}
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>) -> WasiCtxBuilderResult<Self> {
WasiCtxBuilder::new()
.args(args)
.inherit_stdio()
.inherit_env()
.build()
pub fn builder(
random: RefCell<Box<dyn RngCore>>,
clocks: WasiClocks,
sched: Box<dyn WasiSched>,
table: Rc<RefCell<Table>>,
) -> WasiCtxBuilder {
WasiCtxBuilder(WasiCtx {
args: StringArray::new(),
env: StringArray::new(),
random,
clocks,
sched,
table,
})
}
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
pub(crate) fn contains_entry(&self, fd: Fd) -> bool {
self.entries.borrow().contains(&fd)
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
self.table()
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
}
/// Get an immutable `Entry` corresponding to the specified raw WASI `fd`.
pub(crate) fn get_entry(&self, fd: Fd) -> Result<Rc<Entry>, Error> {
match self.entries.borrow().get(&fd) {
Some(entry) => Ok(entry),
None => Err(Error::Badf),
pub fn insert_dir(
&self,
fd: u32,
dir: Box<dyn WasiDir>,
caps: DirCaps,
file_caps: FileCaps,
path: PathBuf,
) {
self.table().insert_at(
fd,
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
);
}
pub fn table(&self) -> RefMut<Table> {
self.table.borrow_mut()
}
}
/// Insert the specified `Entry` into the `WasiCtx` object.
///
/// The `Entry` 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_entry(&self, entry: Entry) -> Result<Fd, Error> {
self.entries.borrow_mut().insert(entry).ok_or(Error::Mfile)
pub struct WasiCtxBuilder(WasiCtx);
impl WasiCtxBuilder {
pub fn build(self) -> Result<WasiCtx, Error> {
use crate::file::TableFileExt;
let t = self.0.table();
for (fd, name) in ["stdin", "stdout", "stderr"].iter().enumerate() {
if t.get_file(fd as u32).is_err() {
return Err(anyhow::anyhow!(
"Cannot build WasiCtx: Missing required file `{}`",
name
));
}
}
drop(t);
Ok(self.0)
}
/// Insert the specified `Entry` with the specified raw WASI `fd` key into the `WasiCtx`
/// object.
pub(crate) fn insert_entry_at(&self, fd: Fd, entry: Rc<Entry>) {
self.entries.borrow_mut().insert_at(&fd, entry)
pub fn arg(mut self, arg: &str) -> Result<Self, StringArrayError> {
self.0.args.push(arg.to_owned())?;
Ok(self)
}
/// Remove `Entry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
pub(crate) fn remove_entry(&self, fd: Fd) -> Result<Rc<Entry>, Error> {
self.entries.borrow_mut().remove(fd).ok_or(Error::Badf)
pub fn env(mut self, var: &str, value: &str) -> Result<Self, StringArrayError> {
self.0.env.push(format!("{}={}", var, value))?;
Ok(self)
}
/*
pub(crate) fn args(&self) -> &impl StringArrayWriter {
&self.args
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
0,
f,
FileCaps::READ | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is read-only
);
self
}
pub(crate) fn env(&self) -> &impl StringArrayWriter {
&self.env
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
1,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
2,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn preopened_dir(
self,
dir: Box<dyn WasiDir>,
path: impl AsRef<Path>,
) -> Result<Self, Error> {
let caps = DirCaps::all();
let file_caps = FileCaps::all();
self.0.table().push(Box::new(DirEntry::new(
caps,
file_caps,
Some(path.as_ref().to_owned()),
dir,
)))?;
Ok(self)
}
*/
}

View File

@@ -0,0 +1,184 @@
use crate::file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile};
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::Ref;
use std::ops::Deref;
use std::path::PathBuf;
pub trait WasiDir {
fn as_any(&self) -> &dyn Any;
fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error>;
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error>;
fn create_dir(&self, path: &str) -> Result<(), Error>;
fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>>>, Error>;
fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>;
fn remove_dir(&self, path: &str) -> Result<(), Error>;
fn unlink_file(&self, path: &str) -> Result<(), Error>;
fn read_link(&self, path: &str) -> Result<PathBuf, Error>;
fn get_filestat(&self) -> Result<Filestat, Error>;
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error>;
fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>;
fn hard_link(
&self,
path: &str,
target_dir: &dyn WasiDir,
target_path: &str,
) -> Result<(), Error>;
fn set_times(
&self,
path: &str,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error>;
}
pub(crate) struct DirEntry {
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>, // precondition: PathBuf is valid unicode
dir: Box<dyn WasiDir>,
}
impl DirEntry {
pub fn new(
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>,
dir: Box<dyn WasiDir>,
) -> Self {
DirEntry {
caps,
file_caps,
preopen_path,
dir,
}
}
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
if self.file_caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable()
.context(format!("desired {:?}, has {:?}", caps, self.file_caps)))
}
}
pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> {
self.capable_of_dir(caps)?;
self.capable_of_file(file_caps)?;
self.caps = caps;
self.file_caps = file_caps;
Ok(())
}
pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps {
self.caps & desired_caps
}
pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps {
self.file_caps & desired_caps
}
pub fn get_dir_fdstat(&self) -> DirFdStat {
DirFdStat {
dir_caps: self.caps,
file_caps: self.file_caps,
}
}
pub fn preopen_path(&self) -> &Option<PathBuf> {
&self.preopen_path
}
}
pub trait DirEntryExt<'a> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error>;
}
impl<'a> DirEntryExt<'a> for Ref<'a, DirEntry> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error> {
self.capable_of_dir(caps)?;
Ok(Ref::map(self, |r| r.dir.deref()))
}
}
bitflags! {
pub struct DirCaps: u32 {
const CREATE_DIRECTORY = 0b1;
const CREATE_FILE = 0b10;
const LINK_SOURCE = 0b100;
const LINK_TARGET = 0b1000;
const OPEN = 0b10000;
const READDIR = 0b100000;
const READLINK = 0b1000000;
const RENAME_SOURCE = 0b10000000;
const RENAME_TARGET = 0b100000000;
const SYMLINK = 0b1000000000;
const REMOVE_DIRECTORY = 0b10000000000;
const UNLINK_FILE = 0b100000000000;
const PATH_FILESTAT_GET = 0b1000000000000;
const PATH_FILESTAT_SET_TIMES = 0b10000000000000;
const FILESTAT_GET = 0b100000000000000;
const FILESTAT_SET_TIMES = 0b1000000000000000;
}
}
#[derive(Debug, Clone)]
pub struct DirFdStat {
pub file_caps: FileCaps,
pub dir_caps: DirCaps,
}
pub(crate) trait TableDirExt {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error>;
fn is_preopen(&self, fd: u32) -> bool;
}
impl TableDirExt for crate::table::Table {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error> {
self.get(fd)
}
fn is_preopen(&self, fd: u32) -> bool {
if self.is::<DirEntry>(fd) {
let dir_entry: std::cell::Ref<DirEntry> = self.get(fd).unwrap();
dir_entry.preopen_path.is_some()
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct ReaddirEntity {
pub next: ReaddirCursor,
pub inode: u64,
pub name: String,
pub filetype: FileType,
}
#[derive(Debug, Copy, Clone)]
pub struct ReaddirCursor(u64);
impl From<u64> for ReaddirCursor {
fn from(c: u64) -> ReaddirCursor {
ReaddirCursor(c)
}
}
impl From<ReaddirCursor> for u64 {
fn from(c: ReaddirCursor) -> u64 {
c.0
}
}

View File

@@ -1,103 +0,0 @@
use crate::handle::{Filetype, Handle, HandleRights};
use crate::{Error, Result};
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::Rc;
pub struct EntryHandle(Rc<dyn Handle>);
impl EntryHandle {
#[allow(dead_code)]
pub(crate) fn new<T: Handle + 'static>(handle: T) -> Self {
Self(Rc::new(handle))
}
pub(crate) fn get(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl std::fmt::Debug for EntryHandle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("EntryHandle").field("opaque", &()).finish()
}
}
impl From<Box<dyn Handle>> for EntryHandle {
fn from(handle: Box<dyn Handle>) -> Self {
Self(handle.into())
}
}
impl Deref for EntryHandle {
type Target = dyn Handle;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
/// An abstraction struct serving as a wrapper for a `Handle` object.
///
/// Here, the `handle` field stores an instance of `Handle` type (such as a file descriptor, or
/// stdin handle), and accessing it can only be done via the provided `Entry::as_handle` method
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
/// `Handle` object is valid for the rights specified.
pub(crate) struct Entry {
handle: EntryHandle,
pub(crate) preopen_path: Option<PathBuf>,
// TODO: directories
}
impl Entry {
pub(crate) fn new(handle: EntryHandle) -> Self {
let preopen_path = None;
Self {
handle,
preopen_path,
}
}
pub(crate) fn get_file_type(&self) -> Filetype {
self.handle.get_file_type()
}
pub(crate) fn get_rights(&self) -> HandleRights {
self.handle.get_rights()
}
pub(crate) fn set_rights(&self, rights: HandleRights) {
self.handle.set_rights(rights)
}
/// Convert this `Entry` into a `Handle` object provided the specified
/// `rights` rights are set on this `Entry` object.
///
/// The `Entry` can only be converted into a valid `Handle` object if
/// the specified set of base rights, and inheriting rights encapsulated within `rights`
/// `HandleRights` structure is a subset of rights attached to this `Entry`. The check is
/// performed using `Entry::validate_rights` method. If the check fails, `Error::Notcapable`
/// is returned.
pub(crate) fn as_handle(&self, rights: HandleRights) -> Result<EntryHandle> {
self.validate_rights(rights)?;
Ok(self.handle.get())
}
/// Check if this `Entry` object satisfies the specified `HandleRights`; i.e., if
/// rights attached to this `Entry` object are a superset.
///
/// Upon unsuccessful check, `Error::Notcapable` is returned.
pub(crate) fn validate_rights(&self, rights: HandleRights) -> Result<()> {
let this_rights = self.handle.get_rights();
if this_rights.contains(rights) {
Ok(())
} else {
tracing::trace!(
required = tracing::field::display(rights),
actual = tracing::field::display(this_rights),
"validate_rights failed",
);
Err(Error::Notcapable)
}
}
}

View File

@@ -1,54 +1,47 @@
use cfg_if::cfg_if;
use thiserror::Error;
//! `wasi_common::Error` is now `anyhow::Error`.
//!
//! Snapshots (right now only `wasi_common::snapshots::preview_1`) contains
//! all of the logic for transforming an `Error` into the snapshot's own
//! `Errno`. They may do so by downcasting the error into any of:
//! * `std::io::Error` - these are thrown by `std`, `cap_std`, etc for most of
//! the operations WASI is concerned with.
//! * `wasi_common::ErrorKind` - these are a subset of the Errnos, and are
//! constructed directly by wasi-common or an impl rather than coming from the
//! OS or some library which doesn't know about WASI.
//! * `wiggle::GuestError`
//! * `std::num::TryFromIntError`
//! * `std::str::Utf8Error`
//! and then applying specialized logic to translate each of those into
//! `Errno`s.
//!
//! The `wasi_common::ErrorExt` trait provides human-friendly constructors for
//! the `wasi_common::ErrorKind` variants .
//!
//! If you throw an error that does not downcast to one of those, it will turn
//! into a `wiggle::Trap` and terminate execution.
//!
//! The real value of using `anyhow::Error` here is being able to use
//! `anyhow::Result::context` to aid in debugging of errors.
pub type Result<T> = std::result::Result<T, Error>;
pub use anyhow::Error;
/// Internal error type for the `wasi-common` crate.
/// Contains variants of the WASI `$errno` type are added according to what is actually used internally by
/// the crate. Not all values are represented presently.
#[derive(Debug, Error)]
pub enum Error {
#[error("Wiggle GuestError: {0}")]
Guest(#[from] wiggle::GuestError),
#[error("TryFromIntError: {0}")]
TryFromInt(#[from] std::num::TryFromIntError),
#[error("Utf8Error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("GetRandom: {0}")]
GetRandom(#[from] getrandom::Error),
/// Some corners of the WASI standard are unsupported.
#[error("Unsupported: {0}")]
Unsupported(&'static str),
/// The host OS may return an io error that doesn't match one of the
/// wasi errno variants we expect. We do not expose the details of this
/// error to the user.
#[error("Unexpected IoError: {0}")]
UnexpectedIo(#[source] std::io::Error),
// Below this, all variants are from the `$errno` type:
#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
/// Errno::Noent: No such file or directory
#[error("Noent: No such file or directory")]
Noent,
/// Errno::TooBig: Argument list too long
#[error("TooBig: Argument list too long")]
TooBig,
/// Errno::Acces: Permission denied
#[error("Acces: Permission denied")]
Acces,
/// Errno::Badf: Bad file descriptor
#[error("Badf: Bad file descriptor")]
Badf,
/// Errno::Busy: Device or resource busy
#[error("Busy: Device or resource busy")]
Busy,
/// Errno::Exist: File exists
#[error("Exist: File exists")]
Exist,
/// Errno::Fault: Bad address
#[error("Fault: Bad address")]
Fault,
/// Errno::Fbig: File too large
#[error("Fbig: File too large")]
Fbig,
/// Errno::Ilseq: Illegal byte sequence
#[error("Ilseq: Illegal byte sequence")]
Ilseq,
@@ -56,144 +49,93 @@ pub enum Error {
#[error("Inval: Invalid argument")]
Inval,
/// Errno::Io: I/O error
#[error("Io: I/o error")]
#[error("Io: I/O error")]
Io,
/// Errno::Isdir: Is a directory
#[error("Isdir: Is a directory")]
Isdir,
/// Errno::Loop: Too many levels of symbolic links
#[error("Loop: Too many levels of symbolic links")]
Loop,
/// Errno::Mfile: File descriptor value too large
#[error("Mfile: File descriptor value too large")]
Mfile,
/// Errno::Mlink: Too many links
#[error("Mlink: Too many links")]
Mlink,
/// Errno::Nametoolong: Filename too long
#[error("Nametoolong: Filename too long")]
Nametoolong,
/// Errno::Nfile: Too many files open in system
#[error("Nfile: Too many files open in system")]
Nfile,
/// Errno::Noent: No such file or directory
#[error("Noent: No such file or directory")]
Noent,
/// Errno::Nomem: Not enough space
#[error("Nomem: Not enough space")]
Nomem,
/// Errno::Nospc: No space left on device
#[error("Nospc: No space left on device")]
Nospc,
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
#[error("Notdir: Not a directory or a symbolic link to a directory")]
Notdir,
/// Errno::Notempty: Directory not empty.
#[error("Notempty: Directory not empty")]
Notempty,
/// Errno::Notsup: Not supported, or operation not supported on socket.
#[error("Notsup: Not supported, or operation not supported on socket")]
Notsup,
/// Errno::Overflow: Value too large to be stored in data type.
#[error("Overflow: Value too large to be stored in data type")]
Overflow,
/// Errno::Pipe: Broken pipe
#[error("Pipe: Broken pipe")]
Pipe,
/// Errno::Perm: Operation not permitted
#[error("Perm: Operation not permitted")]
Perm,
/// Errno::Range: Result too large
#[error("Range: Result too large")]
Range,
/// Errno::Spipe: Invalid seek
#[error("Spipe: Invalid seek")]
Spipe,
/// Errno::Notcapable: Extension: Capabilities insufficient
#[error("Notcapable: cabailities insufficient")]
Notcapable,
/// Errno::NotCapable: Not capable
#[error("Not capable")]
NotCapable,
}
impl From<std::convert::Infallible> for Error {
fn from(_err: std::convert::Infallible) -> Self {
unreachable!("should be impossible: From<Infallible>")
}
pub trait ErrorExt {
fn trap(msg: impl Into<String>) -> Self;
fn not_found() -> Self;
fn too_big() -> Self;
fn badf() -> Self;
fn exist() -> Self;
fn illegal_byte_sequence() -> Self;
fn invalid_argument() -> Self;
fn io() -> Self;
fn name_too_long() -> Self;
fn not_dir() -> Self;
fn not_supported() -> Self;
fn overflow() -> Self;
fn range() -> Self;
fn seek_pipe() -> Self;
fn not_capable() -> Self;
}
// Turning an io::Error into an Error has platform-specific behavior
cfg_if! {
if #[cfg(windows)] {
use winapi::shared::winerror;
use std::io;
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code as u32 {
winerror::ERROR_BAD_ENVIRONMENT => Self::TooBig,
winerror::ERROR_FILE_NOT_FOUND => Self::Noent,
winerror::ERROR_PATH_NOT_FOUND => Self::Noent,
winerror::ERROR_TOO_MANY_OPEN_FILES => Self::Nfile,
winerror::ERROR_ACCESS_DENIED => Self::Acces,
winerror::ERROR_SHARING_VIOLATION => Self::Acces,
winerror::ERROR_PRIVILEGE_NOT_HELD => Self::Notcapable,
winerror::ERROR_INVALID_HANDLE => Self::Badf,
winerror::ERROR_INVALID_NAME => Self::Noent,
winerror::ERROR_NOT_ENOUGH_MEMORY => Self::Nomem,
winerror::ERROR_OUTOFMEMORY => Self::Nomem,
winerror::ERROR_DIR_NOT_EMPTY => Self::Notempty,
winerror::ERROR_NOT_READY => Self::Busy,
winerror::ERROR_BUSY => Self::Busy,
winerror::ERROR_NOT_SUPPORTED => Self::Notsup,
winerror::ERROR_FILE_EXISTS => Self::Exist,
winerror::ERROR_BROKEN_PIPE => Self::Pipe,
winerror::ERROR_BUFFER_OVERFLOW => Self::Nametoolong,
winerror::ERROR_NOT_A_REPARSE_POINT => Self::Inval,
winerror::ERROR_NEGATIVE_SEEK => Self::Inval,
winerror::ERROR_DIRECTORY => Self::Notdir,
winerror::ERROR_ALREADY_EXISTS => Self::Exist,
_ => Self::UnexpectedIo(err),
},
None => Self::UnexpectedIo(err),
}
}
}
} else {
use std::io;
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code {
libc::EPIPE => Self::Pipe,
libc::EPERM => Self::Perm,
libc::ENOENT => Self::Noent,
libc::ENOMEM => Self::Nomem,
libc::E2BIG => Self::TooBig,
libc::EIO => Self::Io,
libc::EBADF => Self::Badf,
libc::EBUSY => Self::Busy,
libc::EACCES => Self::Acces,
libc::EFAULT => Self::Fault,
libc::ENOTDIR => Self::Notdir,
libc::EISDIR => Self::Isdir,
libc::EINVAL => Self::Inval,
libc::EEXIST => Self::Exist,
libc::EFBIG => Self::Fbig,
libc::ENOSPC => Self::Nospc,
libc::ESPIPE => Self::Spipe,
libc::EMFILE => Self::Mfile,
libc::EMLINK => Self::Mlink,
libc::ENAMETOOLONG => Self::Nametoolong,
libc::ENFILE => Self::Nfile,
libc::ENOTEMPTY => Self::Notempty,
libc::ELOOP => Self::Loop,
libc::EOVERFLOW => Self::Overflow,
libc::EILSEQ => Self::Ilseq,
libc::ENOTSUP => Self::Notsup,
_ => Self::UnexpectedIo(err),
},
None => {
Self::UnexpectedIo(err)
}
}
}
}
impl ErrorExt for Error {
fn trap(msg: impl Into<String>) -> Self {
anyhow::anyhow!(msg.into())
}
fn not_found() -> Self {
ErrorKind::Noent.into()
}
fn too_big() -> Self {
ErrorKind::TooBig.into()
}
fn badf() -> Self {
ErrorKind::Badf.into()
}
fn exist() -> Self {
ErrorKind::Exist.into()
}
fn illegal_byte_sequence() -> Self {
ErrorKind::Ilseq.into()
}
fn invalid_argument() -> Self {
ErrorKind::Inval.into()
}
fn io() -> Self {
ErrorKind::Io.into()
}
fn name_too_long() -> Self {
ErrorKind::Nametoolong.into()
}
fn not_dir() -> Self {
ErrorKind::Notdir.into()
}
fn not_supported() -> Self {
ErrorKind::Notsup.into()
}
fn overflow() -> Self {
ErrorKind::Overflow.into()
}
fn range() -> Self {
ErrorKind::Range.into()
}
fn seek_pipe() -> Self {
ErrorKind::Spipe.into()
}
fn not_capable() -> Self {
ErrorKind::NotCapable.into()
}
}

View File

@@ -1,142 +0,0 @@
//! Contains mechanism for managing the WASI file descriptor
//! pool. It's intended to be mainly used within the `WasiCtx`
//! object(s).
/// Any type wishing to be treated as a valid WASI file descriptor
/// should implement this trait.
///
/// This trait is required as internally we use `u32` to represent
/// and manage raw file descriptors.
pub(crate) trait Fd {
/// Convert to `u32`.
fn as_raw(&self) -> u32;
/// Convert from `u32`.
fn from_raw(raw_fd: u32) -> Self;
}
impl Fd for u32 {
fn as_raw(&self) -> u32 {
*self
}
fn from_raw(raw_fd: u32) -> Self {
raw_fd
}
}
/// This container tracks and manages all file descriptors that
/// were already allocated.
/// Internally, we use `u32` to represent the file descriptors;
/// however, the caller may supply any type `T` such that it
/// implements the `Fd` trait when requesting a new descriptor
/// via the `allocate` method, or when returning one back via
/// the `deallocate` method.
#[derive(Debug)]
pub(crate) struct FdPool {
next_alloc: Option<u32>,
available: Vec<u32>,
}
impl FdPool {
pub fn new() -> Self {
Self {
next_alloc: Some(0),
available: Vec::new(),
}
}
/// Obtain another valid WASI file descriptor.
///
/// If we've handed out the maximum possible amount of file
/// descriptors (which would be equal to `2^32 + 1` accounting for `0`),
/// then this method will return `None` to signal that case.
/// Otherwise, a new file descriptor is return as `Some(fd)`.
pub fn allocate<T: Fd>(&mut self) -> Option<T> {
if let Some(fd) = self.available.pop() {
// Since we've had free, unclaimed handle in the pool,
// simply claim it and return.
return Some(T::from_raw(fd));
}
// There are no free handles available in the pool, so try
// allocating an additional one into the pool. If we've
// reached our max number of handles, we will fail with None
// instead.
let fd = self.next_alloc.take()?;
// It's OK to not unpack the result of `fd.checked_add()` here which
// can fail since we check for `None` in the snippet above.
self.next_alloc = fd.checked_add(1);
Some(T::from_raw(fd))
}
/// Return a file descriptor back to the pool.
///
/// If the caller tries to return a file descriptor that was
/// not yet allocated (via spoofing, etc.), this method
/// will panic.
pub fn deallocate<T: Fd>(&mut self, fd: T) {
let fd = fd.as_raw();
if let Some(next_alloc) = self.next_alloc {
assert!(fd < next_alloc);
}
debug_assert!(!self.available.contains(&fd));
self.available.push(fd);
}
}
#[cfg(test)]
mod test {
use super::FdPool;
use std::ops::Deref;
#[derive(Debug)]
struct Fd(u32);
impl super::Fd for Fd {
fn as_raw(&self) -> u32 {
self.0
}
fn from_raw(raw_fd: u32) -> Self {
Self(raw_fd)
}
}
impl Deref for Fd {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[test]
fn basics() {
let mut fd_pool = FdPool::new();
let mut fd: Fd = fd_pool.allocate().expect("success allocating 0");
assert_eq!(*fd, 0);
fd = fd_pool.allocate().expect("success allocating 1");
assert_eq!(*fd, 1);
fd = fd_pool.allocate().expect("success allocating 2");
assert_eq!(*fd, 2);
fd_pool.deallocate(1u32);
fd_pool.deallocate(0u32);
fd = fd_pool.allocate().expect("success reallocating 0");
assert_eq!(*fd, 0);
fd = fd_pool.allocate().expect("success reallocating 1");
assert_eq!(*fd, 1);
fd = fd_pool.allocate().expect("success allocating 3");
assert_eq!(*fd, 3);
}
#[test]
#[should_panic]
fn deallocate_nonexistent() {
let mut fd_pool = FdPool::new();
fd_pool.deallocate(0u32);
}
#[test]
fn max_allocation() {
let mut fd_pool = FdPool::new();
// Spoof reaching the limit of allocs.
fd_pool.next_alloc = None;
assert!(fd_pool.allocate::<Fd>().is_none());
}
}

View File

@@ -0,0 +1,177 @@
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::{Ref, RefMut};
use std::ops::{Deref, DerefMut};
pub trait WasiFile {
fn as_any(&self) -> &dyn Any;
fn datasync(&self) -> Result<(), Error>; // write op
fn sync(&self) -> Result<(), Error>; // file op
fn get_filetype(&self) -> Result<FileType, Error>; // file op
fn get_fdflags(&self) -> Result<FdFlags, Error>; // file op
fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op
fn get_filestat(&self) -> Result<Filestat, Error>; // split out get_length as a read & write op, rest is a file op
fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error>; // file op
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error>;
fn read_vectored(&self, bufs: &mut [std::io::IoSliceMut]) -> Result<u64, Error>; // read op
fn read_vectored_at(&self, bufs: &mut [std::io::IoSliceMut], offset: u64)
-> Result<u64, Error>; // file op
fn write_vectored(&self, bufs: &[std::io::IoSlice]) -> Result<u64, Error>; // write op
fn write_vectored_at(&self, bufs: &[std::io::IoSlice], offset: u64) -> Result<u64, Error>; // file op
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FileType {
Unknown,
BlockDevice,
CharacterDevice,
Directory,
RegularFile,
SocketDgram,
SocketStream,
SymbolicLink,
Pipe,
}
bitflags! {
pub struct FdFlags: u32 {
const APPEND = 0b1;
const DSYNC = 0b10;
const NONBLOCK = 0b100;
const RSYNC = 0b1000;
const SYNC = 0b10000;
}
}
bitflags! {
pub struct OFlags: u32 {
const CREATE = 0b1;
const DIRECTORY = 0b10;
const EXCLUSIVE = 0b100;
const TRUNCATE = 0b1000;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Filestat {
pub device_id: u64,
pub inode: u64,
pub filetype: FileType,
pub nlink: u64,
pub size: u64, // this is a read field, the rest are file fields
pub atim: Option<std::time::SystemTime>,
pub mtim: Option<std::time::SystemTime>,
pub ctim: Option<std::time::SystemTime>,
}
pub(crate) trait TableFileExt {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error>;
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error>;
}
impl TableFileExt for crate::table::Table {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error> {
self.get(fd)
}
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error> {
self.get_mut(fd)
}
}
pub(crate) struct FileEntry {
caps: FileCaps,
file: Box<dyn WasiFile>,
}
impl FileEntry {
pub fn new(caps: FileCaps, file: Box<dyn WasiFile>) -> Self {
FileEntry { caps, file }
}
pub fn capable_of(&self, caps: FileCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> {
self.capable_of(caps)?;
self.caps = caps;
Ok(())
}
pub fn get_fdstat(&self) -> Result<FdStat, Error> {
Ok(FdStat {
filetype: self.file.get_filetype()?,
caps: self.caps,
flags: self.file.get_fdflags()?,
})
}
}
pub trait FileEntryExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryExt<'a> for Ref<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(Ref::map(self, |r| r.file.deref()))
}
}
pub trait FileEntryMutExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryMutExt<'a> for RefMut<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(RefMut::map(self, |r| r.file.deref_mut()))
}
}
bitflags! {
pub struct FileCaps : u32 {
const DATASYNC = 0b1;
const READ = 0b10;
const SEEK = 0b100;
const FDSTAT_SET_FLAGS = 0b1000;
const SYNC = 0b10000;
const TELL = 0b100000;
const WRITE = 0b1000000;
const ADVISE = 0b10000000;
const ALLOCATE = 0b100000000;
const FILESTAT_GET = 0b1000000000;
const FILESTAT_SET_SIZE = 0b10000000000;
const FILESTAT_SET_TIMES = 0b100000000000;
const POLL_READWRITE = 0b1000000000000;
}
}
#[derive(Debug, Clone)]
pub struct FdStat {
pub filetype: FileType,
pub caps: FileCaps,
pub flags: FdFlags,
}
#[derive(Debug, Clone)]
pub enum Advice {
Normal,
Sequential,
Random,
WillNeed,
DontNeed,
NoReuse,
}

View File

@@ -1,217 +0,0 @@
use crate::fs::{Fd, File, OpenOptions, ReadDir};
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::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 WasiCtx,
fd: Fd,
}
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 WasiCtx, fd: Fd) -> 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 = Fd::from(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_LOOKUPFLAGS_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 = Fd::from(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_LOOKUPFLAGS_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
wasi::__WASI_OFLAGS_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 = Fd::from(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_LOOKUPFLAGS_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
path.as_os_str().len(),
wasi::__WASI_OFLAGS_CREAT | wasi::__WASI_OFLAGS_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 _ = self.ctx.fd_close(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

View File

@@ -1,49 +0,0 @@
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

View File

@@ -1,53 +0,0 @@
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

View File

@@ -1,113 +0,0 @@
use crate::fs::{Fd, Metadata};
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::Result;
use crate::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 WasiCtx,
fd: Fd,
}
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 WasiCtx, fd: Fd) -> 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) -> Result<()> {
self.ctx.fd_sync(self.fd)?;
Ok(())
}
/// 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) -> Result<()> {
self.ctx.fd_datasync(self.fd)?;
Ok(())
}
/// 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) -> Result<()> {
self.ctx.fd_filestat_set_size(self.fd, size)?;
Ok(())
}
/// 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) -> 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 _ = self.ctx.fd_close(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> {
// TODO
// let iov = [Iovec {
// 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

View File

@@ -1,49 +0,0 @@
/// 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

View File

@@ -1,106 +0,0 @@
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

View File

@@ -1,52 +0,0 @@
//! 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 file;
mod file_type;
mod metadata;
mod open_options;
mod permissions;
mod readdir;
pub use crate::wasi::types::Fd;
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::*;

View File

@@ -1,99 +0,0 @@
/// 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?

View File

@@ -1,37 +0,0 @@
/// 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

View File

@@ -1,31 +0,0 @@
use crate::fs::{DirEntry, Fd};
/// 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: Fd,
}
impl ReadDir {
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
pub unsafe fn from_raw_wasi_fd(fd: Fd) -> 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

View File

@@ -1,360 +0,0 @@
pub use crate::wasi::types::{
Advice, Dircookie, Dirent, Fdflags, Fdstat, Filedelta, Filesize, Filestat, Filetype, Fstflags,
Lookupflags, Oflags, Prestat, PrestatDir, Rights, Size, Timestamp, Whence,
};
use crate::{Error, Result};
use std::any::Any;
use std::fmt;
use std::io::{self, SeekFrom};
/// Represents rights of a `Handle`, either already held or required.
#[derive(Debug, Copy, Clone)]
pub struct HandleRights {
pub(crate) base: Rights,
pub(crate) inheriting: Rights,
}
impl HandleRights {
/// Creates new `HandleRights` instance from `base` and `inheriting` rights.
pub fn new(base: Rights, inheriting: Rights) -> Self {
Self { base, inheriting }
}
/// Creates new `HandleRights` instance from `base` rights only, keeping
/// `inheriting` set to none.
pub fn from_base(base: Rights) -> Self {
Self {
base,
inheriting: Rights::empty(),
}
}
/// Creates new `HandleRights` instance with both `base` and `inheriting`
/// rights set to none.
pub fn empty() -> Self {
Self {
base: Rights::empty(),
inheriting: Rights::empty(),
}
}
/// Checks if `other` is a subset of those rights.
pub fn contains(&self, other: Self) -> bool {
self.base.contains(other.base) && self.inheriting.contains(other.inheriting)
}
/// Returns base rights.
pub fn base(&self) -> Rights {
self.base
}
/// Returns inheriting rights.
pub fn inheriting(&self) -> Rights {
self.inheriting
}
}
impl fmt::Display for HandleRights {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"HandleRights {{ base: {}, inheriting: {} }}",
self.base, self.inheriting
)
}
}
/// Generic interface for all WASI-compatible handles. We currently group these into two groups:
/// * OS-based resources (actual, real resources): `OsFile`, `OsDir`, `OsOther`, and `Stdio`,
/// * virtual files and directories: VirtualDir`, and `InMemoryFile`.
///
/// # Constructing `Handle`s representing OS-based resources
///
/// Each type of handle can either be constructed directly (see docs entry for a specific handle
/// type such as `OsFile`), or you can let the `wasi_common` crate's machinery work it out
/// automatically for you using `std::convert::TryInto` from `std::fs::File`:
///
/// ```rust,no_run
/// use std::convert::TryInto;
/// use wasi_common::Handle;
/// use std::fs::OpenOptions;
///
/// let some_file = OpenOptions::new().read(true).open("some_file").unwrap();
/// let wasi_handle: Box<dyn Handle> = some_file.try_into().unwrap();
/// ```
pub trait Handle {
fn as_any(&self) -> &dyn Any;
fn try_clone(&self) -> io::Result<Box<dyn Handle>>;
fn get_file_type(&self) -> Filetype;
fn get_rights(&self) -> HandleRights {
HandleRights::empty()
}
fn set_rights(&self, rights: HandleRights);
fn is_directory(&self) -> bool {
self.get_file_type() == Filetype::Directory
}
/// Test whether this descriptor is considered a tty within WASI.
/// Note that since WASI itself lacks an `isatty` syscall and relies
/// on a conservative approximation, we use the same approximation here.
fn is_tty(&self) -> bool {
let file_type = self.get_file_type();
let rights = self.get_rights();
let required_rights = HandleRights::from_base(Rights::FD_SEEK | Rights::FD_TELL);
file_type == Filetype::CharacterDevice && rights.contains(required_rights)
}
// TODO perhaps should be a separate trait?
// FdOps
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn datasync(&self) -> Result<()> {
Err(Error::Inval)
}
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(Fdflags::empty())
}
fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> {
Err(Error::Badf)
}
fn filestat_get(&self) -> Result<Filestat> {
Err(Error::Badf)
}
fn filestat_set_size(&self, _st_size: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn filestat_set_times(
&self,
_atim: Timestamp,
_mtim: Timestamp,
_fst_flags: Fstflags,
) -> Result<()> {
Err(Error::Badf)
}
fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result<usize> {
Err(Error::Badf)
}
fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result<usize> {
Err(Error::Badf)
}
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Err(Error::Badf)
}
fn readdir<'a>(
&'a self,
_cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>> + 'a>> {
Err(Error::Badf)
}
fn seek(&self, _offset: SeekFrom) -> Result<u64> {
Err(Error::Badf)
}
fn sync(&self) -> Result<()> {
Ok(())
}
fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result<usize> {
Err(Error::Badf)
}
// TODO perhaps should be a separate trait?
// PathOps
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
fn filestat_get_at(&self, _path: &str, _follow: bool) -> Result<Filestat> {
Err(Error::Acces)
}
fn filestat_set_times_at(
&self,
_path: &str,
_atim: Timestamp,
_mtim: Timestamp,
_fst_flags: Fstflags,
_follow: bool,
) -> Result<()> {
Err(Error::Acces)
}
fn openat(
&self,
_path: &str,
_read: bool,
_write: bool,
_oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
Err(Error::Acces)
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Acces)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Acces)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Acces)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Acces)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Acces)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
}
impl From<std::fs::FileType> for Filetype {
fn from(ftype: std::fs::FileType) -> Self {
if ftype.is_file() {
Self::RegularFile
} else if ftype.is_dir() {
Self::Directory
} else if ftype.is_symlink() {
Self::SymbolicLink
} else {
Self::Unknown
}
}
}
pub(crate) trait AsBytes {
fn as_bytes(&self) -> Result<Vec<u8>>;
}
impl AsBytes for Dirent {
fn as_bytes(&self) -> Result<Vec<u8>> {
use std::convert::TryInto;
use wiggle::GuestType;
assert_eq!(
Self::guest_size(),
std::mem::size_of::<Self>() as _,
"guest repr of Dirent and host repr should match"
);
let offset = Self::guest_size().try_into()?;
let mut bytes: Vec<u8> = Vec::with_capacity(offset);
bytes.resize(offset, 0);
let ptr = bytes.as_mut_ptr() as *mut Self;
unsafe { ptr.write_unaligned(self.clone()) };
Ok(bytes)
}
}
pub(crate) trait RightsExt: Sized {
fn block_device_base() -> Self;
fn block_device_inheriting() -> Self;
fn character_device_base() -> Self;
fn character_device_inheriting() -> Self;
fn directory_base() -> Self;
fn directory_inheriting() -> Self;
fn regular_file_base() -> Self;
fn regular_file_inheriting() -> Self;
fn socket_base() -> Self;
fn socket_inheriting() -> Self;
fn tty_base() -> Self;
fn tty_inheriting() -> Self;
}
impl RightsExt for Rights {
// Block and character device interaction is outside the scope of
// WASI. Simply allow everything.
fn block_device_base() -> Self {
Self::all()
}
fn block_device_inheriting() -> Self {
Self::all()
}
fn character_device_base() -> Self {
Self::all()
}
fn character_device_inheriting() -> Self {
Self::all()
}
// Only allow directory operations on directories. Directories can only
// yield file descriptors to other directories and files.
fn directory_base() -> Self {
Self::FD_FDSTAT_SET_FLAGS
| Self::FD_SYNC
| Self::FD_ADVISE
| Self::PATH_CREATE_DIRECTORY
| Self::PATH_CREATE_FILE
| Self::PATH_LINK_SOURCE
| Self::PATH_LINK_TARGET
| Self::PATH_OPEN
| Self::FD_READDIR
| Self::PATH_READLINK
| Self::PATH_RENAME_SOURCE
| Self::PATH_RENAME_TARGET
| Self::PATH_FILESTAT_GET
| Self::PATH_FILESTAT_SET_SIZE
| Self::PATH_FILESTAT_SET_TIMES
| Self::FD_FILESTAT_GET
| Self::FD_FILESTAT_SET_TIMES
| Self::PATH_SYMLINK
| Self::PATH_UNLINK_FILE
| Self::PATH_REMOVE_DIRECTORY
| Self::POLL_FD_READWRITE
}
fn directory_inheriting() -> Self {
Self::all() ^ Self::SOCK_SHUTDOWN
}
// Operations that apply to regular files.
fn regular_file_base() -> Self {
Self::FD_DATASYNC
| Self::FD_READ
| Self::FD_SEEK
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_SYNC
| Self::FD_TELL
| Self::FD_WRITE
| Self::FD_ADVISE
| Self::FD_ALLOCATE
| Self::FD_FILESTAT_GET
| Self::FD_FILESTAT_SET_SIZE
| Self::FD_FILESTAT_SET_TIMES
| Self::POLL_FD_READWRITE
}
fn regular_file_inheriting() -> Self {
Self::empty()
}
// Operations that apply to sockets and socket pairs.
fn socket_base() -> Self {
Self::FD_READ
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_WRITE
| Self::FD_FILESTAT_GET
| Self::POLL_FD_READWRITE
| Self::SOCK_SHUTDOWN
}
fn socket_inheriting() -> Self {
Self::all()
}
// Operations that apply to TTYs.
fn tty_base() -> Self {
Self::FD_READ
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_WRITE
| Self::FD_FILESTAT_GET
| Self::POLL_FD_READWRITE
}
fn tty_inheriting() -> Self {
Self::empty()
}
}
pub(crate) const DIRCOOKIE_START: Dircookie = 0;

View File

@@ -1,45 +1,73 @@
#![deny(
// missing_docs,
trivial_numeric_casts,
unused_extern_crates,
unstable_features,
clippy::use_self
)]
#![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::map_unwrap_or,
clippy::clippy::unicode_not_nfc,
clippy::use_self
)
)]
//! ## The `WasiFile` and `WasiDir` traits
//!
//! The WASI specification only defines one `handle` type, `fd`, on which all
//! operations on both files and directories (aka dirfds) are defined. We
//! believe this is a design mistake, and are architecting wasi-common to make
//! this straightforward to correct in future snapshots of WASI. Wasi-common
//! internally treats files and directories as two distinct resource types in
//! the table - `Box<dyn WasiFile>` and `Box<dyn WasiDir>`. The snapshot 0 and
//! 1 interfaces via `fd` will attempt to downcast a table element to one or
//! both of these interfaces depending on what is appropriate - e.g.
//! `fd_close` operates on both files and directories, `fd_read` only operates
//! on files, and `fd_readdir` only operates on directories.
//! The `WasiFile` and `WasiDir` traits are defined by `wasi-common` in terms
//! of types defined directly in the crate's source code (I decided it should
//! NOT those generated by the `wiggle` proc macros, see snapshot architecture
//! below), as well as the `cap_std::time` family of types. And, importantly,
//! `wasi-common` itself provides no implementation of `WasiDir`, and only two
//! trivial implementations of `WasiFile` on the `crate::pipe::{ReadPipe,
//! WritePipe}` types, which in turn just delegate to `std::io::{Read,
//! Write}`. In order for `wasi-common` to access the local filesystem at all,
//! you need to provide `WasiFile` and `WasiDir` impls through either the new
//! `wasi-cap-std-sync` crate found at `crates/wasi-common/cap-std-sync` - see
//! the section on that crate below - or by providing your own implementation
//! from elsewhere.
//!
//! This design makes it possible for `wasi-common` embedders to statically
//! reason about access to the local filesystem by examining what impls are
//! linked into an application. We found that this separation of concerns also
//! makes it pretty enjoyable to write alternative implementations, e.g. a
//! virtual filesystem (which will land in a future PR).
//!
//! ## Traits for the rest of WASI's features
//!
//! Other aspects of a WASI implementation are not yet considered resources
//! and accessed by `handle`. We plan to correct this design deficiency in
//! WASI in the future, but for now we have designed the following traits to
//! provide embedders with the same sort of implementation flexibility they
//! get with WasiFile/WasiDir:
//!
//! * Timekeeping: `WasiSystemClock` and `WasiMonotonicClock` provide the two
//! interfaces for a clock. `WasiSystemClock` represents time as a
//! `cap_std::time::SystemTime`, and `WasiMonotonicClock` represents time as
//! `cap_std::time::Instant`. * Randomness: we re-use the `cap_rand::RngCore`
//! trait to represent a randomness source. A trivial `Deterministic` impl is
//! provided. * Scheduling: The `WasiSched` trait abstracts over the
//! `sched_yield` and `poll_oneoff` functions.
//!
//! Users can provide implementations of each of these interfaces to the
//! `WasiCtx::builder(...)` function. The
//! `wasi_cap_std_sync::WasiCtxBuilder::new()` function uses this public
//! interface to plug in its own implementations of each of these resources.
pub mod clocks;
mod ctx;
mod entry;
pub mod dir;
mod error;
mod fdpool;
pub mod fs;
mod handle;
mod path;
mod sandboxed_tty_writer;
pub(crate) mod sched;
pub mod file;
pub mod pipe;
pub mod random;
pub mod sched;
pub mod snapshots;
mod string_array;
mod sys;
pub mod virtfs;
pub mod wasi;
pub mod table;
pub use ctx::{WasiCtx, WasiCtxBuilder, WasiCtxBuilderError};
pub use error::{Error, Result};
pub use handle::{Handle, HandleRights};
pub use sys::osdir::OsDir;
pub use sys::osfile::OsFile;
pub use sys::osother::OsOther;
pub use sys::preopen_dir;
pub use virtfs::{FileContents, VirtualDirEntry};
pub use cap_rand::RngCore;
pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock};
pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use dir::WasiDir;
pub use error::{Error, ErrorExt, ErrorKind};
pub use file::WasiFile;
pub use sched::{Poll, WasiSched};
pub use string_array::StringArrayError;
pub use table::Table;

View File

@@ -1,190 +0,0 @@
use crate::entry::Entry;
use crate::handle::{Fdflags, Filetype, Handle, HandleRights, Lookupflags, Oflags};
use crate::{Error, Result};
use std::path::{Component, Path};
use std::str;
pub(crate) use crate::sys::path::{from_host, open_rights};
/// 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 get(
entry: &Entry,
required_rights: HandleRights,
dirflags: Lookupflags,
path: &str,
needs_final_component: bool,
) -> Result<(Box<dyn Handle>, String)> {
const MAX_SYMLINK_EXPANSIONS: usize = 128;
tracing::trace!(path = path);
if path.contains('\0') {
// if contains NUL, return Ilseq
return Err(Error::Ilseq);
}
if entry.get_file_type() != Filetype::Directory {
// if `dirfd` doesn't refer to a directory, return `Notdir`.
return Err(Error::Notdir);
}
let handle = entry.as_handle(required_rights)?;
let dirfd = handle.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) => {
tracing::debug!(cur_path = tracing::field::display(&cur_path), "path get");
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::Noent),
Some(p) => p,
};
let tail = components.as_path();
if tail.components().next().is_some() {
let mut tail = from_host(tail.as_os_str())?;
if ends_with_slash {
tail.push('/');
}
path_stack.push(tail);
}
tracing::debug!(path_stack = tracing::field::debug(&path_stack), "path_get");
match head {
Component::Prefix(_) | Component::RootDir => {
// path is absolute!
return Err(Error::Notcapable);
}
Component::CurDir => {
// "." so skip
}
Component::ParentDir => {
// ".." so pop a dir
let _ = dir_stack.pop().ok_or(Error::Notcapable)?;
// we're not allowed to pop past the original directory
if dir_stack.is_empty() {
return Err(Error::Notcapable);
}
}
Component::Normal(head) => {
let mut head = from_host(head)?;
if ends_with_slash {
// preserve trailing slash
head.push('/');
}
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
match fd.openat(
&head,
false,
false,
Oflags::DIRECTORY,
Fdflags::empty(),
) {
Ok(new_dir) => {
dir_stack.push(new_dir);
}
Err(e) => {
match e {
Error::Loop | Error::Mlink | Error::Notdir =>
// Check to see if it was a symlink. Linux indicates
// this with ENOTDIR because of the O_DIRECTORY flag.
{
// attempt symlink expansion
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
let mut link_path = fd.readlinkat(&head)?;
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::Loop);
}
if head.ends_with('/') {
link_path.push('/');
}
tracing::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
}
_ => {
return Err(e);
}
}
}
}
continue;
} else if ends_with_slash || dirflags.contains(Lookupflags::SYMLINK_FOLLOW)
{
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
// symlink expansion
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
match fd.readlinkat(&head) {
Ok(mut link_path) => {
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::Loop);
}
if head.ends_with('/') {
link_path.push('/');
}
tracing::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
continue;
}
Err(Error::Inval) | Err(Error::Noent) | Err(Error::Notdir) => {
// this handles the cases when trying to link to
// a destination that already exists, and the target
// path contains a slash
}
Err(e) => {
return Err(e);
}
}
}
// not a symlink, so we're done;
return Ok((dir_stack.pop().ok_or(Error::Notcapable)?, 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((dir_stack.pop().ok_or(Error::Notcapable)?, String::from(".")));
}
}
}
}

View File

@@ -0,0 +1,321 @@
// This is mostly stubs
#![allow(unused_variables, dead_code)]
//! Virtual pipes.
//!
//! These types provide easy implementations of `WasiFile` that mimic much of the behavior of Unix
//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other
//! than OS files.
//!
//! Some convenience constructors are included for common backing types like `Vec<u8>` and `String`,
//! but the virtual pipes can be instantiated with any `Read` or `Write` type.
//!
use crate::{
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt, SystemTimeSpec,
};
use std::any::Any;
use std::convert::TryInto;
use std::io::{self, Read, Write};
use std::sync::{Arc, RwLock};
/// A virtual pipe read end.
///
/// A variety of `From` impls are provided so that common pipe types are easy to create. For example:
///
/// ```no_run
/// # use std::rc::Rc;
/// # use std::cell::RefCell;
/// use wasi_common::{pipe::ReadPipe, WasiCtx, Table};
/// let stdin = ReadPipe::from("hello from stdin!");
/// // Brint these instances from elsewhere (e.g. wasi-cap-std-sync):
/// let random = todo!();
/// let clocks = todo!();
/// let sched = todo!();
/// let table = Rc::new(RefCell::new(Table::new()));
/// let ctx = WasiCtx::builder(random, clocks, sched, table)
/// .stdin(Box::new(stdin.clone()))
/// .build();
/// ```
#[derive(Debug)]
pub struct ReadPipe<R: Read> {
reader: Arc<RwLock<R>>,
}
impl<R: Read> Clone for ReadPipe<R> {
fn clone(&self) -> Self {
Self {
reader: self.reader.clone(),
}
}
}
impl<R: Read> ReadPipe<R> {
/// Create a new pipe from a `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn new(r: R) -> Self {
Self::from_shared(Arc::new(RwLock::new(r)))
}
/// Create a new pipe from a shareable `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn from_shared(reader: Arc<RwLock<R>>) -> Self {
Self { reader }
}
/// Try to convert this `ReadPipe<R>` back to the underlying `R` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `R` exist.
pub fn try_into_inner(mut self) -> Result<R, Self> {
match Arc::try_unwrap(self.reader) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(reader) => {
self.reader = reader;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<R> {
RwLock::write(&self.reader).unwrap()
}
}
impl From<Vec<u8>> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: Vec<u8>) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&[u8]> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: &[u8]) -> Self {
Self::from(r.to_vec())
}
}
impl From<String> for ReadPipe<io::Cursor<String>> {
fn from(r: String) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&str> for ReadPipe<io::Cursor<String>> {
fn from(r: &str) -> Self {
Self::from(r.to_string())
}
}
impl<R: Read + Any> WasiFile for ReadPipe<R> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(()) // trivial: no implementation needed
}
fn sync(&self) -> Result<(), Error> {
Ok(()) // trivial
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Read :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
let n = self.borrow().read_vectored(bufs)?;
Ok(n.try_into()?)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}
/// A virtual pipe write end.
///
/// ```no_run
/// # use std::rc::Rc;
/// # use std::cell::RefCell;
/// use wasi_common::{pipe::WritePipe, WasiCtx, Table};
/// let stdout = WritePipe::new_in_memory();
/// // Brint these instances from elsewhere (e.g. wasi-cap-std-sync):
/// let random = todo!();
/// let clocks = todo!();
/// let sched = todo!();
/// let table = Rc::new(RefCell::new(Table::new()));
/// let ctx = WasiCtx::builder(random, clocks, sched, table)
/// .stdout(Box::new(stdout.clone()))
/// .build();
/// // use ctx in an instance, then make sure it is dropped:
/// drop(ctx);
/// let contents: Vec<u8> = stdout.try_into_inner().expect("sole remaining reference to WritePipe").into_inner();
/// println!("contents of stdout: {:?}", contents);
/// ```
#[derive(Debug)]
pub struct WritePipe<W: Write> {
writer: Arc<RwLock<W>>,
}
impl<W: Write> Clone for WritePipe<W> {
fn clone(&self) -> Self {
Self {
writer: self.writer.clone(),
}
}
}
impl<W: Write> WritePipe<W> {
/// Create a new pipe from a `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn new(w: W) -> Self {
Self::from_shared(Arc::new(RwLock::new(w)))
}
/// Create a new pipe from a shareable `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn from_shared(writer: Arc<RwLock<W>>) -> Self {
Self { writer }
}
/// Try to convert this `WritePipe<W>` back to the underlying `W` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `W` exist.
pub fn try_into_inner(mut self) -> Result<W, Self> {
match Arc::try_unwrap(self.writer) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(writer) => {
self.writer = writer;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<W> {
RwLock::write(&self.writer).unwrap()
}
}
impl WritePipe<io::Cursor<Vec<u8>>> {
/// Create a new writable virtual pipe backed by a `Vec<u8>` buffer.
pub fn new_in_memory() -> Self {
Self::new(io::Cursor::new(vec![]))
}
}
impl<W: Write + Any> WasiFile for WritePipe<W> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Write :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
Err(Error::badf())
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
let n = self.borrow().write_vectored(bufs)?;
Ok(n.try_into()?)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}

View File

@@ -0,0 +1,52 @@
use cap_rand::RngCore;
/// Implement `WasiRandom` using a deterministic cycle of bytes.
pub struct Deterministic {
cycle: std::iter::Cycle<std::vec::IntoIter<u8>>,
}
impl Deterministic {
pub fn new(bytes: Vec<u8>) -> Self {
Deterministic {
cycle: bytes.into_iter().cycle(),
}
}
}
impl RngCore for Deterministic {
fn next_u32(&mut self) -> u32 {
let b0 = self.cycle.next().expect("infinite sequence");
let b1 = self.cycle.next().expect("infinite sequence");
let b2 = self.cycle.next().expect("infinite sequence");
let b3 = self.cycle.next().expect("infinite sequence");
((b0 as u32) << 24) + ((b1 as u32) << 16) + ((b2 as u32) << 8) + (b3 as u32)
}
fn next_u64(&mut self) -> u64 {
let w0 = self.next_u32();
let w1 = self.next_u32();
((w0 as u64) << 32) + (w1 as u64)
}
fn fill_bytes(&mut self, buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = self.cycle.next().expect("infinite sequence");
}
}
fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), cap_rand::Error> {
self.fill_bytes(buf);
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deterministic() {
let mut det = Deterministic::new(vec![1, 2, 3, 4]);
let mut buf = vec![0; 1024];
det.try_fill_bytes(&mut buf).expect("get randomness");
for (ix, b) in buf.iter().enumerate() {
assert_eq!(*b, (ix % 4) as u8 + 1)
}
}
}

View File

@@ -1,199 +0,0 @@
use std::io::{IoSlice, Result, Write};
/// An adapter around a `Write` stream that guarantees that its output
/// is valid UTF-8 and contains no control characters. It does this by
/// replacing characters with inert control pictures and replacement
/// characters.
pub(crate) struct SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
inner: &'writer mut Writer,
}
impl<'writer, Writer> SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
/// Construct a new `SandboxedTTYWriter` with the given inner `Writer`.
pub(crate) fn new(inner: &'writer mut Writer) -> Self {
Self { inner }
}
/// Write a single character to the output.
pub(crate) fn write_char(&mut self, c: char) -> Result<()> {
self.inner.write(
match c {
'\u{0000}' => '␀',
'\u{0001}' => '␁',
'\u{0002}' => '␂',
'\u{0003}' => '␃',
'\u{0004}' => '␄',
'\u{0005}' => '␅',
'\u{0006}' => '␆',
'\u{0007}' => '␇',
'\u{0008}' => '␈',
'\u{0009}' => '\t',
'\u{000A}' => '\n',
'\u{000B}' => '␋',
'\u{000C}' => '␌',
'\u{000D}' => '\r',
'\u{000E}' => '␎',
'\u{000F}' => '␏',
'\u{0010}' => '␐',
'\u{0011}' => '␑',
'\u{0012}' => '␒',
'\u{0013}' => '␓',
'\u{0014}' => '␔',
'\u{0015}' => '␕',
'\u{0016}' => '␖',
'\u{0017}' => '␗',
'\u{0018}' => '␘',
'\u{0019}' => '␙',
'\u{001A}' => '␚',
'\u{001B}' => '␛',
'\u{001C}' => '␜',
'\u{001D}' => '␝',
'\u{001E}' => '␞',
'\u{001F}' => '␟',
'\u{007F}' => '␡',
x if x.is_control() => '<27>',
x => x,
}
.encode_utf8(&mut [0; 4]) // UTF-8 encoding of a `char` is at most 4 bytes.
.as_bytes(),
)?;
Ok(())
}
/// Write a string to the output.
pub(crate) fn write_str(&mut self, s: &str) -> Result<usize> {
let mut result = 0;
for c in s.chars() {
self.write_char(c)?;
// Note that we use the encoding length of the given char, rather than
// how many bytes we actually wrote, because our users don't know about
// what's really being written.
result += c.len_utf8();
}
Ok(result)
}
}
impl<'writer, Writer> Write for SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let mut input = buf;
let mut result = 0;
// Decode the string without heap-allocating it. See the example here
// for more details:
// https://doc.rust-lang.org/std/str/struct.Utf8Error.html#examples
loop {
match std::str::from_utf8(input) {
Ok(valid) => {
result += self.write_str(valid)?;
break;
}
Err(error) => {
let (valid, after_valid) = input.split_at(error.valid_up_to());
result += self.write_str(unsafe { std::str::from_utf8_unchecked(valid) })?;
self.write_char('<27>')?;
if let Some(invalid_sequence_length) = error.error_len() {
// An invalid sequence was encountered. Tell the application we've
// written those bytes (though actually, we replaced them with U+FFFD).
result += invalid_sequence_length;
// Set up `input` to resume writing after the end of the sequence.
input = &after_valid[invalid_sequence_length..];
} else {
// The end of the buffer was encountered unexpectedly. Tell the application
// we've written out the remainder of the buffer.
result += after_valid.len();
break;
}
}
}
}
return Ok(result);
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> Result<usize> {
// Terminal output is [not expected to be atomic], so just write all the
// individual buffers in sequence.
//
// [not expected to be atomic]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_08
let mut total_written = 0;
for buf in bufs {
let written = self.write(buf)?;
total_written += written;
// Stop at the first point where the OS writes less than we asked.
if written < buf.len() {
break;
}
}
Ok(total_written)
}
fn flush(&mut self) -> Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::SandboxedTTYWriter;
use std::io::{Result, Write};
#[test]
fn basic() -> Result<()> {
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write_str("a\0b\u{0080}")?;
safe.write_char('\u{0007}')?;
safe.write(&[0x80])?;
safe.write(&[0xed, 0xa0, 0x80, 0xff, 0xfe])?;
assert_eq!(
buffer,
"a\u{2400}b\u{FFFD}\u{2407}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes()
);
Ok(())
}
#[test]
fn how_many_replacements() -> Result<()> {
// See https://hsivonen.fi/broken-utf-8/ for background.
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0x80, 0x80, 0x80, 0x80])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF0, 0x80, 0x80, 0x41])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}A".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF0, 0x80, 0x80])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF4, 0x80, 0x80, 0xC0])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}".as_bytes());
Ok(())
}
}

View File

@@ -1,17 +1,87 @@
use crate::entry::EntryHandle;
pub use crate::wasi::types::{
Clockid, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, Subclockflags,
SubscriptionClock, Timestamp, Userdata,
};
#[derive(Debug, Copy, Clone)]
pub struct ClockEventData {
pub delay: u128, // delay is expressed in nanoseconds
pub userdata: Userdata,
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use cap_std::time::{Duration, Instant};
use std::cell::Ref;
pub mod subscription;
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
pub trait WasiSched {
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
fn sched_yield(&self) -> Result<(), Error>;
}
#[derive(Debug)]
pub struct FdEventData {
pub handle: EntryHandle,
pub r#type: Eventtype,
pub userdata: Userdata,
pub struct Userdata(u64);
impl From<u64> for Userdata {
fn from(u: u64) -> Userdata {
Userdata(u)
}
}
impl From<Userdata> for u64 {
fn from(u: Userdata) -> u64 {
u.0
}
}
pub struct Poll<'a> {
subs: Vec<(Subscription<'a>, Userdata)>,
}
impl<'a> Poll<'a> {
pub fn new() -> Self {
Self { subs: Vec::new() }
}
pub fn subscribe_monotonic_clock(
&mut self,
clock: &'a dyn WasiMonotonicClock,
deadline: Instant,
precision: Duration,
ud: Userdata,
) {
self.subs.push((
Subscription::MonotonicClock(MonotonicClockSubscription {
clock,
deadline,
precision,
}),
ud,
));
}
pub fn subscribe_read(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
self.subs
.push((Subscription::Read(RwSubscription::new(file)), ud));
}
pub fn subscribe_write(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
self.subs
.push((Subscription::Write(RwSubscription::new(file)), ud));
}
pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> {
self.subs
.into_iter()
.filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud)))
.collect()
}
pub fn is_empty(&self) -> bool {
self.subs.is_empty()
}
pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> {
let mut subs = self
.subs
.iter()
.filter_map(|(s, _ud)| match s {
Subscription::MonotonicClock(t) => Some(t),
_ => None,
})
.collect::<Vec<&MonotonicClockSubscription<'a>>>();
subs.sort_by(|a, b| a.deadline.cmp(&b.deadline));
subs.into_iter().next() // First element is earliest
}
pub fn rw_subscriptions(&'a self) -> impl Iterator<Item = &Subscription<'a>> {
self.subs.iter().filter_map(|(s, _ud)| match s {
Subscription::Read { .. } | Subscription::Write { .. } => Some(s),
_ => None,
})
}
}

View File

@@ -0,0 +1,79 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use bitflags::bitflags;
use cap_std::time::{Duration, Instant};
use std::cell::{Cell, Ref};
bitflags! {
pub struct RwEventFlags: u32 {
const HANGUP = 0b1;
}
}
pub struct RwSubscription<'a> {
pub file: Ref<'a, dyn WasiFile>,
status: Cell<Option<Result<(u64, RwEventFlags), Error>>>,
}
impl<'a> RwSubscription<'a> {
pub fn new(file: Ref<'a, dyn WasiFile>) -> Self {
Self {
file,
status: Cell::new(None),
}
}
pub fn complete(&self, size: u64, flags: RwEventFlags) {
self.status.set(Some(Ok((size, flags))))
}
pub fn error(&self, error: Error) {
self.status.set(Some(Err(error)))
}
pub fn result(self) -> Option<Result<(u64, RwEventFlags), Error>> {
self.status.into_inner()
}
}
pub struct MonotonicClockSubscription<'a> {
pub clock: &'a dyn WasiMonotonicClock,
pub deadline: Instant,
pub precision: Duration,
}
impl<'a> MonotonicClockSubscription<'a> {
pub fn now(&self) -> Instant {
self.clock.now(self.precision)
}
pub fn duration_until(&self) -> Option<Duration> {
self.deadline.checked_duration_since(self.now())
}
pub fn result(&self) -> Option<Result<(), Error>> {
if self.now().checked_duration_since(self.deadline).is_some() {
Some(Ok(()))
} else {
None
}
}
}
pub enum Subscription<'a> {
Read(RwSubscription<'a>),
Write(RwSubscription<'a>),
MonotonicClock(MonotonicClockSubscription<'a>),
}
pub enum SubscriptionResult {
Read(Result<(u64, RwEventFlags), Error>),
Write(Result<(u64, RwEventFlags), Error>),
MonotonicClock(Result<(), Error>),
}
impl SubscriptionResult {
pub fn from_subscription(s: Subscription) -> Option<SubscriptionResult> {
match s {
Subscription::Read(s) => s.result().map(SubscriptionResult::Read),
Subscription::Write(s) => s.result().map(SubscriptionResult::Write),
Subscription::MonotonicClock(s) => s.result().map(SubscriptionResult::MonotonicClock),
}
}
}

View File

@@ -1,2 +1,27 @@
pub mod wasi_snapshot_preview1;
pub mod wasi_unstable;
//! One goal of `wasi-common` is for multiple WASI snapshots to provide an
//! interface to the same underlying `crate::WasiCtx`. This provides us a path
//! to evolve WASI by allowing the same WASI Command to import functions from
//! different snapshots - e.g. the user could use Rust's `std` which imports
//! snapshot 1, but also depend directly on the `wasi` crate which imports
//! some future snapshot 2. Right now, this amounts to supporting snapshot 1
//! and "snapshot 0" aka wasi_unstable at once.
//!
//! The architectural rules for snapshots are:
//!
//! * Snapshots are arranged into modules under `crate::snapshots::`.
//!
//! * Each snapshot should invoke `wiggle::from_witx!` with `ctx:
//! crate::WasiCtx` in its module, and impl all of the required traits.
//!
//!* Snapshots can be implemented in terms of other snapshots. For example,
//! snapshot 0 is mostly implemented by calling the snapshot 1 implementation,
//! and converting its own types back and forth with the snapshot 1 types. In a
//! few cases, that is not feasible, so snapshot 0 carries its own
//! implementations in terms of the `WasiFile` and `WasiSched` traits.
//!
//! * Snapshots can be implemented in terms of the `Wasi*` traits given by
//! `WasiCtx`. No further downcasting via the `as_any` escape hatch is
//! permitted.
pub mod preview_0;
pub mod preview_1;

View File

@@ -0,0 +1,925 @@
use crate::file::{FileCaps, FileEntryExt, TableFileExt};
use crate::sched::{
subscription::{RwEventFlags, SubscriptionResult},
Poll,
};
use crate::snapshots::preview_1::types as snapshot1_types;
use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1;
use crate::{Error, ErrorExt, WasiCtx};
use cap_std::time::Duration;
use std::convert::{TryFrom, TryInto};
use std::io::{IoSlice, IoSliceMut};
use std::ops::Deref;
use tracing::debug;
use wiggle::GuestPtr;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
ctx: WasiCtx,
errors: { errno => Error },
});
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
Self::Success
}
}
impl types::GuestErrorConversion for WasiCtx {
fn into_errno(&self, e: wiggle::GuestError) -> types::Errno {
debug!("Guest error: {:?}", e);
let snapshot1_errno: snapshot1_types::Errno = e.into();
snapshot1_errno.into()
}
}
impl types::UserErrorConversion for WasiCtx {
fn errno_from_error(&self, e: Error) -> Result<types::Errno, wiggle::Trap> {
debug!("Error: {:?}", e);
e.try_into()
.map_err(|e| wiggle::Trap::String(format!("{:?}", e)))
}
}
impl TryFrom<Error> for types::Errno {
type Error = Error;
fn try_from(e: Error) -> Result<types::Errno, Error> {
let snapshot1_errno: snapshot1_types::Errno = e.try_into()?;
Ok(snapshot1_errno.into())
}
}
// Type conversions
// The vast majority of the types defined in `types` and `snapshot1_types` are identical. However,
// since they are defined in separate places for mechanical (wiggle) reasons, we need to manually
// define conversion functions between them.
// Below we have defined these functions as they are needed.
/// Fd is a newtype wrapper around u32. Unwrap and wrap it.
impl From<types::Fd> for snapshot1_types::Fd {
fn from(fd: types::Fd) -> snapshot1_types::Fd {
u32::from(fd).into()
}
}
/// Fd is a newtype wrapper around u32. Unwrap and wrap it.
impl From<snapshot1_types::Fd> for types::Fd {
fn from(fd: snapshot1_types::Fd) -> types::Fd {
u32::from(fd).into()
}
}
/// Trivial conversion between two c-style enums that have the exact same set of variants.
/// Could we do something unsafe and not list all these variants out? Probably, but doing
/// it this way doesn't bother me much. I copy-pasted the list of variants out of the
/// rendered rustdocs.
/// LLVM ought to compile these From impls into no-ops, inshallah
macro_rules! convert_enum {
($from:ty, $to:ty, $($var:ident),+) => {
impl From<$from> for $to {
fn from(e: $from) -> $to {
match e {
$( <$from>::$var => <$to>::$var, )+
}
}
}
}
}
convert_enum!(
snapshot1_types::Errno,
types::Errno,
Success,
TooBig,
Acces,
Addrinuse,
Addrnotavail,
Afnosupport,
Again,
Already,
Badf,
Badmsg,
Busy,
Canceled,
Child,
Connaborted,
Connrefused,
Connreset,
Deadlk,
Destaddrreq,
Dom,
Dquot,
Exist,
Fault,
Fbig,
Hostunreach,
Idrm,
Ilseq,
Inprogress,
Intr,
Inval,
Io,
Isconn,
Isdir,
Loop,
Mfile,
Mlink,
Msgsize,
Multihop,
Nametoolong,
Netdown,
Netreset,
Netunreach,
Nfile,
Nobufs,
Nodev,
Noent,
Noexec,
Nolck,
Nolink,
Nomem,
Nomsg,
Noprotoopt,
Nospc,
Nosys,
Notconn,
Notdir,
Notempty,
Notrecoverable,
Notsock,
Notsup,
Notty,
Nxio,
Overflow,
Ownerdead,
Perm,
Pipe,
Proto,
Protonosupport,
Prototype,
Range,
Rofs,
Spipe,
Srch,
Stale,
Timedout,
Txtbsy,
Xdev,
Notcapable
);
convert_enum!(
types::Clockid,
snapshot1_types::Clockid,
Realtime,
Monotonic,
ProcessCputimeId,
ThreadCputimeId
);
convert_enum!(
types::Advice,
snapshot1_types::Advice,
Normal,
Sequential,
Random,
Willneed,
Dontneed,
Noreuse
);
convert_enum!(
snapshot1_types::Filetype,
types::Filetype,
Directory,
BlockDevice,
CharacterDevice,
RegularFile,
SocketDgram,
SocketStream,
SymbolicLink,
Unknown
);
convert_enum!(types::Whence, snapshot1_types::Whence, Cur, End, Set);
/// Prestat isn't a c-style enum, its a union where the variant has a payload. Its the only one of
/// those we need to convert, so write it by hand.
impl From<snapshot1_types::Prestat> for types::Prestat {
fn from(p: snapshot1_types::Prestat) -> types::Prestat {
match p {
snapshot1_types::Prestat::Dir(d) => types::Prestat::Dir(d.into()),
}
}
}
/// Trivial conversion between two structs that have the exact same set of fields,
/// with recursive descent into the field types.
macro_rules! convert_struct {
($from:ty, $to:path, $($field:ident),+) => {
impl From<$from> for $to {
fn from(e: $from) -> $to {
$to {
$( $field: e.$field.into(), )+
}
}
}
}
}
convert_struct!(snapshot1_types::PrestatDir, types::PrestatDir, pr_name_len);
convert_struct!(
snapshot1_types::Fdstat,
types::Fdstat,
fs_filetype,
fs_rights_base,
fs_rights_inheriting,
fs_flags
);
/// Snapshot1 Filestat is incompatible with Snapshot0 Filestat - the nlink
/// field is u32 on this Filestat, and u64 on theirs. If you've got more than
/// 2^32 links I don't know what to tell you
impl From<snapshot1_types::Filestat> for types::Filestat {
fn from(f: snapshot1_types::Filestat) -> types::Filestat {
types::Filestat {
dev: f.dev.into(),
ino: f.ino.into(),
filetype: f.filetype.into(),
nlink: f.nlink.try_into().unwrap_or(u32::MAX),
size: f.size.into(),
atim: f.atim.into(),
mtim: f.mtim.into(),
ctim: f.ctim.into(),
}
}
}
/// Trivial conversion between two bitflags that have the exact same set of flags.
macro_rules! convert_flags {
($from:ty, $to:ty, $($flag:ident),+) => {
impl From<$from> for $to {
fn from(f: $from) -> $to {
let mut out = <$to>::empty();
$(
if f.contains(<$from>::$flag) {
out |= <$to>::$flag;
}
)+
out
}
}
}
}
/// Need to convert in both directions? This saves listing out the flags twice
macro_rules! convert_flags_bidirectional {
($from:ty, $to:ty, $($flag:tt)*) => {
convert_flags!($from, $to, $($flag)*);
convert_flags!($to, $from, $($flag)*);
}
}
convert_flags_bidirectional!(
snapshot1_types::Fdflags,
types::Fdflags,
APPEND,
DSYNC,
NONBLOCK,
RSYNC,
SYNC
);
convert_flags!(
types::Lookupflags,
snapshot1_types::Lookupflags,
SYMLINK_FOLLOW
);
convert_flags!(
types::Fstflags,
snapshot1_types::Fstflags,
ATIM,
ATIM_NOW,
MTIM,
MTIM_NOW
);
convert_flags!(
types::Oflags,
snapshot1_types::Oflags,
CREAT,
DIRECTORY,
EXCL,
TRUNC
);
convert_flags_bidirectional!(
types::Rights,
snapshot1_types::Rights,
FD_DATASYNC,
FD_READ,
FD_SEEK,
FD_FDSTAT_SET_FLAGS,
FD_SYNC,
FD_TELL,
FD_WRITE,
FD_ADVISE,
FD_ALLOCATE,
PATH_CREATE_DIRECTORY,
PATH_CREATE_FILE,
PATH_LINK_SOURCE,
PATH_LINK_TARGET,
PATH_OPEN,
FD_READDIR,
PATH_READLINK,
PATH_RENAME_SOURCE,
PATH_RENAME_TARGET,
PATH_FILESTAT_GET,
PATH_FILESTAT_SET_SIZE,
PATH_FILESTAT_SET_TIMES,
FD_FILESTAT_GET,
FD_FILESTAT_SET_SIZE,
FD_FILESTAT_SET_TIMES,
PATH_SYMLINK,
PATH_REMOVE_DIRECTORY,
PATH_UNLINK_FILE,
POLL_FD_READWRITE,
SOCK_SHUTDOWN
);
// This implementation, wherever possible, delegates directly to the Snapshot1 implementation,
// performing the no-op type conversions along the way.
impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
fn args_get<'b>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
) -> Result<(), Error> {
Snapshot1::args_get(self, argv, argv_buf)
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::args_sizes_get(self)
}
fn environ_get<'b>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
) -> Result<(), Error> {
Snapshot1::environ_get(self, environ, environ_buf)
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::environ_sizes_get(self)
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp, Error> {
Snapshot1::clock_res_get(self, id.into())
}
fn clock_time_get(
&self,
id: types::Clockid,
precision: types::Timestamp,
) -> Result<types::Timestamp, Error> {
Snapshot1::clock_time_get(self, id.into(), precision)
}
fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<(), Error> {
Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into())
}
fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
) -> Result<(), Error> {
Snapshot1::fd_allocate(self, fd.into(), offset, len)
}
fn fd_close(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_close(self, fd.into())
}
fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_datasync(self, fd.into())
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat, Error> {
Ok(Snapshot1::fd_fdstat_get(self, fd.into())?.into())
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into())
}
fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
) -> Result<(), Error> {
Snapshot1::fd_fdstat_set_rights(
self,
fd.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
)
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat, Error> {
Ok(Snapshot1::fd_filestat_get(self, fd.into())?.into())
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<(), Error> {
Snapshot1::fd_filestat_set_size(self, fd.into(), size)
}
fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<(), Error> {
Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into())
}
// NOTE on fd_read, fd_pread, fd_write, fd_pwrite implementations:
// Because the arguments to these function sit behind GuestPtrs, they are not values we
// can convert and pass to the corresponding function in Snapshot1.
// Instead, we have copied the implementation of these functions from the Snapshot1 code.
// The implementations are identical, but the `types::` in scope locally is different.
// The bodies of these functions is mostly about converting the GuestPtr and types::-based
// representation to a std::io::IoSlice(Mut) representation.
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::READ)?;
let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?)
})
.collect::<Result<_, Error>>()?;
let mut ioslices: Vec<IoSliceMut> = guest_slices
.iter_mut()
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored(&mut ioslices)?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_pread(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table
.get_file(u32::from(fd))?
.get_cap(FileCaps::READ | FileCaps::SEEK)?;
let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?)
})
.collect::<Result<_, Error>>()?;
let mut ioslices: Vec<IoSliceMut> = guest_slices
.iter_mut()
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored_at(&mut ioslices, offset)?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_write(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::WRITE)?;
let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Ciovec = iov_ptr.read()?;
Ok(iov.buf.as_array(iov.buf_len).as_slice()?)
})
.collect::<Result<_, Error>>()?;
let ioslices: Vec<IoSlice> = guest_slices
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored(&ioslices)?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_pwrite(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table
.get_file(u32::from(fd))?
.get_cap(FileCaps::WRITE | FileCaps::SEEK)?;
let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Ciovec = iov_ptr.read()?;
Ok(iov.buf.as_array(iov.buf_len).as_slice()?)
})
.collect::<Result<_, Error>>()?;
let ioslices: Vec<IoSlice> = guest_slices
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored_at(&ioslices, offset)?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat, Error> {
Ok(Snapshot1::fd_prestat_get(self, fd.into())?.into())
}
fn fd_prestat_dir_name(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path_max_len: types::Size,
) -> Result<(), Error> {
Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len)
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
Snapshot1::fd_renumber(self, from.into(), to.into())
}
fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize, Error> {
Snapshot1::fd_seek(self, fd.into(), offset, whence.into())
}
fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_sync(self, fd.into())
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize, Error> {
Snapshot1::fd_tell(self, fd.into())
}
fn fd_readdir(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size, Error> {
Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie)
}
fn path_create_directory(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
) -> Result<(), Error> {
Snapshot1::path_create_directory(self, dirfd.into(), path)
}
fn path_filestat_get(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
) -> Result<types::Filestat, Error> {
Ok(Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path)?.into())
}
fn path_filestat_set_times(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<(), Error> {
Snapshot1::path_filestat_set_times(
self,
dirfd.into(),
flags.into(),
path,
atim,
mtim,
fst_flags.into(),
)
}
fn path_link(
&self,
src_fd: types::Fd,
src_flags: types::Lookupflags,
src_path: &GuestPtr<'_, str>,
target_fd: types::Fd,
target_path: &GuestPtr<'_, str>,
) -> Result<(), Error> {
Snapshot1::path_link(
self,
src_fd.into(),
src_flags.into(),
src_path,
target_fd.into(),
target_path,
)
}
fn path_open(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
fdflags: types::Fdflags,
) -> Result<types::Fd, Error> {
Ok(Snapshot1::path_open(
self,
dirfd.into(),
dirflags.into(),
path,
oflags.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
fdflags.into(),
)?
.into())
}
fn path_readlink(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
buf_len: types::Size,
) -> Result<types::Size, Error> {
Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len)
}
fn path_remove_directory(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
) -> Result<(), Error> {
Snapshot1::path_remove_directory(self, dirfd.into(), path)
}
fn path_rename(
&self,
src_fd: types::Fd,
src_path: &GuestPtr<'_, str>,
dest_fd: types::Fd,
dest_path: &GuestPtr<'_, str>,
) -> Result<(), Error> {
Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path)
}
fn path_symlink(
&self,
src_path: &GuestPtr<'_, str>,
dirfd: types::Fd,
dest_path: &GuestPtr<'_, str>,
) -> Result<(), Error> {
Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path)
}
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<(), Error> {
Snapshot1::path_unlink_file(self, dirfd.into(), path)
}
// NOTE on poll_oneoff implementation:
// Like fd_write and friends, the arguments and return values are behind GuestPtrs,
// so they are not values we can convert and pass to the poll_oneoff in Snapshot1.
// Instead, we have copied the implementation of these functions from the Snapshot1 code.
// The implementations are identical, but the `types::` in scope locally is different.
// The bodies of these functions is mostly about converting the GuestPtr and types::-based
// representation to use the Poll abstraction.
fn poll_oneoff(
&self,
subs: &GuestPtr<types::Subscription>,
events: &GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size, Error> {
if nsubscriptions == 0 {
return Err(Error::invalid_argument().context("nsubscriptions must be nonzero"));
}
let table = self.table();
let mut poll = Poll::new();
let subs = subs.as_array(nsubscriptions);
for sub_elem in subs.iter() {
let sub_ptr = sub_elem?;
let sub = sub_ptr.read()?;
match sub.u {
types::SubscriptionU::Clock(clocksub) => match clocksub.id {
types::Clockid::Monotonic => {
let clock = self.clocks.monotonic.deref();
let precision = Duration::from_micros(clocksub.precision);
let duration = Duration::from_micros(clocksub.timeout);
let deadline = if clocksub
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
self.clocks
.creation_time
.checked_add(duration)
.ok_or_else(|| Error::overflow().context("deadline"))?
} else {
clock
.now(precision)
.checked_add(duration)
.ok_or_else(|| Error::overflow().context("deadline"))?
};
poll.subscribe_monotonic_clock(
clock,
deadline,
precision,
sub.userdata.into(),
)
}
_ => Err(Error::invalid_argument()
.context("timer subscriptions only support monotonic timer"))?,
},
types::SubscriptionU::FdRead(readsub) => {
let fd = readsub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_read(file, sub.userdata.into());
}
types::SubscriptionU::FdWrite(writesub) => {
let fd = writesub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_write(file, sub.userdata.into());
}
}
}
self.sched.poll_oneoff(&poll)?;
let results = poll.results();
let num_results = results.len();
assert!(
num_results <= nsubscriptions as usize,
"results exceeds subscriptions"
);
let events = events.as_array(
num_results
.try_into()
.expect("not greater than nsubscriptions"),
);
for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) {
let event_ptr = event_elem?;
let userdata: types::Userdata = userdata.into();
event_ptr.write(match result {
SubscriptionResult::Read(r) => {
let type_ = types::Eventtype::FdRead;
match r {
Ok((nbytes, flags)) => types::Event {
userdata,
error: types::Errno::Success,
type_,
fd_readwrite: types::EventFdReadwrite {
nbytes,
flags: types::Eventrwflags::from(&flags),
},
},
Err(e) => types::Event {
userdata,
error: e.try_into().expect("non-trapping"),
type_,
fd_readwrite: fd_readwrite_empty(),
},
}
}
SubscriptionResult::Write(r) => {
let type_ = types::Eventtype::FdWrite;
match r {
Ok((nbytes, flags)) => types::Event {
userdata,
error: types::Errno::Success,
type_,
fd_readwrite: types::EventFdReadwrite {
nbytes,
flags: types::Eventrwflags::from(&flags),
},
},
Err(e) => types::Event {
userdata,
error: e.try_into()?,
type_,
fd_readwrite: fd_readwrite_empty(),
},
}
}
SubscriptionResult::MonotonicClock(r) => {
let type_ = types::Eventtype::Clock;
types::Event {
userdata,
error: match r {
Ok(()) => types::Errno::Success,
Err(e) => e.try_into()?,
},
type_,
fd_readwrite: fd_readwrite_empty(),
}
}
})?;
}
Ok(num_results.try_into().expect("results fit into memory"))
}
fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
Snapshot1::proc_exit(self, status)
}
fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> {
Err(Error::trap("proc_raise unsupported"))
}
fn sched_yield(&self) -> Result<(), Error> {
Snapshot1::sched_yield(self)
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<(), Error> {
Snapshot1::random_get(self, buf, buf_len)
}
fn sock_recv(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags), Error> {
Err(Error::trap("sock_recv unsupported"))
}
fn sock_send(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size, Error> {
Err(Error::trap("sock_send unsupported"))
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
Err(Error::trap("sock_shutdown unsupported"))
}
}
impl From<&RwEventFlags> for types::Eventrwflags {
fn from(flags: &RwEventFlags) -> types::Eventrwflags {
let mut out = types::Eventrwflags::empty();
if flags.contains(RwEventFlags::HANGUP) {
out = out | types::Eventrwflags::FD_READWRITE_HANGUP;
}
out
}
}
fn fd_readwrite_empty() -> types::EventFdReadwrite {
types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,861 +0,0 @@
use crate::entry::{Entry, EntryHandle};
use crate::handle::{AsBytes, HandleRights};
use crate::sys::{clock, poll};
use crate::wasi::types;
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::{path, sched, Error, Result, WasiCtx};
use std::cmp::min;
use std::convert::TryInto;
use std::io::{self, SeekFrom};
use std::ops::Deref;
use tracing::{debug, trace};
use wiggle::{GuestPtr, GuestSliceMut};
impl<'a> WasiSnapshotPreview1 for WasiCtx {
fn args_get<'b>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.args.write_to_guest(argv_buf, argv)
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.args.number_elements, self.args.cumulative_size))
}
fn environ_get<'b>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.env.write_to_guest(environ_buf, environ)
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.env.number_elements, self.env.cumulative_size))
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp> {
let resolution = clock::res_get(id)?;
Ok(resolution)
}
fn clock_time_get(
&self,
id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
let time = clock::time_get(id)?;
Ok(time)
}
fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ADVISE);
let entry = self.get_entry(fd)?;
entry
.as_handle(required_rights)?
.advise(advice, offset, len)
}
fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ALLOCATE);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.allocate(offset, len)
}
fn fd_close(&self, fd: types::Fd) -> Result<()> {
if let Ok(fe) = self.get_entry(fd) {
// can't close preopened files
if fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
self.remove_entry(fd)?;
Ok(())
}
fn fd_datasync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_DATASYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.datasync()
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
let entry = self.get_entry(fd)?;
let file = entry.as_handle(HandleRights::empty())?;
let fs_flags = file.fdstat_get()?;
let rights = entry.get_rights();
let fdstat = types::Fdstat {
fs_filetype: entry.get_file_type(),
fs_rights_base: rights.base,
fs_rights_inheriting: rights.inheriting,
fs_flags,
};
Ok(fdstat)
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.fdstat_set_flags(flags)
}
fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
) -> Result<()> {
let rights = HandleRights::new(fs_rights_base, fs_rights_inheriting);
let entry = self.get_entry(fd)?;
if !entry.get_rights().contains(rights) {
return Err(Error::Notcapable);
}
entry.set_rights(rights);
Ok(())
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_GET);
let entry = self.get_entry(fd)?;
let host_filestat = entry.as_handle(required_rights)?.filestat_get()?;
Ok(host_filestat)
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.filestat_set_size(size)
}
fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
let entry = self.get_entry(fd)?;
entry
.as_handle(required_rights)?
.filestat_set_times(atim, mtim, fst_flags)
}
fn fd_pread(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices: Vec<GuestSliceMut<'_, u8>> = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice_mut()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nread = {
let mut buf = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect::<Vec<io::IoSliceMut<'_>>>();
entry
.as_handle(required_rights)?
.preadv(&mut buf, offset)?
.try_into()?
};
Ok(host_nread)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat> {
// TODO: should we validate any rights here?
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let path = path::from_host(po_path.as_os_str())?;
let prestat = types::PrestatDir {
pr_name_len: path.len().try_into()?,
};
Ok(types::Prestat::Dir(prestat))
}
fn fd_prestat_dir_name(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path_len: types::Size,
) -> Result<()> {
// TODO: should we validate any rights here?
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let host_path = path::from_host(po_path.as_os_str())?;
let host_path_len = host_path.len().try_into()?;
if host_path_len > path_len {
return Err(Error::Nametoolong);
}
trace!(" | path='{}'", host_path);
path.as_array(host_path_len)
.copy_from_slice(host_path.as_bytes())?;
Ok(())
}
fn fd_pwrite(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nwritten = {
let buf: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(required_rights)?
.pwritev(&buf, offset)?
.try_into()?
};
Ok(host_nwritten)
}
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice_mut()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_READ);
let entry = self.get_entry(fd)?;
let host_nread = {
let mut slices: Vec<io::IoSliceMut> = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect();
entry
.as_handle(required_rights)?
.read_vectored(&mut slices)?
.try_into()?
};
Ok(host_nread)
}
fn fd_readdir(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::FD_READDIR);
let entry = self.get_entry(fd)?;
let mut bufused = 0;
let mut buf = buf.clone();
for pair in entry.as_handle(required_rights)?.readdir(cookie)? {
let (dirent, name) = pair?;
let dirent_raw = dirent.as_bytes()?;
let dirent_len: types::Size = dirent_raw.len().try_into()?;
let name_raw = name.as_bytes();
let name_len = name_raw.len().try_into()?;
// Copy as many bytes of the dirent as we can, up to the end of the buffer.
let dirent_copy_len = min(dirent_len, buf_len - bufused);
buf.as_array(dirent_copy_len)
.copy_from_slice(&dirent_raw[..dirent_copy_len as usize])?;
// If the dirent struct wasn't copied entirely, return that we
// filled the buffer, which tells libc that we're not at EOF.
if dirent_copy_len < dirent_len {
return Ok(buf_len);
}
buf = buf.add(dirent_copy_len)?;
bufused += dirent_copy_len;
// Copy as many bytes of the name as we can, up to the end of the buffer.
let name_copy_len = min(name_len, buf_len - bufused);
buf.as_array(name_copy_len)
.copy_from_slice(&name_raw[..name_copy_len as usize])?;
// If the dirent struct wasn't copied entirely, return that we
// filled the buffer, which tells libc that we're not at EOF.
if name_copy_len < name_len {
return Ok(buf_len);
}
buf = buf.add(name_copy_len)?;
bufused += name_copy_len;
}
Ok(bufused)
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> {
if !self.contains_entry(from) {
return Err(Error::Badf);
}
// Don't allow renumbering over a pre-opened resource.
// TODO: Eventually, we do want to permit this, once libpreopen in
// userspace is capable of removing entries from its tables as well.
if let Ok(from_fe) = self.get_entry(from) {
if from_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
if let Ok(to_fe) = self.get_entry(to) {
if to_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
let fe = self.remove_entry(from)?;
self.insert_entry_at(to, fe);
Ok(())
}
fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize> {
let base = if offset == 0 && whence == types::Whence::Cur {
types::Rights::FD_TELL
} else {
types::Rights::FD_SEEK | types::Rights::FD_TELL
};
let required_rights = HandleRights::from_base(base);
let entry = self.get_entry(fd)?;
let pos = match whence {
types::Whence::Cur => SeekFrom::Current(offset),
types::Whence::End => SeekFrom::End(offset),
types::Whence::Set => SeekFrom::Start(offset as u64),
};
let host_newoffset = entry.as_handle(required_rights)?.seek(pos)?;
Ok(host_newoffset)
}
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_SYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.sync()
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
let required_rights = HandleRights::from_base(types::Rights::FD_TELL);
let entry = self.get_entry(fd)?;
let host_offset = entry
.as_handle(required_rights)?
.seek(SeekFrom::Current(0))?;
Ok(host_offset)
}
fn fd_write(&self, fd: types::Fd, ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_WRITE);
let entry = self.get_entry(fd)?;
let host_nwritten = {
let slices: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(required_rights)?
.write_vectored(&slices)?
.try_into()?
};
Ok(host_nwritten)
}
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(
types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY,
);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?;
dirfd.create_directory(&path)
}
fn path_filestat_get(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_GET);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(&entry, required_rights, flags, path.deref(), false)?;
let host_filestat =
dirfd.filestat_get_at(&path, flags.contains(types::Lookupflags::SYMLINK_FOLLOW))?;
Ok(host_filestat)
}
fn path_filestat_set_times(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(&entry, required_rights, flags, path.deref(), false)?;
dirfd.filestat_set_times_at(
&path,
atim,
mtim,
fst_flags,
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)?;
Ok(())
}
fn path_link(
&self,
old_fd: types::Fd,
old_flags: types::Lookupflags,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_SOURCE);
let old_entry = self.get_entry(old_fd)?;
let (old_dirfd, old_path) = {
// Borrow old_path for just this scope
let old_path = old_path.as_str()?;
path::get(
&old_entry,
required_rights,
types::Lookupflags::empty(),
old_path.deref(),
false,
)?
};
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_TARGET);
let new_entry = self.get_entry(new_fd)?;
let (new_dirfd, new_path) = {
// Borrow new_path for just this scope
let new_path = new_path.as_str()?;
path::get(
&new_entry,
required_rights,
types::Lookupflags::empty(),
new_path.deref(),
false,
)?
};
old_dirfd.link(
&old_path,
new_dirfd,
&new_path,
old_flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)
}
fn path_open(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
fdflags: types::Fdflags,
) -> Result<types::Fd> {
let needed_rights = path::open_rights(
&HandleRights::new(fs_rights_base, fs_rights_inheriting),
oflags,
fdflags,
);
trace!(" | needed_rights={}", needed_rights);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
needed_rights,
dirflags,
path.deref(),
oflags & types::Oflags::CREAT != types::Oflags::empty(),
)?
};
// which open mode do we need?
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
!= types::Rights::empty();
let write = fs_rights_base
& (types::Rights::FD_DATASYNC
| types::Rights::FD_WRITE
| types::Rights::FD_ALLOCATE
| types::Rights::FD_FILESTAT_SET_SIZE)
!= types::Rights::empty();
trace!(
" | calling path_open impl: read={}, write={}",
read,
write
);
let fd = dirfd.openat(&path, read, write, oflags, fdflags)?;
let entry = Entry::new(EntryHandle::from(fd));
// We need to manually deny the rights which are not explicitly requested
// because Entry::from will assign maximal consistent rights.
let mut rights = entry.get_rights();
rights.base &= fs_rights_base;
rights.inheriting &= fs_rights_inheriting;
entry.set_rights(rights);
let guest_fd = self.insert_entry(entry)?;
Ok(guest_fd)
}
fn path_readlink(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
buf_len: types::Size,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::PATH_READLINK);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
// borrow path for just this scope
let path = path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?
};
let mut slice = buf.as_array(buf_len).as_slice_mut()?;
let host_bufused = dirfd.readlink(&path, &mut *slice)?.try_into()?;
Ok(host_bufused)
}
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
true,
)?
};
dirfd.remove_directory(&path)
}
fn path_rename(
&self,
old_fd: types::Fd,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_SOURCE);
let entry = self.get_entry(old_fd)?;
let (old_dirfd, old_path) = {
let old_path = old_path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
old_path.deref(),
true,
)?
};
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_TARGET);
let entry = self.get_entry(new_fd)?;
let (new_dirfd, new_path) = {
let new_path = new_path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
new_path.deref(),
true,
)?
};
old_dirfd.rename(&old_path, new_dirfd, &new_path)
}
fn path_symlink(
&self,
old_path: &GuestPtr<'_, str>,
dirfd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_SYMLINK);
let entry = self.get_entry(dirfd)?;
let (new_fd, new_path) = {
let new_path = new_path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
new_path.deref(),
true,
)?
};
let old_path = old_path.as_str()?;
trace!(old_path = old_path.deref());
new_fd.symlink(&old_path, &new_path)
}
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_UNLINK_FILE);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?
};
dirfd.unlink_file(&path)?;
Ok(())
}
fn poll_oneoff(
&self,
in_: &GuestPtr<types::Subscription>,
out: &GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size> {
if u64::from(nsubscriptions) > types::Filesize::max_value() {
return Err(Error::Inval);
}
let mut subscriptions = Vec::new();
let subs = in_.as_array(nsubscriptions);
for sub_ptr in subs.iter() {
let sub_ptr = sub_ptr?;
let sub: types::Subscription = sub_ptr.read()?;
subscriptions.push(sub);
}
let events = self.poll_oneoff_impl(&subscriptions)?;
let nevents = events.len().try_into()?;
let out_events = out.as_array(nevents);
for (event, event_ptr) in events.into_iter().zip(out_events.iter()) {
let event_ptr = event_ptr?;
event_ptr.write(event)?;
}
trace!(nevents = nevents);
Ok(nevents)
}
fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
// Check that the status is within WASI's range.
if status < 126 {
wiggle::Trap::I32Exit(status as i32)
} else {
wiggle::Trap::String("exit with invalid exit status outside of [0..126)".to_owned())
}
}
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
Err(Error::Unsupported("proc_raise"))
}
fn sched_yield(&self) -> Result<()> {
std::thread::yield_now();
Ok(())
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<()> {
let mut slice = buf.as_array(buf_len).as_slice_mut()?;
getrandom::getrandom(&mut *slice)?;
Ok(())
}
fn sock_recv(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags)> {
Err(Error::Unsupported("sock_recv"))
}
fn sock_send(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size> {
Err(Error::Unsupported("sock_send"))
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
Err(Error::Unsupported("sock_shutdown"))
}
}
impl WasiCtx {
pub(crate) fn poll_oneoff_impl(
&self,
subscriptions: &[types::Subscription],
) -> Result<Vec<types::Event>> {
let mut events = Vec::new();
let mut timeout: Option<sched::ClockEventData> = None;
let mut fd_events = Vec::new();
// As mandated by the WASI spec:
// > If `nsubscriptions` is 0, returns `errno::inval`.
if subscriptions.is_empty() {
return Err(Error::Inval);
}
for subscription in subscriptions {
match &subscription.u {
types::SubscriptionU::Clock(clock) => {
let delay = clock::to_relative_ns_delay(&clock)?;
debug!(
clock = tracing::field::debug(&clock),
delay_ns = tracing::field::debug(delay),
"poll_oneoff"
);
let current = sched::ClockEventData {
delay,
userdata: subscription.userdata,
};
let timeout = timeout.get_or_insert(current);
if current.delay < timeout.delay {
*timeout = current;
}
}
types::SubscriptionU::FdRead(fd_read) => {
let fd = fd_read.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.try_into().expect("non-trapping error"),
type_: types::Eventtype::FdRead,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(required_rights)?,
r#type: types::Eventtype::FdRead,
userdata: subscription.userdata,
});
}
types::SubscriptionU::FdWrite(fd_write) => {
let fd = fd_write.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.try_into().expect("non-trapping error"),
type_: types::Eventtype::FdWrite,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(required_rights)?,
r#type: types::Eventtype::FdWrite,
userdata: subscription.userdata,
});
}
}
}
debug!(
events = tracing::field::debug(&events),
timeout = tracing::field::debug(timeout),
"poll_oneoff"
);
// The underlying implementation should successfully and immediately return
// if no events have been passed. Such situation may occur if all provided
// events have been filtered out as errors in the code above.
poll::oneoff(timeout, fd_events, &mut events)?;
Ok(events)
}
}

View File

@@ -1,939 +0,0 @@
use crate::wasi::{types as types_new, wasi_snapshot_preview1::WasiSnapshotPreview1};
use crate::{Error, WasiCtx};
use std::convert::{TryFrom, TryInto};
use types::*;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
ctx: WasiCtx,
errors: { errno => Error },
});
impl wiggle::GuestErrorType for Errno {
fn success() -> Self {
Self::Success
}
}
impl types::GuestErrorConversion for WasiCtx {
fn into_errno(&self, e: wiggle::GuestError) -> Errno {
tracing::debug!("Guest error: {:?}", e);
e.into()
}
}
impl types::UserErrorConversion for WasiCtx {
fn errno_from_error(&self, e: Error) -> Result<Errno, wiggle::Trap> {
tracing::debug!("Error: {:?}", e);
e.try_into()
}
}
impl TryFrom<Error> for Errno {
type Error = wiggle::Trap;
fn try_from(e: Error) -> Result<Errno, wiggle::Trap> {
Ok(types_new::Errno::try_from(e)?.into())
}
}
impl From<wiggle::GuestError> for Errno {
fn from(err: wiggle::GuestError) -> Self {
types_new::Errno::from(err).into()
}
}
impl wasi_unstable::WasiUnstable for WasiCtx {
fn args_get<'a>(
&self,
argv: &wiggle::GuestPtr<'a, wiggle::GuestPtr<'a, u8>>,
argv_buf: &wiggle::GuestPtr<'a, u8>,
) -> Result<(), Error> {
WasiSnapshotPreview1::args_get(self, argv, argv_buf)
}
fn args_sizes_get(&self) -> Result<(Size, Size), Error> {
WasiSnapshotPreview1::args_sizes_get(self)
}
fn environ_get<'a>(
&self,
environ: &wiggle::GuestPtr<'a, wiggle::GuestPtr<'a, u8>>,
environ_buf: &wiggle::GuestPtr<'a, u8>,
) -> Result<(), Error> {
WasiSnapshotPreview1::environ_get(self, environ, environ_buf)
}
fn environ_sizes_get(&self) -> Result<(Size, Size), Error> {
WasiSnapshotPreview1::environ_sizes_get(self)
}
fn clock_res_get(&self, id: Clockid) -> Result<Timestamp, Error> {
WasiSnapshotPreview1::clock_res_get(self, id.into())
}
fn clock_time_get(&self, id: Clockid, precision: Timestamp) -> Result<Timestamp, Error> {
WasiSnapshotPreview1::clock_time_get(self, id.into(), precision)
}
fn fd_advise(
&self,
fd: Fd,
offset: Filesize,
len: Filesize,
advice: Advice,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_advise(self, fd.into(), offset, len, advice.into())
}
fn fd_allocate(&self, fd: Fd, offset: Filesize, len: Filesize) -> Result<(), Error> {
WasiSnapshotPreview1::fd_allocate(self, fd.into(), offset, len)
}
fn fd_close(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_close(self, fd.into())
}
fn fd_datasync(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_datasync(self, fd.into())
}
fn fd_fdstat_get(&self, fd: Fd) -> Result<Fdstat, Error> {
WasiSnapshotPreview1::fd_fdstat_get(self, fd.into()).map(|s| s.into())
}
fn fd_fdstat_set_flags(&self, fd: Fd, flags: Fdflags) -> Result<(), Error> {
WasiSnapshotPreview1::fd_fdstat_set_flags(self, fd.into(), flags.into())
}
fn fd_fdstat_set_rights(
&self,
fd: Fd,
fs_rights_base: Rights,
fs_rights_inheriting: Rights,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_fdstat_set_rights(
self,
fd.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
)
}
fn fd_filestat_get(&self, fd: Fd) -> Result<Filestat, Error> {
WasiSnapshotPreview1::fd_filestat_get(self, fd.into()).and_then(|e| e.try_into())
}
fn fd_filestat_set_size(&self, fd: Fd, size: Filesize) -> Result<(), Error> {
WasiSnapshotPreview1::fd_filestat_set_size(self, fd.into(), size)
}
fn fd_filestat_set_times(
&self,
fd: Fd,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into())
}
fn fd_pread<'a>(&self, fd: Fd, iovs: &IovecArray<'a>, offset: Filesize) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_pread(self, fd.into(), &cvt_iovec(iovs), offset)
}
fn fd_prestat_get(&self, fd: Fd) -> Result<Prestat, Error> {
WasiSnapshotPreview1::fd_prestat_get(self, fd.into()).map(|e| e.into())
}
fn fd_prestat_dir_name<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, u8>,
path_len: Size,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_prestat_dir_name(self, fd.into(), path, path_len)
}
fn fd_pwrite<'a>(
&self,
fd: Fd,
iovs: &CiovecArray<'a>,
offset: Filesize,
) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_pwrite(self, fd.into(), &cvt_ciovec(iovs), offset)
}
fn fd_read<'a>(&self, fd: Fd, iovs: &IovecArray<'a>) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_read(self, fd.into(), &cvt_iovec(iovs))
}
fn fd_readdir<'a>(
&self,
fd: Fd,
buf: &wiggle::GuestPtr<'a, u8>,
buf_len: Size,
cookie: Dircookie,
) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_readdir(self, fd.into(), buf, buf_len, cookie)
}
fn fd_renumber(&self, from: Fd, to: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_renumber(self, from.into(), to.into())
}
fn fd_seek(&self, fd: Fd, offset: Filedelta, whence: Whence) -> Result<Filesize, Error> {
WasiSnapshotPreview1::fd_seek(self, fd.into(), offset, whence.into())
}
fn fd_sync(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_sync(self, fd.into())
}
fn fd_tell(&self, fd: Fd) -> Result<Filesize, Error> {
WasiSnapshotPreview1::fd_tell(self, fd.into())
}
fn fd_write<'a>(&self, fd: Fd, iovs: &CiovecArray<'a>) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_write(self, fd.into(), &cvt_ciovec(iovs))
}
fn path_create_directory<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_create_directory(self, fd.into(), path)
}
fn path_filestat_get<'a>(
&self,
fd: Fd,
flags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<Filestat, Error> {
WasiSnapshotPreview1::path_filestat_get(self, fd.into(), flags.into(), path)
.and_then(|e| e.try_into())
}
fn path_filestat_set_times<'a>(
&self,
fd: Fd,
flags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_filestat_set_times(
self,
fd.into(),
flags.into(),
path,
atim,
mtim,
fst_flags.into(),
)
}
fn path_link<'a>(
&self,
old_fd: Fd,
old_flags: Lookupflags,
old_path: &wiggle::GuestPtr<'a, str>,
new_fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_link(
self,
old_fd.into(),
old_flags.into(),
old_path,
new_fd.into(),
new_path,
)
}
fn path_open<'a>(
&self,
fd: Fd,
dirflags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
oflags: Oflags,
fs_rights_base: Rights,
fs_rights_inheriting: Rights,
fdflags: Fdflags,
) -> Result<Fd, Error> {
WasiSnapshotPreview1::path_open(
self,
fd.into(),
dirflags.into(),
path,
oflags.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
fdflags.into(),
)
.map(|e| e.into())
}
fn path_readlink<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
buf: &wiggle::GuestPtr<'a, u8>,
buf_len: Size,
) -> Result<Size, Error> {
WasiSnapshotPreview1::path_readlink(self, fd.into(), path, buf, buf_len)
}
fn path_remove_directory<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_remove_directory(self, fd.into(), path)
}
fn path_rename<'a>(
&self,
fd: Fd,
old_path: &wiggle::GuestPtr<'a, str>,
new_fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_rename(self, fd.into(), old_path, new_fd.into(), new_path)
}
fn path_symlink<'a>(
&self,
old_path: &wiggle::GuestPtr<'a, str>,
fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_symlink(self, old_path, fd.into(), new_path)
}
fn path_unlink_file<'a>(&self, fd: Fd, path: &wiggle::GuestPtr<'a, str>) -> Result<(), Error> {
WasiSnapshotPreview1::path_unlink_file(self, fd.into(), path)
}
fn poll_oneoff<'a>(
&self,
in_: &wiggle::GuestPtr<'a, Subscription>,
out: &wiggle::GuestPtr<'a, Event>,
nsubscriptions: Size,
) -> Result<Size, Error> {
if u64::from(nsubscriptions) > types::Filesize::max_value() {
return Err(Error::Inval);
}
let mut subscriptions = Vec::new();
let subs = in_.as_array(nsubscriptions);
for sub_ptr in subs.iter() {
let sub_ptr = sub_ptr?;
let sub: types::Subscription = sub_ptr.read()?;
subscriptions.push(sub.into());
}
let events = self.poll_oneoff_impl(&subscriptions)?;
let nevents = events.len().try_into()?;
let out_events = out.as_array(nevents);
for (event, event_ptr) in events.into_iter().zip(out_events.iter()) {
let event_ptr = event_ptr?;
event_ptr.write(event.into())?;
}
Ok(nevents)
}
fn proc_exit(&self, rval: Exitcode) -> wiggle::Trap {
WasiSnapshotPreview1::proc_exit(self, rval)
}
fn proc_raise(&self, sig: Signal) -> Result<(), Error> {
WasiSnapshotPreview1::proc_raise(self, sig.into())
}
fn sched_yield(&self) -> Result<(), Error> {
WasiSnapshotPreview1::sched_yield(self)
}
fn random_get<'a>(&self, buf: &wiggle::GuestPtr<'a, u8>, buf_len: Size) -> Result<(), Error> {
WasiSnapshotPreview1::random_get(self, buf, buf_len)
}
fn sock_recv<'a>(
&self,
fd: Fd,
ri_data: &IovecArray<'a>,
ri_flags: Riflags,
) -> Result<(Size, Roflags), Error> {
WasiSnapshotPreview1::sock_recv(self, fd.into(), &cvt_iovec(ri_data), ri_flags.into())
.map(|(s, f)| (s, f.into()))
}
fn sock_send<'a>(
&self,
fd: Fd,
si_data: &CiovecArray<'a>,
si_flags: Siflags,
) -> Result<Size, Error> {
WasiSnapshotPreview1::sock_send(self, fd.into(), &cvt_ciovec(si_data), si_flags.into())
}
fn sock_shutdown(&self, fd: Fd, how: Sdflags) -> Result<(), Error> {
WasiSnapshotPreview1::sock_shutdown(self, fd.into(), how.into())
}
}
impl From<Clockid> for types_new::Clockid {
fn from(id: Clockid) -> types_new::Clockid {
match id {
Clockid::Realtime => types_new::Clockid::Realtime,
Clockid::Monotonic => types_new::Clockid::Monotonic,
Clockid::ProcessCputimeId => types_new::Clockid::ProcessCputimeId,
Clockid::ThreadCputimeId => types_new::Clockid::ThreadCputimeId,
}
}
}
impl From<Fd> for types_new::Fd {
fn from(fd: Fd) -> types_new::Fd {
types_new::Fd::from(u32::from(fd))
}
}
impl From<types_new::Fd> for Fd {
fn from(fd: types_new::Fd) -> Fd {
Fd::from(u32::from(fd))
}
}
impl From<Advice> for types_new::Advice {
fn from(e: Advice) -> types_new::Advice {
match e {
Advice::Normal => types_new::Advice::Normal,
Advice::Sequential => types_new::Advice::Sequential,
Advice::Random => types_new::Advice::Random,
Advice::Willneed => types_new::Advice::Willneed,
Advice::Dontneed => types_new::Advice::Dontneed,
Advice::Noreuse => types_new::Advice::Noreuse,
}
}
}
impl From<types_new::Fdstat> for Fdstat {
fn from(e: types_new::Fdstat) -> Fdstat {
Fdstat {
fs_filetype: e.fs_filetype.into(),
fs_flags: e.fs_flags.into(),
fs_rights_base: e.fs_rights_base.into(),
fs_rights_inheriting: e.fs_rights_inheriting.into(),
}
}
}
fn assert_rights_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u64::from(Rights::$id), u64::from(types_new::Rights::$id));
)*});
}
assert_same! {
FD_DATASYNC
FD_READ
FD_SEEK
FD_FDSTAT_SET_FLAGS
FD_SYNC
FD_TELL
FD_WRITE
FD_ADVISE
FD_ALLOCATE
PATH_CREATE_DIRECTORY
PATH_CREATE_FILE
PATH_LINK_SOURCE
PATH_LINK_TARGET
PATH_OPEN
FD_READDIR
PATH_READLINK
PATH_RENAME_SOURCE
PATH_RENAME_TARGET
PATH_FILESTAT_GET
PATH_FILESTAT_SET_TIMES
PATH_FILESTAT_SET_SIZE
FD_FILESTAT_GET
FD_FILESTAT_SET_SIZE
FD_FILESTAT_SET_TIMES
PATH_SYMLINK
PATH_REMOVE_DIRECTORY
PATH_UNLINK_FILE
POLL_FD_READWRITE
SOCK_SHUTDOWN
}
}
impl From<Rights> for types_new::Rights {
fn from(e: Rights) -> types_new::Rights {
assert_rights_same();
u64::from(e).try_into().unwrap()
}
}
impl From<types_new::Rights> for Rights {
fn from(e: types_new::Rights) -> Rights {
assert_rights_same();
u64::from(e).try_into().unwrap()
}
}
impl From<Filetype> for types_new::Filetype {
fn from(e: Filetype) -> types_new::Filetype {
match e {
Filetype::Unknown => types_new::Filetype::Unknown,
Filetype::BlockDevice => types_new::Filetype::BlockDevice,
Filetype::CharacterDevice => types_new::Filetype::CharacterDevice,
Filetype::Directory => types_new::Filetype::Directory,
Filetype::RegularFile => types_new::Filetype::RegularFile,
Filetype::SocketDgram => types_new::Filetype::SocketDgram,
Filetype::SocketStream => types_new::Filetype::SocketStream,
Filetype::SymbolicLink => types_new::Filetype::SymbolicLink,
}
}
}
impl From<types_new::Filetype> for Filetype {
fn from(e: types_new::Filetype) -> Filetype {
match e {
types_new::Filetype::Unknown => Filetype::Unknown,
types_new::Filetype::BlockDevice => Filetype::BlockDevice,
types_new::Filetype::CharacterDevice => Filetype::CharacterDevice,
types_new::Filetype::Directory => Filetype::Directory,
types_new::Filetype::RegularFile => Filetype::RegularFile,
types_new::Filetype::SocketDgram => Filetype::SocketDgram,
types_new::Filetype::SocketStream => Filetype::SocketStream,
types_new::Filetype::SymbolicLink => Filetype::SymbolicLink,
}
}
}
fn assert_fdflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Fdflags::$id), u16::from(types_new::Fdflags::$id));
)*});
}
assert_same! {
APPEND
DSYNC
NONBLOCK
RSYNC
SYNC
}
}
impl From<Fdflags> for types_new::Fdflags {
fn from(e: Fdflags) -> types_new::Fdflags {
assert_fdflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Fdflags> for Fdflags {
fn from(e: types_new::Fdflags) -> Fdflags {
assert_fdflags_same();
u16::from(e).try_into().unwrap()
}
}
impl TryFrom<types_new::Filestat> for Filestat {
type Error = Error;
fn try_from(e: types_new::Filestat) -> Result<Filestat, Error> {
Ok(Filestat {
dev: e.dev,
ino: e.ino,
filetype: e.filetype.into(),
// wasi_snapshot_preview1 has a 64-bit nlink field but we have a
// 32-bit field, so we need to perform a fallible conversion.
nlink: e.nlink.try_into()?,
size: e.size,
atim: e.atim,
mtim: e.mtim,
ctim: e.ctim,
})
}
}
fn assert_fstflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Fstflags::$id), u16::from(types_new::Fstflags::$id));
)*});
}
assert_same! {
ATIM
ATIM_NOW
MTIM
MTIM_NOW
}
}
impl From<Fstflags> for types_new::Fstflags {
fn from(e: Fstflags) -> types_new::Fstflags {
assert_fstflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Fstflags> for Fstflags {
fn from(e: types_new::Fstflags) -> Fstflags {
assert_fstflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Prestat> for Prestat {
fn from(e: types_new::Prestat) -> Prestat {
match e {
types_new::Prestat::Dir(d) => Prestat::Dir(d.into()),
}
}
}
impl From<types_new::PrestatDir> for PrestatDir {
fn from(e: types_new::PrestatDir) -> PrestatDir {
PrestatDir {
pr_name_len: e.pr_name_len,
}
}
}
impl From<Whence> for types_new::Whence {
fn from(e: Whence) -> types_new::Whence {
match e {
Whence::Set => types_new::Whence::Set,
Whence::Cur => types_new::Whence::Cur,
Whence::End => types_new::Whence::End,
}
}
}
fn assert_lookupflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u32::from(Lookupflags::$id), u32::from(types_new::Lookupflags::$id));
)*});
}
assert_same! {
SYMLINK_FOLLOW
}
}
impl From<Lookupflags> for types_new::Lookupflags {
fn from(e: Lookupflags) -> types_new::Lookupflags {
assert_lookupflags_same();
u32::from(e).try_into().unwrap()
}
}
fn assert_oflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Oflags::$id), u16::from(types_new::Oflags::$id));
)*});
}
assert_same! {
CREAT
DIRECTORY
EXCL
TRUNC
}
}
impl From<Oflags> for types_new::Oflags {
fn from(e: Oflags) -> types_new::Oflags {
assert_oflags_same();
u16::from(e).try_into().unwrap()
}
}
fn assert_sdflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u8::from(Sdflags::$id), u8::from(types_new::Sdflags::$id));
)*});
}
assert_same! {
RD WR
}
}
impl From<Sdflags> for types_new::Sdflags {
fn from(e: Sdflags) -> types_new::Sdflags {
assert_sdflags_same();
u8::from(e).try_into().unwrap()
}
}
impl From<Signal> for types_new::Signal {
fn from(e: Signal) -> types_new::Signal {
match e {
Signal::None => types_new::Signal::None,
Signal::Hup => types_new::Signal::Hup,
Signal::Int => types_new::Signal::Int,
Signal::Quit => types_new::Signal::Quit,
Signal::Ill => types_new::Signal::Ill,
Signal::Trap => types_new::Signal::Trap,
Signal::Abrt => types_new::Signal::Abrt,
Signal::Bus => types_new::Signal::Bus,
Signal::Fpe => types_new::Signal::Fpe,
Signal::Kill => types_new::Signal::Kill,
Signal::Usr1 => types_new::Signal::Usr1,
Signal::Segv => types_new::Signal::Segv,
Signal::Usr2 => types_new::Signal::Usr2,
Signal::Pipe => types_new::Signal::Pipe,
Signal::Alrm => types_new::Signal::Alrm,
Signal::Term => types_new::Signal::Term,
Signal::Chld => types_new::Signal::Chld,
Signal::Cont => types_new::Signal::Cont,
Signal::Stop => types_new::Signal::Stop,
Signal::Tstp => types_new::Signal::Tstp,
Signal::Ttin => types_new::Signal::Ttin,
Signal::Ttou => types_new::Signal::Ttou,
Signal::Urg => types_new::Signal::Urg,
Signal::Xcpu => types_new::Signal::Xcpu,
Signal::Xfsz => types_new::Signal::Xfsz,
Signal::Vtalrm => types_new::Signal::Vtalrm,
Signal::Prof => types_new::Signal::Prof,
Signal::Winch => types_new::Signal::Winch,
Signal::Poll => types_new::Signal::Poll,
Signal::Pwr => types_new::Signal::Pwr,
Signal::Sys => types_new::Signal::Sys,
}
}
}
// For `wasi_unstable` and `wasi_snapshot_preview1` the memory layout of these
// two types was manually verified. It should be fine to effectively cast
// between the two types and get the same behavior.
fn cvt_iovec<'a>(e: &IovecArray<'a>) -> types_new::IovecArray<'a> {
wiggle::GuestPtr::new(e.mem(), (e.offset_base(), e.len()))
}
fn cvt_ciovec<'a>(e: &CiovecArray<'a>) -> types_new::CiovecArray<'a> {
wiggle::GuestPtr::new(e.mem(), (e.offset_base(), e.len()))
}
fn assert_riflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Riflags::$id), u16::from(types_new::Riflags::$id));
)*});
}
assert_same! {
RECV_PEEK
RECV_WAITALL
}
}
impl From<Riflags> for types_new::Riflags {
fn from(e: Riflags) -> types_new::Riflags {
assert_riflags_same();
u16::from(e).try_into().unwrap()
}
}
fn assert_roflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Roflags::$id), u16::from(types_new::Roflags::$id));
)*});
}
assert_same! {
RECV_DATA_TRUNCATED
}
}
impl From<types_new::Roflags> for Roflags {
fn from(e: types_new::Roflags) -> Roflags {
assert_roflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<Subscription> for types_new::Subscription {
fn from(e: Subscription) -> types_new::Subscription {
types_new::Subscription {
userdata: e.userdata,
u: e.u.into(),
}
}
}
impl From<SubscriptionU> for types_new::SubscriptionU {
fn from(e: SubscriptionU) -> types_new::SubscriptionU {
match e {
SubscriptionU::Clock(c) => {
types_new::SubscriptionU::Clock(types_new::SubscriptionClock {
id: c.id.into(),
timeout: c.timeout,
precision: c.precision,
flags: c.flags.into(),
})
}
SubscriptionU::FdRead(c) => {
types_new::SubscriptionU::FdRead(types_new::SubscriptionFdReadwrite {
file_descriptor: c.file_descriptor.into(),
})
}
SubscriptionU::FdWrite(c) => {
types_new::SubscriptionU::FdWrite(types_new::SubscriptionFdReadwrite {
file_descriptor: c.file_descriptor.into(),
})
}
}
}
}
impl From<Subclockflags> for types_new::Subclockflags {
fn from(e: Subclockflags) -> types_new::Subclockflags {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Subclockflags::$id), u16::from(types_new::Subclockflags::$id));
)*});
}
assert_same! {
SUBSCRIPTION_CLOCK_ABSTIME
}
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Event> for Event {
fn from(e: types_new::Event) -> Event {
Event {
userdata: e.userdata,
error: e.error.into(),
type_: e.type_.into(),
fd_readwrite: e.fd_readwrite.into(),
}
}
}
impl From<types_new::Errno> for Errno {
fn from(e: types_new::Errno) -> Errno {
match e {
types_new::Errno::Success => Errno::Success,
types_new::Errno::TooBig => Errno::TooBig,
types_new::Errno::Acces => Errno::Acces,
types_new::Errno::Addrinuse => Errno::Addrinuse,
types_new::Errno::Addrnotavail => Errno::Addrnotavail,
types_new::Errno::Afnosupport => Errno::Afnosupport,
types_new::Errno::Again => Errno::Again,
types_new::Errno::Already => Errno::Already,
types_new::Errno::Badf => Errno::Badf,
types_new::Errno::Badmsg => Errno::Badmsg,
types_new::Errno::Busy => Errno::Busy,
types_new::Errno::Canceled => Errno::Canceled,
types_new::Errno::Child => Errno::Child,
types_new::Errno::Connaborted => Errno::Connaborted,
types_new::Errno::Connrefused => Errno::Connrefused,
types_new::Errno::Connreset => Errno::Connreset,
types_new::Errno::Deadlk => Errno::Deadlk,
types_new::Errno::Destaddrreq => Errno::Destaddrreq,
types_new::Errno::Dom => Errno::Dom,
types_new::Errno::Dquot => Errno::Dquot,
types_new::Errno::Exist => Errno::Exist,
types_new::Errno::Fault => Errno::Fault,
types_new::Errno::Fbig => Errno::Fbig,
types_new::Errno::Hostunreach => Errno::Hostunreach,
types_new::Errno::Idrm => Errno::Idrm,
types_new::Errno::Ilseq => Errno::Ilseq,
types_new::Errno::Inprogress => Errno::Inprogress,
types_new::Errno::Intr => Errno::Intr,
types_new::Errno::Inval => Errno::Inval,
types_new::Errno::Io => Errno::Io,
types_new::Errno::Isconn => Errno::Isconn,
types_new::Errno::Isdir => Errno::Isdir,
types_new::Errno::Loop => Errno::Loop,
types_new::Errno::Mfile => Errno::Mfile,
types_new::Errno::Mlink => Errno::Mlink,
types_new::Errno::Msgsize => Errno::Msgsize,
types_new::Errno::Multihop => Errno::Multihop,
types_new::Errno::Nametoolong => Errno::Nametoolong,
types_new::Errno::Netdown => Errno::Netdown,
types_new::Errno::Netreset => Errno::Netreset,
types_new::Errno::Netunreach => Errno::Netunreach,
types_new::Errno::Nfile => Errno::Nfile,
types_new::Errno::Nobufs => Errno::Nobufs,
types_new::Errno::Nodev => Errno::Nodev,
types_new::Errno::Noent => Errno::Noent,
types_new::Errno::Noexec => Errno::Noexec,
types_new::Errno::Nolck => Errno::Nolck,
types_new::Errno::Nolink => Errno::Nolink,
types_new::Errno::Nomem => Errno::Nomem,
types_new::Errno::Nomsg => Errno::Nomsg,
types_new::Errno::Noprotoopt => Errno::Noprotoopt,
types_new::Errno::Nospc => Errno::Nospc,
types_new::Errno::Nosys => Errno::Nosys,
types_new::Errno::Notconn => Errno::Notconn,
types_new::Errno::Notdir => Errno::Notdir,
types_new::Errno::Notempty => Errno::Notempty,
types_new::Errno::Notrecoverable => Errno::Notrecoverable,
types_new::Errno::Notsock => Errno::Notsock,
types_new::Errno::Notsup => Errno::Notsup,
types_new::Errno::Notty => Errno::Notty,
types_new::Errno::Nxio => Errno::Nxio,
types_new::Errno::Overflow => Errno::Overflow,
types_new::Errno::Ownerdead => Errno::Ownerdead,
types_new::Errno::Perm => Errno::Perm,
types_new::Errno::Pipe => Errno::Pipe,
types_new::Errno::Proto => Errno::Proto,
types_new::Errno::Protonosupport => Errno::Protonosupport,
types_new::Errno::Prototype => Errno::Prototype,
types_new::Errno::Range => Errno::Range,
types_new::Errno::Rofs => Errno::Rofs,
types_new::Errno::Spipe => Errno::Spipe,
types_new::Errno::Srch => Errno::Srch,
types_new::Errno::Stale => Errno::Stale,
types_new::Errno::Timedout => Errno::Timedout,
types_new::Errno::Txtbsy => Errno::Txtbsy,
types_new::Errno::Xdev => Errno::Xdev,
types_new::Errno::Notcapable => Errno::Notcapable,
}
}
}
impl From<types_new::Eventtype> for Eventtype {
fn from(e: types_new::Eventtype) -> Eventtype {
match e {
types_new::Eventtype::Clock => Eventtype::Clock,
types_new::Eventtype::FdRead => Eventtype::FdRead,
types_new::Eventtype::FdWrite => Eventtype::FdWrite,
}
}
}
impl From<types_new::EventFdReadwrite> for EventFdReadwrite {
fn from(e: types_new::EventFdReadwrite) -> EventFdReadwrite {
EventFdReadwrite {
nbytes: e.nbytes,
flags: e.flags.into(),
}
}
}
impl From<types_new::Eventrwflags> for Eventrwflags {
fn from(e: types_new::Eventrwflags) -> Eventrwflags {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Eventrwflags::$id), u16::from(types_new::Eventrwflags::$id));
)*});
}
assert_same! {
FD_READWRITE_HANGUP
}
u16::from(e).try_into().unwrap()
}
}

View File

@@ -1,121 +1,48 @@
use crate::Error;
use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::{CString, OsString};
use crate::{Error, ErrorExt};
use wiggle::GuestPtr;
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum PendingString {
Bytes(Vec<u8>),
OsString(OsString),
}
impl From<Vec<u8>> for PendingString {
fn from(bytes: Vec<u8>) -> Self {
Self::Bytes(bytes)
}
}
impl From<OsString> for PendingString {
fn from(s: OsString) -> Self {
Self::OsString(s)
}
}
impl PendingString {
pub fn into_string(self) -> Result<String, StringArrayError> {
let res = match self {
Self::Bytes(v) => String::from_utf8(v)?,
#[cfg(unix)]
Self::OsString(s) => {
use std::os::unix::ffi::OsStringExt;
String::from_utf8(s.into_vec())?
}
#[cfg(windows)]
Self::OsString(s) => {
use std::os::windows::ffi::OsStrExt;
let bytes: Vec<u16> = s.encode_wide().collect();
String::from_utf16(&bytes)?
}
};
Ok(res)
}
pub struct StringArray {
elems: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum StringArrayError {
/// Provided sequence of bytes contained an unexpected NUL byte.
#[error("provided sequence contained an unexpected NUL byte")]
Nul(#[from] std::ffi::NulError),
/// Too many elements: must fit into u32
#[error("too many elements")]
NumElements,
/// Element size: must fit into u32
#[error("element too big")]
ElemSize,
/// Cumulative element size: must fit into u32
#[error("cumulative element size too big")]
CumElemSize,
/// Provided sequence of bytes was not a valid UTF-8.
#[error("provided sequence is not valid UTF-8: {0}")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
/// Provided sequence of bytes was not a valid UTF-16.
///
/// This error is expected to only occur on Windows hosts.
#[error("provided sequence is not valid UTF-16: {0}")]
InvalidUtf16(#[from] std::string::FromUtf16Error),
#[error("Number of elements exceeds 2^32")]
NumberElements,
#[error("Element size exceeds 2^32")]
ElementSize,
#[error("Cumulative size exceeds 2^32")]
CumulativeSize,
}
pub struct StringArray {
elems: Vec<CString>,
pub number_elements: u32,
pub cumulative_size: u32,
}
impl StringArray {
pub fn from_pending_vec(elems: Vec<PendingString>) -> Result<Self, StringArrayError> {
let elems = elems
.into_iter()
.map(|arg| arg.into_string())
.collect::<Result<Vec<String>, StringArrayError>>()?;
Self::from_strings(elems)
pub fn new() -> Self {
StringArray { elems: Vec::new() }
}
pub fn from_pending_map(
elems: HashMap<PendingString, PendingString>,
) -> Result<Self, StringArrayError> {
let mut pairs = Vec::new();
for (k, v) in elems.into_iter() {
let mut pair = k.into_string()?;
pair.push('=');
pair.push_str(&v.into_string()?);
pairs.push(pair);
pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> {
if self.elems.len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::NumberElements);
}
Self::from_strings(pairs)
if elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::ElementSize);
}
pub fn from_strings(elems: Vec<String>) -> Result<Self, StringArrayError> {
let elems = elems
.into_iter()
.map(|s| CString::new(s))
.collect::<Result<Vec<CString>, _>>()?;
let number_elements = elems
.len()
.try_into()
.map_err(|_| StringArrayError::NumElements)?;
let mut cumulative_size: u32 = 0;
for elem in elems.iter() {
let elem_bytes = elem
.as_bytes_with_nul()
.len()
.try_into()
.map_err(|_| StringArrayError::ElemSize)?;
cumulative_size = cumulative_size
.checked_add(elem_bytes)
.ok_or(StringArrayError::CumElemSize)?;
if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::CumulativeSize);
}
Ok(Self {
elems,
number_elements,
cumulative_size,
})
self.elems.push(elem);
Ok(())
}
pub fn number_elements(&self) -> u32 {
self.elems.len() as u32
}
pub fn cumulative_size(&self) -> u32 {
self.elems
.iter()
.map(|e| e.as_bytes().len() + 1)
.sum::<usize>() as u32
}
pub fn write_to_guest<'a>(
@@ -123,22 +50,24 @@ impl StringArray {
buffer: &GuestPtr<'a, u8>,
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
) -> Result<(), Error> {
let element_heads = element_heads.as_array(self.number_elements);
let buffer = buffer.as_array(self.cumulative_size);
let element_heads = element_heads.as_array(self.number_elements());
let buffer = buffer.as_array(self.cumulative_size());
let mut cursor = 0;
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
let bytes = elem.as_bytes_with_nul();
let len: u32 = bytes.len().try_into()?;
let bytes = elem.as_bytes();
let len = bytes.len() as u32;
{
let elem_buffer = buffer
.get_range(cursor..(cursor + len))
.ok_or(Error::Inval)?; // Elements don't fit in buffer provided
.ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided
elem_buffer.copy_from_slice(bytes)?;
head?.write(
elem_buffer
.get(0)
.expect("all elem buffers at least length 1"),
)?;
cursor += len;
}
buffer
.get(cursor + len)
.ok_or(Error::invalid_argument())?
.write(0)?; // 0 terminate
head?.write(buffer.get(cursor).expect("already validated"))?;
cursor += len + 1;
}
Ok(())
}

View File

@@ -1,17 +0,0 @@
use crate::sched::{Subclockflags, SubscriptionClock};
use crate::{Error, Result};
use std::time::SystemTime;
pub(crate) use super::sys_impl::clock::*;
pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result<u128> {
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
return Ok(u128::from(clock.timeout));
}
let now: u128 = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Error::Notcapable)?
.as_nanos();
let deadline = u128::from(clock.timeout);
Ok(deadline.saturating_sub(now))
}

View File

@@ -1,45 +0,0 @@
use crate::handle::{Fstflags, Timestamp};
use crate::{Error, Result};
use filetime::{set_file_handle_times, FileTime};
use std::fs::File;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(crate) use super::sys_impl::fd::*;
pub(crate) fn filestat_set_times(
file: &File,
st_atim: Timestamp,
st_mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
let set_atim = fst_flags.contains(Fstflags::ATIM);
let set_atim_now = fst_flags.contains(Fstflags::ATIM_NOW);
let set_mtim = fst_flags.contains(Fstflags::MTIM);
let set_mtim_now = fst_flags.contains(Fstflags::MTIM_NOW);
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(Error::Inval);
}
let atim = if set_atim {
let time = UNIX_EPOCH + Duration::from_nanos(st_atim);
Some(FileTime::from_system_time(time))
} else if set_atim_now {
let time = SystemTime::now();
Some(FileTime::from_system_time(time))
} else {
None
};
let mtim = if set_mtim {
let time = UNIX_EPOCH + Duration::from_nanos(st_mtim);
Some(FileTime::from_system_time(time))
} else if set_mtim_now {
let time = SystemTime::now();
Some(FileTime::from_system_time(time))
} else {
None
};
set_file_handle_times(file, atim, mtim)?;
Ok(())
}

View File

@@ -1,95 +0,0 @@
pub(crate) mod clock;
pub(crate) mod fd;
pub(crate) mod osdir;
pub(crate) mod osfile;
pub(crate) mod osother;
pub(crate) mod stdio;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(unix)] {
mod unix;
use unix as sys_impl;
pub use unix::preopen_dir;
} else if #[cfg(windows)] {
mod windows;
use windows as sys_impl;
pub use windows::preopen_dir;
} else {
compile_error!("wasi-common doesn't compile for this platform yet");
}
}
pub(crate) use sys_impl::path;
pub(crate) use sys_impl::poll;
use super::handle::{Filetype, Handle};
use osdir::OsDir;
use osfile::OsFile;
use osother::OsOther;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use stdio::{Stderr, Stdin, Stdout};
use sys_impl::get_file_type;
pub(crate) trait AsFile {
fn as_file(&self) -> io::Result<ManuallyDrop<File>>;
}
impl AsFile for dyn Handle + 'static {
fn as_file(&self) -> io::Result<ManuallyDrop<File>> {
if let Some(file) = self.as_any().downcast_ref::<OsFile>() {
file.as_file()
} else if let Some(dir) = self.as_any().downcast_ref::<OsDir>() {
dir.as_file()
} else if let Some(stdin) = self.as_any().downcast_ref::<Stdin>() {
stdin.as_file()
} else if let Some(stdout) = self.as_any().downcast_ref::<Stdout>() {
stdout.as_file()
} else if let Some(stderr) = self.as_any().downcast_ref::<Stderr>() {
stderr.as_file()
} else if let Some(other) = self.as_any().downcast_ref::<OsOther>() {
other.as_file()
} else {
tracing::error!("tried to make std::fs::File from non-OS handle");
Err(io::Error::from_raw_os_error(libc::EBADF))
}
}
}
impl TryFrom<File> for Box<dyn Handle> {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let file_type = get_file_type(&file)?;
match file_type {
Filetype::RegularFile => {
let handle = OsFile::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsFile"
);
Ok(Box::new(handle))
}
Filetype::Directory => {
let handle = OsDir::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsDir"
);
Ok(Box::new(handle))
}
_ => {
let handle = OsOther::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsOther"
);
Ok(Box::new(handle))
}
}
}
}

View File

@@ -1,145 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, path, AsFile};
use crate::handle::{
Dircookie, Dirent, Fdflags, Filestat, Filetype, Fstflags, Handle, HandleRights, Oflags,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::io;
use std::ops::Deref;
use tracing::{debug, error};
// TODO could this be cleaned up?
// The actual `OsDir` struct is OS-dependent, therefore we delegate
// its definition to OS-specific modules.
pub use super::sys_impl::osdir::OsDir;
impl Deref for OsDir {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsDir {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let handle = self.handle.try_clone()?;
let new = Self::new(self.rights.get(), handle)?;
Ok(Box::new(new))
}
fn get_file_type(&self) -> Filetype {
Filetype::Directory
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(new_file) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(new_file);
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn filestat_set_times(
&self,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags)
}
fn readdir<'a>(
&'a self,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>> + 'a>> {
fd::readdir(self, cookie)
}
// PathOps
fn create_directory(&self, path: &str) -> Result<()> {
path::create_directory(self, path)
}
fn filestat_get_at(&self, path: &str, follow: bool) -> Result<Filestat> {
path::filestat_get_at(self, path, follow)
}
fn filestat_set_times_at(
&self,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
follow: bool,
) -> Result<()> {
path::filestat_set_times_at(self, path, atim, mtim, fst_flags, follow)
}
fn openat(
&self,
path: &str,
read: bool,
write: bool,
oflags: Oflags,
fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
path::open(self, path, read, write, oflags, fd_flags)
}
fn link(
&self,
old_path: &str,
new_handle: Box<dyn Handle>,
new_path: &str,
follow: bool,
) -> Result<()> {
let new_handle = match new_handle.as_any().downcast_ref::<Self>() {
None => {
error!("Tried to link with handle that's not an OsDir");
return Err(Error::Badf);
}
Some(handle) => handle,
};
path::link(self, old_path, new_handle, new_path, follow)
}
fn symlink(&self, old_path: &str, new_path: &str) -> Result<()> {
path::symlink(old_path, self, new_path)
}
fn readlink(&self, path: &str, buf: &mut [u8]) -> Result<usize> {
path::readlink(self, path, buf)
}
fn readlinkat(&self, path: &str) -> Result<String> {
path::readlinkat(self, path)
}
fn rename(&self, old_path: &str, new_handle: Box<dyn Handle>, new_path: &str) -> Result<()> {
let new_handle = match new_handle.as_any().downcast_ref::<Self>() {
None => {
error!("Tried to rename with handle that's not an OsDir");
return Err(Error::Badf);
}
Some(handle) => handle,
};
debug!("rename (old_dirfd, old_path)=({:?}, {:?})", self, old_path);
debug!(
"rename (new_dirfd, new_path)=({:?}, {:?})",
new_handle, new_path
);
path::rename(self, old_path, new_handle, new_path)
}
fn remove_directory(&self, path: &str) -> Result<()> {
debug!("remove_directory (dirfd, path)=({:?}, {:?})", self, path);
path::remove_directory(self, path)
}
fn unlink_file(&self, path: &str) -> Result<()> {
path::unlink_file(self, path)
}
}

View File

@@ -1,148 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, AsFile};
use crate::handle::{
Advice, Fdflags, Filesize, Filestat, Filetype, Fstflags, Handle, HandleRights,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::cell::Cell;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::Deref;
#[derive(Debug)]
/// A file backed by the operating system's file system. Dereferences to a
/// `RawOsHandle`. Its impl of `Handle` uses Rust's `std` to implement all
/// file descriptor operations.
///
/// # Constructing `OsFile`
///
/// `OsFile` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsFile;
///
/// let file = OpenOptions::new().read(true).open("some_file").unwrap();
/// let os_file = OsFile::try_from(file).unwrap();
/// ```
pub struct OsFile {
rights: Cell<HandleRights>,
handle: RawOsHandle,
}
impl OsFile {
pub(super) fn new(rights: HandleRights, handle: RawOsHandle) -> Self {
let rights = Cell::new(rights);
Self { rights, handle }
}
}
impl Deref for OsFile {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsFile {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let handle = self.handle.try_clone()?;
let rights = self.rights.clone();
Ok(Box::new(Self { rights, handle }))
}
fn get_file_type(&self) -> Filetype {
Filetype::RegularFile
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn advise(&self, advice: Advice, offset: Filesize, len: Filesize) -> Result<()> {
fd::advise(self, advice, offset, len)
}
fn allocate(&self, offset: Filesize, len: Filesize) -> Result<()> {
let fd = self.as_file()?;
let metadata = fd.metadata()?;
let current_size = metadata.len();
let wanted_size = offset.checked_add(len).ok_or(Error::TooBig)?;
// This check will be unnecessary when rust-lang/rust#63326 is fixed
if wanted_size > i64::max_value() as u64 {
return Err(Error::TooBig);
}
if wanted_size > current_size {
fd.set_len(wanted_size)?;
}
Ok(())
}
fn datasync(&self) -> Result<()> {
self.as_file()?.sync_data()?;
Ok(())
}
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(new_handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(new_handle);
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn filestat_set_size(&self, size: Filesize) -> Result<()> {
self.as_file()?.set_len(size)?;
Ok(())
}
fn filestat_set_times(
&self,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags)
}
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: u64) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nread = self.read_vectored(buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nread)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: u64) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nwritten = self.write_vectored(&buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nwritten)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = self.as_file()?.read_vectored(iovs)?;
Ok(nread)
}
fn seek(&self, offset: SeekFrom) -> Result<u64> {
let pos = self.as_file()?.seek(offset)?;
Ok(pos)
}
fn sync(&self) -> Result<()> {
self.as_file()?.sync_all()?;
Ok(())
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let nwritten = self.as_file()?.write_vectored(&iovs)?;
Ok(nwritten)
}
}

View File

@@ -1,103 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, AsFile};
use crate::handle::{Fdflags, Filetype, Handle, HandleRights};
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::Result;
use std::any::Any;
use std::cell::Cell;
use std::fs::File;
use std::io::{self, Read, Write};
use std::ops::Deref;
/// `OsOther` is something of a catch-all for everything not covered with the specific handle
/// types (`OsFile`, `OsDir`, `Stdio`). It currently encapsulates handles such as OS pipes,
/// sockets, streams, etc. As such, when redirecting stdio within `WasiCtxBuilder`, the redirected
/// pipe should be encapsulated within this instance _and not_ `OsFile` which represents a regular
/// OS file.
///
/// # Constructing `OsOther`
///
/// `OsOther` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsOther;
///
/// let pipe = OpenOptions::new().read(true).open("a_pipe").unwrap();
/// let os_other = OsOther::try_from(pipe).unwrap();
/// ```
#[derive(Debug)]
pub struct OsOther {
file_type: Filetype,
rights: Cell<HandleRights>,
handle: RawOsHandle,
}
impl OsOther {
pub(super) fn new(file_type: Filetype, rights: HandleRights, handle: RawOsHandle) -> Self {
let rights = Cell::new(rights);
Self {
file_type,
rights,
handle,
}
}
}
impl Deref for OsOther {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsOther {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let file_type = self.file_type;
let handle = self.handle.try_clone()?;
let rights = self.rights.clone();
Ok(Box::new(Self {
file_type,
rights,
handle,
}))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(handle);
}
Ok(())
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = self.as_file()?.read_vectored(iovs)?;
Ok(nread)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let nwritten = if self.is_tty() {
SandboxedTTYWriter::new(&mut fd).write_vectored(&iovs)?
} else {
fd.write_vectored(iovs)?
};
Ok(nwritten)
}
}

View File

@@ -1,241 +0,0 @@
// The reason we have a separate Stdio wrappers is to correctly facilitate redirects on Windows.
// To elaborate further, in POSIX, we can get a stdio handle by opening a specific fd {0,1,2}.
// On Windows however, we need to issue a syscall that's separate from standard Windows "open"
// to get a console handle, and this is GetStdHandle. This is exactly what Rust does and what
// is wrapped inside their Stdio object in the libstd. We wrap it here as well because of this
// nuance on Windows:
//
// The standard handles of a process may be redirected by a call to SetStdHandle, in which
// case GetStdHandle returns the redirected handle.
//
// The MSDN also says this however:
//
// If the standard handles have been redirected, you can specify the CONIN$ value in a call
// to the CreateFile function to get a handle to a console's input buffer. Similarly, you
// can specify the CONOUT$ value to get a handle to a console's active screen buffer.
//
// TODO it might worth re-investigating the suitability of this type on Windows.
use super::{fd, AsFile};
use crate::handle::{Fdflags, Filestat, Filetype, Handle, HandleRights, Rights, RightsExt, Size};
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::{Error, Result};
use std::any::Any;
use std::cell::Cell;
use std::convert::TryInto;
use std::io::{self, Read, Write};
pub(crate) trait StdinExt: Sized {
/// Create `Stdin` from `io::stdin`.
fn stdin() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdin {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = io::stdin().read_vectored(iovs)?;
Ok(nread)
}
}
pub(crate) trait StdoutExt: Sized {
/// Create `Stdout` from `io::stdout`.
fn stdout() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdout {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdout {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// lock for the duration of the scope
let stdout = io::stdout();
let mut stdout = stdout.lock();
let nwritten = if self.is_tty() {
SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)?
} else {
stdout.write_vectored(iovs)?
};
stdout.flush()?;
Ok(nwritten)
}
}
pub(crate) trait StderrExt: Sized {
/// Create `Stderr` from `io::stderr`.
fn stderr() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stderr {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stderr {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// Always sanitize stderr, even if it's not directly connected to a tty,
// because stderr is meant for diagnostics rather than binary output,
// and may be redirected to a file which could end up being displayed
// on a tty later.
let nwritten = SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?;
Ok(nwritten)
}
}
#[derive(Debug, Clone)]
pub(crate) struct NullDevice {
pub(crate) rights: Cell<HandleRights>,
pub(crate) fd_flags: Cell<Fdflags>,
}
impl NullDevice {
pub(crate) fn new() -> Self {
let rights = HandleRights::new(
Rights::character_device_base(),
Rights::character_device_inheriting(),
);
let rights = Cell::new(rights);
let fd_flags = Fdflags::empty();
let fd_flags = Cell::new(fd_flags);
Self { rights, fd_flags }
}
}
impl Handle for NullDevice {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
Filetype::CharacterDevice
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(self.fd_flags.get())
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
self.fd_flags.set(fdflags);
Ok(())
}
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Ok(0)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let mut total_len = 0u32;
for iov in iovs {
let len: Size = iov.len().try_into()?;
total_len = total_len.checked_add(len).ok_or(Error::Overflow)?;
}
Ok(total_len as usize)
}
}

View File

@@ -1,4 +0,0 @@
pub(crate) mod osdir;
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::SYNC;

View File

@@ -1,65 +0,0 @@
use crate::handle::HandleRights;
use crate::sys::sys_impl::oshandle::RawOsHandle;
use crate::Result;
use std::cell::{Cell, RefCell, RefMut};
use std::io;
use yanix::dir::Dir;
#[derive(Debug)]
/// A directory in the operating system's file system. Its impl of `Handle` is
/// in `sys::osdir`. This type is exposed to all other modules as
/// `sys::osdir::OsDir` when configured.
///
/// # Constructing `OsDir`
///
/// `OsDir` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsDir;
///
/// let dir = OpenOptions::new().read(true).open("some_dir").unwrap();
/// let os_dir = OsDir::try_from(dir).unwrap();
/// ```
pub struct OsDir {
pub(crate) rights: Cell<HandleRights>,
pub(crate) handle: RawOsHandle,
// When the client makes a `fd_readdir` syscall on this descriptor,
// we will need to cache the `libc::DIR` pointer manually in order
// to be able to seek on it later. While on Linux, this is handled
// by the OS, BSD Unixes require the client to do this caching.
//
// This comes directly from the BSD man pages on `readdir`:
// > Values returned by telldir() are good only for the lifetime
// > of the DIR pointer, dirp, from which they are derived.
// > If the directory is closed and then reopened, prior values
// > returned by telldir() will no longer be valid.
stream_ptr: RefCell<Dir>,
}
impl OsDir {
pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result<Self> {
let rights = Cell::new(rights);
// We need to duplicate the handle, because `opendir(3)`:
// Upon successful return from fdopendir(), the file descriptor is under
// control of the system, and if any attempt is made to close the file
// descriptor, or to modify the state of the associated description other
// than by means of closedir(), readdir(), readdir_r(), or rewinddir(),
// the behaviour is undefined.
let stream_ptr = Dir::from(handle.try_clone()?)?;
let stream_ptr = RefCell::new(stream_ptr);
Ok(Self {
rights,
handle,
stream_ptr,
})
}
/// Returns the `Dir` stream pointer associated with this `OsDir`. Duck
/// typing: sys::unix::fd::readdir expects the configured OsDir to have
/// this method.
pub(crate) fn stream_ptr(&self) -> Result<RefMut<Dir>> {
Ok(self.stream_ptr.borrow_mut())
}
}

Some files were not shown because too many files have changed in this diff Show More