Merge pull request #2487 from bytecodealliance/pch/wasi_common_cap_std
rewrite wasi-common in terms of cap-std
This commit is contained in:
306
Cargo.lock
generated
306
Cargo.lock
generated
@@ -260,9 +260,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.4.0"
|
version = "3.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -270,6 +270,71 @@ version = "1.4.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
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]]
|
[[package]]
|
||||||
name = "capstone"
|
name = "capstone"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -443,16 +508,6 @@ dependencies = [
|
|||||||
"glob",
|
"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]]
|
[[package]]
|
||||||
name = "cpuid-bool"
|
name = "cpuid-bool"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -815,9 +870,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.1.3"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183"
|
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1070,10 +1125,6 @@ dependencies = [
|
|||||||
name = "example-fib-debug-wasm"
|
name = "example-fib-debug-wasm"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "example-wasi-fs-wasm"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-wasi-wasm"
|
name = "example-wasi-wasm"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@@ -1133,6 +1184,16 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
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]]
|
[[package]]
|
||||||
name = "fst"
|
name = "fst"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@@ -1180,7 +1241,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.1+wasi-snapshot-preview1",
|
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1321,6 +1382,12 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"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]]
|
[[package]]
|
||||||
name = "iter-enum"
|
name = "iter-enum"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
@@ -1408,9 +1475,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.82"
|
version = "0.2.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -1473,11 +1540,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1498,6 +1565,12 @@ dependencies = [
|
|||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-owned"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.4"
|
||||||
@@ -1800,7 +1873,7 @@ dependencies = [
|
|||||||
"peepmatic-test",
|
"peepmatic-test",
|
||||||
"peepmatic-test-operator",
|
"peepmatic-test-operator",
|
||||||
"peepmatic-traits",
|
"peepmatic-traits",
|
||||||
"rand 0.8.2",
|
"rand 0.8.3",
|
||||||
"serde",
|
"serde",
|
||||||
"wast 32.0.0",
|
"wast 32.0.0",
|
||||||
]
|
]
|
||||||
@@ -1878,6 +1951,15 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -1916,6 +1998,19 @@ dependencies = [
|
|||||||
"universal-hash",
|
"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]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@@ -2036,7 +2131,7 @@ checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger 0.8.2",
|
"env_logger 0.8.2",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.2",
|
"rand 0.8.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2069,9 +2164,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
|
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.0",
|
"rand_chacha 0.3.0",
|
||||||
@@ -2338,6 +2433,15 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
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]]
|
[[package]]
|
||||||
name = "rusty-fork"
|
name = "rusty-fork"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -2382,9 +2486,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scroll_derive"
|
name = "scroll_derive"
|
||||||
version = "0.10.4"
|
version = "0.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b12bd20b94c7cdfda8c7ba9b92ad0d9a56e3fa018c25fca83b51aa664c9b4c0d"
|
checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2392,19 +2496,37 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "semver"
|
||||||
version = "1.0.120"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.120"
|
version = "1.0.123"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775"
|
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2424,18 +2546,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_test"
|
name = "serde_test"
|
||||||
version = "1.0.120"
|
version = "1.0.123"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dd7d96489b14fa2f4a89be299ac117c8023d1ead9aaee963a2dde72dad4d14b"
|
checksum = "38145a8510bdf71d9a8cceeb57664049538446e77f24648328bdbcf22dc7e169"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
|
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
@@ -2476,7 +2598,7 @@ checksum = "4ee9977fa98489d9006f4ab26fc5cbe2a139985baed09d2ec08dee6e506fc496"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"rand 0.8.2",
|
"rand 0.8.3",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2587,9 +2709,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.58"
|
version = "1.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
|
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2608,6 +2730,23 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -2622,7 +2761,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"rand 0.8.2",
|
"rand 0.8.3",
|
||||||
"redox_syscall 0.2.4",
|
"redox_syscall 0.2.4",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
"winapi",
|
"winapi",
|
||||||
@@ -2662,11 +2801,13 @@ name = "test-programs"
|
|||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cap-std",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"wasi-cap-std-sync",
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-wasi",
|
"wasmtime-wasi",
|
||||||
@@ -2704,11 +2845,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.0"
|
version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
|
checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2827,6 +2968,12 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
@@ -2864,6 +3011,15 @@ dependencies = [
|
|||||||
"traitobject",
|
"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]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -2904,27 +3060,44 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
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"
|
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]]
|
[[package]]
|
||||||
name = "wasi-common"
|
name = "wasi-common"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if 1.0.0",
|
"bitflags",
|
||||||
"cpu-time",
|
"cap-rand",
|
||||||
"filetime",
|
"cap-std",
|
||||||
"getrandom 0.2.2",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
"libc",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wiggle",
|
"wiggle",
|
||||||
"winapi",
|
"winapi",
|
||||||
"winx",
|
|
||||||
"yanix",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3035,6 +3208,7 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"wasi-cap-std-sync",
|
||||||
"wasmparser",
|
"wasmparser",
|
||||||
"wasmtime-cache",
|
"wasmtime-cache",
|
||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
@@ -3051,8 +3225,9 @@ name = "wasmtime-bench-api"
|
|||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cap-std",
|
||||||
"shuffling-allocator",
|
"shuffling-allocator",
|
||||||
"wasi-common",
|
"wasi-cap-std-sync",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-wasi",
|
"wasmtime-wasi",
|
||||||
"wat",
|
"wat",
|
||||||
@@ -3063,8 +3238,10 @@ name = "wasmtime-c-api"
|
|||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cap-std",
|
||||||
"env_logger 0.8.2",
|
"env_logger 0.8.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"wasi-cap-std-sync",
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-c-api-macros",
|
"wasmtime-c-api-macros",
|
||||||
@@ -3109,6 +3286,7 @@ name = "wasmtime-cli"
|
|||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cap-std",
|
||||||
"env_logger 0.8.2",
|
"env_logger 0.8.2",
|
||||||
"file-per-thread-logger",
|
"file-per-thread-logger",
|
||||||
"filecheck",
|
"filecheck",
|
||||||
@@ -3124,6 +3302,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"test-programs",
|
"test-programs",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"wasi-cap-std-sync",
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
"wasmparser",
|
"wasmparser",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
@@ -3336,10 +3515,8 @@ name = "wasmtime-wasi"
|
|||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"tracing",
|
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-runtime",
|
|
||||||
"wasmtime-wiggle",
|
"wasmtime-wiggle",
|
||||||
"wiggle",
|
"wiggle",
|
||||||
]
|
]
|
||||||
@@ -3529,6 +3706,8 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "winx"
|
name = "winx"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d35176e4c7e0daf9d8da18b7e9df81a8f2358fb3d6feea775ce810f974a512"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cvt",
|
"cvt",
|
||||||
@@ -3564,17 +3743,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yanix"
|
|
||||||
version = "0.22.0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"filetime",
|
|
||||||
"libc",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "z3"
|
name = "z3"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|||||||
@@ -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-crypto = { path = "crates/wasi-crypto", version = "0.22.0", optional = true }
|
||||||
wasmtime-wasi-nn = { path = "crates/wasi-nn", 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-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"] }
|
structopt = { version = "0.3.5", features = ["color", "suggestions"] }
|
||||||
object = { version = "0.23.0", default-features = false, features = ["write"] }
|
object = { version = "0.23.0", default-features = false, features = ["write"] }
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
@@ -45,6 +46,7 @@ log = "0.4.8"
|
|||||||
rayon = "1.2.1"
|
rayon = "1.2.1"
|
||||||
humantime = "2.0.0"
|
humantime = "2.0.0"
|
||||||
wasmparser = "0.73.0"
|
wasmparser = "0.73.0"
|
||||||
|
cap-std = "0.13"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.8.1"
|
env_logger = "0.8.1"
|
||||||
@@ -73,9 +75,10 @@ members = [
|
|||||||
"crates/misc/rust",
|
"crates/misc/rust",
|
||||||
"crates/wiggle",
|
"crates/wiggle",
|
||||||
"crates/wiggle/wasmtime",
|
"crates/wiggle/wasmtime",
|
||||||
|
"crates/wasi-common",
|
||||||
|
"crates/wasi-common/cap-std-sync",
|
||||||
"examples/fib-debug/wasm",
|
"examples/fib-debug/wasm",
|
||||||
"examples/wasi/wasm",
|
"examples/wasi/wasm",
|
||||||
"examples/wasi-fs/wasm",
|
|
||||||
"fuzz",
|
"fuzz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ anyhow = "1.0"
|
|||||||
shuffling-allocator = { version = "1.1.1", optional = true }
|
shuffling-allocator = { version = "1.1.1", optional = true }
|
||||||
wasmtime = { path = "../wasmtime", default-features = false }
|
wasmtime = { path = "../wasmtime", default-features = false }
|
||||||
wasmtime-wasi = { path = "../wasi" }
|
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]
|
[dev-dependencies]
|
||||||
wat = "1.0"
|
wat = "1.0"
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ use std::env;
|
|||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use wasi_common::WasiCtxBuilder;
|
use wasi_cap_std_sync::WasiCtxBuilder;
|
||||||
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
|
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
|
||||||
use wasmtime_wasi::Wasi;
|
use wasmtime_wasi::Wasi;
|
||||||
|
|
||||||
@@ -211,16 +211,16 @@ impl BenchState {
|
|||||||
// Create a WASI environment.
|
// Create a WASI environment.
|
||||||
|
|
||||||
let mut cx = WasiCtxBuilder::new();
|
let mut cx = WasiCtxBuilder::new();
|
||||||
cx.inherit_stdio();
|
cx = cx.inherit_stdio();
|
||||||
// Allow access to the working directory so that the benchmark can read
|
// Allow access to the working directory so that the benchmark can read
|
||||||
// its input workload(s).
|
// 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")?;
|
.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
|
// 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.
|
// input workload(s) if it has them and that has been requested.
|
||||||
if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
|
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()?;
|
let cx = cx.build()?;
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ wat = { version = "1.0.23", optional = true }
|
|||||||
|
|
||||||
# Optional dependencies for the `wasi` feature
|
# Optional dependencies for the `wasi` feature
|
||||||
wasi-common = { path = "../wasi-common", optional = true }
|
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 }
|
wasmtime-wasi = { path = "../wasi", optional = true }
|
||||||
|
cap-std = { version = "0.13", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ['jitdump', 'wat', 'wasi', 'cache']
|
default = ['jitdump', 'wat', 'wasi', 'cache']
|
||||||
lightbeam = ["wasmtime/lightbeam"]
|
lightbeam = ["wasmtime/lightbeam"]
|
||||||
jitdump = ["wasmtime/jitdump"]
|
jitdump = ["wasmtime/jitdump"]
|
||||||
cache = ["wasmtime/cache"]
|
cache = ["wasmtime/cache"]
|
||||||
wasi = ['wasi-common', 'wasmtime-wasi']
|
wasi = ['wasi-common', 'wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std']
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
//! The WASI embedding API definitions for Wasmtime.
|
//! The WASI embedding API definitions for Wasmtime.
|
||||||
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t};
|
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use cap_std::fs::Dir;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::str;
|
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::{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> {
|
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
|
||||||
CStr::from_ptr(path).to_str().map(Path::new).ok()
|
CStr::from_ptr(path).to_str().map(Path::new).ok()
|
||||||
@@ -39,7 +45,7 @@ pub struct wasi_config_t {
|
|||||||
stdin: Option<File>,
|
stdin: Option<File>,
|
||||||
stdout: Option<File>,
|
stdout: Option<File>,
|
||||||
stderr: Option<File>,
|
stderr: Option<File>,
|
||||||
preopens: Vec<(File, PathBuf)>,
|
preopens: Vec<(Dir, PathBuf)>,
|
||||||
inherit_args: bool,
|
inherit_args: bool,
|
||||||
inherit_env: bool,
|
inherit_env: bool,
|
||||||
inherit_stdin: bool,
|
inherit_stdin: bool,
|
||||||
@@ -180,7 +186,7 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let dir = match cstr_to_path(path) {
|
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,
|
Ok(d) => d,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
},
|
},
|
||||||
@@ -197,39 +203,57 @@ enum WasiInstance {
|
|||||||
Snapshot0(WasiSnapshot0),
|
Snapshot0(WasiSnapshot0),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_wasi_ctx(config: wasi_config_t) -> Result<WasiCtx> {
|
fn create_wasi_ctx(config: wasi_config_t) -> Result<Rc<RefCell<WasiCtx>>> {
|
||||||
use std::convert::TryFrom;
|
|
||||||
use wasi_common::OsFile;
|
|
||||||
let mut builder = WasiCtxBuilder::new();
|
let mut builder = WasiCtxBuilder::new();
|
||||||
if config.inherit_args {
|
if config.inherit_args {
|
||||||
builder.inherit_args();
|
builder = builder.inherit_args()?;
|
||||||
} else if !config.args.is_empty() {
|
} 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 {
|
if config.inherit_env {
|
||||||
builder.inherit_env();
|
builder = builder.inherit_env()?;
|
||||||
} else if !config.env.is_empty() {
|
} 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 {
|
if config.inherit_stdin {
|
||||||
builder.inherit_stdin();
|
builder = builder.inherit_stdin();
|
||||||
} else if let Some(file) = config.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 {
|
if config.inherit_stdout {
|
||||||
builder.inherit_stdout();
|
builder = builder.inherit_stdout();
|
||||||
} else if let Some(file) = config.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 {
|
if config.inherit_stderr {
|
||||||
builder.inherit_stderr();
|
builder = builder.inherit_stderr();
|
||||||
} else if let Some(file) = config.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 {
|
for (dir, path) in config.preopens {
|
||||||
builder.preopened_dir(preopen.0, preopen.1);
|
builder = builder.preopened_dir(dir, path)?;
|
||||||
}
|
}
|
||||||
Ok(builder.build()?)
|
Ok(Rc::new(RefCell::new(builder.build()?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ cfg-if = "1.0"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasi-common = { path = "../wasi-common", version = "0.22.0" }
|
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 = { path = "../wasmtime", version = "0.22.0" }
|
||||||
|
wasmtime-wasi = { path = "../wasi", version = "0.22.0" }
|
||||||
target-lexicon = "0.11.0"
|
target-lexicon = "0.11.0"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
os_pipe = "0.9"
|
os_pipe = "0.9"
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
wat = "1.0.23"
|
wat = "1.0.23"
|
||||||
|
cap-std = "0.13"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test_programs = []
|
test_programs = []
|
||||||
|
|||||||
@@ -16,14 +16,6 @@ mod wasi_tests {
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
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() {
|
pub(super) fn build_and_generate_tests() {
|
||||||
// Validate if any of test sources are present and if they changed
|
// Validate if any of test sources are present and if they changed
|
||||||
// This should always work since there is no submodule to init anymore
|
// This should always work since there is no submodule to init anymore
|
||||||
@@ -47,11 +39,13 @@ mod wasi_tests {
|
|||||||
let mut out =
|
let mut out =
|
||||||
File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file");
|
File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file");
|
||||||
build_tests("wasi-tests", &out_dir).expect("building tests");
|
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<()> {
|
fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
|
cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "1");
|
||||||
cmd.args(&[
|
cmd.args(&[
|
||||||
"build",
|
"build",
|
||||||
"--release",
|
"--release",
|
||||||
@@ -75,7 +69,12 @@ mod wasi_tests {
|
|||||||
Ok(())
|
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"))
|
let mut dir_entries: Vec<_> = read_dir(out_dir.join("wasm32-wasi/release"))
|
||||||
.expect("reading testsuite directory")
|
.expect("reading testsuite directory")
|
||||||
.map(|r| r.expect("reading testsuite directory entry"))
|
.map(|r| r.expect("reading testsuite directory entry"))
|
||||||
@@ -110,33 +109,19 @@ mod wasi_tests {
|
|||||||
.expect("testsuite filename should be representable as a string")
|
.expect("testsuite filename should be representable as a string")
|
||||||
.replace("-", "_")
|
.replace("-", "_")
|
||||||
)?;
|
)?;
|
||||||
writeln!(out, " use super::{{runtime, utils, setup_log}};")?;
|
writeln!(
|
||||||
writeln!(out, " use runtime::PreopenType;")?;
|
out,
|
||||||
|
" use super::{{runtime::{} as runtime, utils, setup_log}};",
|
||||||
|
runtime
|
||||||
|
)?;
|
||||||
for dir_entry in dir_entries {
|
for dir_entry in dir_entries {
|
||||||
let test_path = dir_entry.path();
|
write_testsuite_tests(out, &dir_entry.path(), testsuite)?;
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
writeln!(out, "}}")?;
|
writeln!(out, "}}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_testsuite_tests(
|
fn write_testsuite_tests(out: &mut File, path: &Path, testsuite: &str) -> io::Result<()> {
|
||||||
out: &mut File,
|
|
||||||
path: &Path,
|
|
||||||
testsuite: &str,
|
|
||||||
preopen_type: PreopenType,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let stemstr = path
|
let stemstr = path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.expect("file_stem")
|
.expect("file_stem")
|
||||||
@@ -144,15 +129,7 @@ mod wasi_tests {
|
|||||||
.expect("to_str");
|
.expect("to_str");
|
||||||
|
|
||||||
writeln!(out, " #[test]")?;
|
writeln!(out, " #[test]")?;
|
||||||
let test_fn_name = format!(
|
let test_fn_name = stemstr.replace("-", "_");
|
||||||
"{}{}",
|
|
||||||
&stemstr.replace("-", "_"),
|
|
||||||
if let PreopenType::Virtual = preopen_type {
|
|
||||||
"_virtualfs"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if ignore(testsuite, &test_fn_name) {
|
if ignore(testsuite, &test_fn_name) {
|
||||||
writeln!(out, " #[ignore]")?;
|
writeln!(out, " #[ignore]")?;
|
||||||
}
|
}
|
||||||
@@ -171,109 +148,124 @@ mod wasi_tests {
|
|||||||
let workspace = if no_preopens(testsuite, stemstr) {
|
let workspace = if no_preopens(testsuite, stemstr) {
|
||||||
"None"
|
"None"
|
||||||
} else {
|
} else {
|
||||||
match preopen_type {
|
writeln!(
|
||||||
PreopenType::OS => {
|
out,
|
||||||
writeln!(
|
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
||||||
out,
|
)?;
|
||||||
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
"Some(workspace.path())"
|
||||||
)?;
|
|
||||||
"Some(workspace.path())"
|
|
||||||
}
|
|
||||||
PreopenType::Virtual => "Some(std::path::Path::new(&bin_name))",
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
" runtime::instantiate(&data, &bin_name, {}, {})",
|
" runtime::{}(&data, &bin_name, {})",
|
||||||
|
if inherit_stdio(testsuite, stemstr) {
|
||||||
|
"instantiate_inherit_stdio"
|
||||||
|
} else {
|
||||||
|
"instantiate"
|
||||||
|
},
|
||||||
workspace,
|
workspace,
|
||||||
match preopen_type {
|
|
||||||
PreopenType::OS => "PreopenType::OS",
|
|
||||||
PreopenType::Virtual => "PreopenType::Virtual",
|
|
||||||
}
|
|
||||||
)?;
|
)?;
|
||||||
writeln!(out, " }}")?;
|
writeln!(out, " }}")?;
|
||||||
writeln!(out)?;
|
writeln!(out)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
fn ignore(testsuite: &str, name: &str) -> bool {
|
||||||
if #[cfg(not(windows))] {
|
match testsuite {
|
||||||
/// Ignore tests that aren't supported yet.
|
"wasi-cap-std-sync" => cap_std_sync_ignore(name),
|
||||||
fn ignore(testsuite: &str, name: &str) -> bool {
|
"wasi-virtfs" => virtfs_ignore(name),
|
||||||
if testsuite == "wasi-tests" {
|
_ => panic!("unknown test suite: {}", testsuite),
|
||||||
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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/// 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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
/// Ignore tests that aren't supported yet.
|
||||||
|
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
|
/// Mark tests which do not require preopens
|
||||||
fn no_preopens(testsuite: &str, name: &str) -> bool {
|
fn no_preopens(testsuite: &str, name: &str) -> bool {
|
||||||
if testsuite == "wasi-tests" {
|
if testsuite.starts_with("wasi-") {
|
||||||
match name {
|
match name {
|
||||||
"big_random_buf" => true,
|
"big_random_buf" => true,
|
||||||
"clock_time_get" => true,
|
"clock_time_get" => true,
|
||||||
"sched_yield" => true,
|
"sched_yield" => true,
|
||||||
|
"poll_oneoff_stdio" => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) }
|
|
||||||
}
|
|
||||||
125
crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
Normal file
125
crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
Normal 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,))),
|
||||||
|
}
|
||||||
|
}
|
||||||
1
crates/test-programs/tests/wasm_tests/runtime/mod.rs
Normal file
1
crates/test-programs/tests/wasm_tests/runtime/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod cap_std_sync;
|
||||||
23
crates/test-programs/wasi-tests/Cargo.lock
generated
23
crates/test-programs/wasi-tests/Cargo.lock
generated
@@ -1,30 +1,35 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.72"
|
version = "0.2.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "more-asserts"
|
name = "more-asserts"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi-tests"
|
name = "wasi-tests"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static",
|
||||||
"more-asserts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc",
|
||||||
"wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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"
|
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.65"
|
libc = "0.2.65"
|
||||||
wasi = "0.10.0"
|
wasi = "0.10.2"
|
||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
|
||||||
# This crate is built with the wasm32-wasi target, so it's separate
|
# 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
|
# from the main Wasmtime build, so use this directive to exclude it
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
|
||||||
let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as 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");
|
assert_gt!(dir_fd, pre_fd, "dir_fd number");
|
||||||
|
|
||||||
// Try to close a preopened directory handle.
|
// Try to close a preopened directory handle.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_close(pre_fd)
|
wasi::fd_close(pre_fd)
|
||||||
.expect_err("closing a preopened file descriptor")
|
.expect_err("closing a preopened file descriptor")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTSUP,
|
wasi::ERRNO_NOTSUP
|
||||||
"errno should ERRNO_NOTSUP",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to renumber over a preopened directory handle.
|
// Try to renumber over a preopened directory handle.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_renumber(dir_fd, pre_fd)
|
wasi::fd_renumber(dir_fd, pre_fd)
|
||||||
.expect_err("renumbering over a preopened file descriptor")
|
.expect_err("renumbering over a preopened file descriptor")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTSUP,
|
wasi::ERRNO_NOTSUP
|
||||||
"errno should be ERRNO_NOTSUP",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that dir_fd is still open.
|
// 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.
|
// Try to renumber a preopened directory handle.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_renumber(pre_fd, dir_fd)
|
wasi::fd_renumber(pre_fd, dir_fd)
|
||||||
.expect_err("renumbering over a preopened file descriptor")
|
.expect_err("renumbering over a preopened file descriptor")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTSUP,
|
wasi::ERRNO_NOTSUP
|
||||||
"errno should be ERRNO_NOTSUP",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that dir_fd is still open.
|
// Ensure that dir_fd is still open.
|
||||||
|
|||||||
@@ -1,33 +1,35 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_dangling_fd(dir_fd: wasi::Fd) {
|
||||||
// Create a file, open it, delete it without closing the handle,
|
if TESTCONFIG.support_dangling_filesystem() {
|
||||||
// and then try creating it again
|
// Create a file, open it, delete it without closing the handle,
|
||||||
let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap();
|
// and then try creating it again
|
||||||
wasi::fd_close(fd).unwrap();
|
let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap();
|
||||||
let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("failed to open");
|
wasi::fd_close(fd).unwrap();
|
||||||
assert_gt!(
|
let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("failed to open");
|
||||||
file_fd,
|
assert_gt!(
|
||||||
libc::STDERR_FILENO as wasi::Fd,
|
file_fd,
|
||||||
"file descriptor range check",
|
libc::STDERR_FILENO as wasi::Fd,
|
||||||
);
|
"file descriptor range check",
|
||||||
wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink");
|
);
|
||||||
let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap();
|
wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink");
|
||||||
wasi::fd_close(fd).unwrap();
|
let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap();
|
||||||
|
wasi::fd_close(fd).unwrap();
|
||||||
|
|
||||||
// Now, repeat the same process but for a directory
|
// Now, repeat the same process but for a directory
|
||||||
wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir");
|
wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir");
|
||||||
let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect("failed to open dir");
|
.expect("failed to open dir");
|
||||||
assert_gt!(
|
assert_gt!(
|
||||||
subdir_fd,
|
subdir_fd,
|
||||||
libc::STDERR_FILENO as wasi::Fd,
|
libc::STDERR_FILENO as wasi::Fd,
|
||||||
"file descriptor range check",
|
"file descriptor range check",
|
||||||
);
|
);
|
||||||
wasi::path_remove_directory(dir_fd, "subdir").expect("failed to remove dir 2");
|
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");
|
wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir 2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) {
|
||||||
// First create a dangling symlink.
|
if TESTCONFIG.support_dangling_filesystem() {
|
||||||
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
|
// 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.
|
// 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)
|
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect_err("opening a dangling symlink as a directory")
|
.expect_err("opening a dangling symlink as a directory")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_LOOP",
|
wasi::ERRNO_LOOP
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up.
|
// Try to open it as a file with O_NOFOLLOW.
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("failed to remove file");
|
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() {
|
fn main() {
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
|
||||||
// Create a directory in the scratch directory.
|
// Create a directory in the scratch directory.
|
||||||
wasi::path_create_directory(dir_fd, "dir").expect("failed to make directory");
|
wasi::path_create_directory(dir_fd, "dir").expect("failed to make directory");
|
||||||
|
|
||||||
// Open the directory and attempt to request rights for seeking.
|
// 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(
|
||||||
.expect("failed to open file");
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"dir",
|
||||||
|
wasi::OFLAGS_DIRECTORY,
|
||||||
|
wasi::RIGHTS_FD_SEEK,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("failed to open file");
|
||||||
assert_gt!(
|
assert_gt!(
|
||||||
fd,
|
fd,
|
||||||
libc::STDERR_FILENO as wasi::Fd,
|
libc::STDERR_FILENO as wasi::Fd,
|
||||||
@@ -16,12 +24,11 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Attempt to seek.
|
// Attempt to seek.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_seek(fd, 0, wasi::WHENCE_CUR)
|
wasi::fd_seek(fd, 0, wasi::WHENCE_CUR)
|
||||||
.expect_err("seek on a directory")
|
.expect_err("seek on a directory")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTCAPABLE,
|
wasi::ERRNO_BADF
|
||||||
"errno should be ERRNO_NOTCAPABLE"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if we obtained the right to seek.
|
// Check if we obtained the right to seek.
|
||||||
@@ -34,7 +41,7 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
(fdstat.fs_rights_base & wasi::RIGHTS_FD_SEEK),
|
(fdstat.fs_rights_base & wasi::RIGHTS_FD_SEEK),
|
||||||
0,
|
0,
|
||||||
"directory has the seek right",
|
"directory does NOT have the seek right",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_fd_advise(dir_fd: wasi::Fd) {
|
||||||
// Create a file in the scratch directory.
|
// 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_WRITE
|
||||||
| wasi::RIGHTS_FD_ADVISE
|
| wasi::RIGHTS_FD_ADVISE
|
||||||
| wasi::RIGHTS_FD_FILESTAT_GET
|
| wasi::RIGHTS_FD_FILESTAT_GET
|
||||||
|
| wasi::RIGHTS_FD_FILESTAT_SET_SIZE
|
||||||
| wasi::RIGHTS_FD_ALLOCATE,
|
| wasi::RIGHTS_FD_ALLOCATE,
|
||||||
0,
|
0,
|
||||||
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");
|
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat");
|
||||||
assert_eq!(stat.size, 0, "file size should be 0");
|
assert_eq!(stat.size, 0, "file size should be 0");
|
||||||
|
|
||||||
// Allocate some size
|
// set_size it bigger
|
||||||
wasi::fd_allocate(file_fd, 0, 100).expect("allocating size");
|
wasi::fd_filestat_set_size(file_fd, 100).expect("setting size");
|
||||||
|
|
||||||
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 2");
|
let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 2");
|
||||||
assert_eq!(stat.size, 100, "file size should be 100");
|
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
|
// Advise the kernel
|
||||||
wasi::fd_advise(file_fd, 10, 50, wasi::ADVICE_NORMAL).expect("failed advise");
|
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::fd_close(file_fd).expect("failed to close");
|
||||||
wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink");
|
wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 mut buf: [u8; BUF_LEN] = [0; BUF_LEN];
|
||||||
let bufused =
|
let bufused =
|
||||||
wasi::fd_readdir(fd, buf.as_mut_ptr(), BUF_LEN, cookie).expect("failed fd_readdir");
|
wasi::fd_readdir(fd, buf.as_mut_ptr(), BUF_LEN, cookie).expect("failed fd_readdir");
|
||||||
|
|
||||||
assert!(bufused <= BUF_LEN);
|
assert!(bufused <= BUF_LEN);
|
||||||
|
|
||||||
let sl = slice::from_raw_parts(buf.as_ptr(), bufused);
|
let sl = slice::from_raw_parts(buf.as_ptr(), bufused);
|
||||||
@@ -183,7 +182,7 @@ unsafe fn test_fd_readdir_lots(dir_fd: wasi::Fd) {
|
|||||||
if eof {
|
if eof {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cookie = dirs[dirs.len()-1].dirent.d_next;
|
cookie = dirs[dirs.len() - 1].dirent.d_next;
|
||||||
}
|
}
|
||||||
assert_eq!(total, 1002, "expected 1000 entries plus . and ..");
|
assert_eq!(total, 1002, "expected 1000 entries plus . and ..");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_file_allocate(dir_fd: wasi::Fd) {
|
||||||
// Create a file in the scratch directory.
|
// Create a file in the scratch directory.
|
||||||
@@ -27,21 +27,22 @@ unsafe fn test_file_allocate(dir_fd: wasi::Fd) {
|
|||||||
let mut stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
let mut stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
||||||
assert_eq!(stat.size, 0, "file size should be 0");
|
assert_eq!(stat.size, 0, "file size should be 0");
|
||||||
|
|
||||||
// Allocate some size
|
if TESTCONFIG.support_fd_allocate() {
|
||||||
wasi::fd_allocate(file_fd, 0, 100).expect("allocating size");
|
// Allocate some size
|
||||||
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
wasi::fd_allocate(file_fd, 0, 100).expect("allocating size");
|
||||||
assert_eq!(stat.size, 100, "file size should be 100");
|
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
||||||
|
assert_eq!(stat.size, 100, "file size should be 100");
|
||||||
|
|
||||||
// Allocate should not modify if less than current size
|
// Allocate should not modify if less than current size
|
||||||
wasi::fd_allocate(file_fd, 10, 10).expect("allocating size less than current size");
|
wasi::fd_allocate(file_fd, 10, 10).expect("allocating size less than current size");
|
||||||
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
stat = wasi::fd_filestat_get(file_fd).expect("reading file stats");
|
||||||
assert_eq!(stat.size, 100, "file size should remain unchanged at 100");
|
assert_eq!(stat.size, 100, "file size should remain unchanged at 100");
|
||||||
|
|
||||||
// Allocate should modify if offset+len > current_len
|
|
||||||
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");
|
|
||||||
|
|
||||||
|
// Allocate should modify if offset+len > current_len
|
||||||
|
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::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, "file").expect("removing a file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_file_seek_tell(dir_fd: wasi::Fd) {
|
||||||
// Create a file in the scratch directory.
|
// 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");
|
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
|
// Seek before byte 0 is an error though
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_seek(file_fd, -2000, wasi::WHENCE_CUR)
|
wasi::fd_seek(file_fd, -2000, wasi::WHENCE_CUR)
|
||||||
.expect_err("seeking before byte 0 should be an error")
|
.expect_err("seeking before byte 0 should be an error")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_INVAL,
|
wasi::ERRNO_INVAL
|
||||||
"errno should be ERRNO_INVAL",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that fd_read properly updates the file offset
|
// 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 buffer = &mut [0u8; 100];
|
||||||
let iovec = wasi::Iovec {
|
let iovec = wasi::Iovec {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
|
||||||
// Create a directory in the scratch directory.
|
// 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");
|
create_file(dir_fd, "dir/nested/file");
|
||||||
|
|
||||||
// Now open it with an absolute path.
|
// Now open it with an absolute path.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "/dir/nested/file", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "/dir/nested/file", 0, 0, 0, 0)
|
||||||
.expect_err("opening a file with an absolute path")
|
.expect_err("opening a file with an absolute path")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTCAPABLE,
|
wasi::ERRNO_PERM
|
||||||
"errno should be ERRNO_NOTCAPABLE"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now open it with a path containing "..".
|
// 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");
|
wasi::fd_close(file_fd).expect("closing a file");
|
||||||
|
|
||||||
// Now open it with a trailing NUL.
|
// 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)
|
wasi::path_open(dir_fd, 0, "dir/nested/file\0", 0, 0, 0, 0)
|
||||||
.expect_err("opening a file with a trailing NUL")
|
.expect_err("opening a file with a trailing NUL")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_ILSEQ,
|
wasi::ERRNO_ILSEQ
|
||||||
"errno should be ERRNO_ILSEQ",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now open it with a trailing slash.
|
// Now open it with a trailing slash.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "dir/nested/file/", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "dir/nested/file/", 0, 0, 0, 0)
|
||||||
.expect_err("opening a file with a trailing slash should fail")
|
.expect_err("opening a file with a trailing slash should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_NOTDIR",
|
wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now open it with trailing slashes.
|
// Now open it with trailing slashes.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "dir/nested/file///", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "dir/nested/file///", 0, 0, 0, 0)
|
||||||
.expect_err("opening a file with trailing slashes should fail")
|
.expect_err("opening a file with trailing slashes should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_NOTDIR",
|
wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now open the directory with a trailing slash.
|
// 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.
|
// Now open it with a path containing too many ".."s.
|
||||||
let bad_path = format!("dir/nested/../../../{}/dir/nested/file", arg);
|
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)
|
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")
|
.expect_err("opening a file with too many \"..\"s in the path should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTCAPABLE,
|
wasi::ERRNO_PERM
|
||||||
"errno should be ERRNO_NOTCAPABLE",
|
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "dir/nested/file")
|
wasi::path_unlink_file(dir_fd, "dir/nested/file")
|
||||||
.expect("unlink_file on a symlink should succeed");
|
.expect("unlink_file on a symlink should succeed");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use libc;
|
use libc;
|
||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
|
||||||
// Create a directory for the symlink to point to.
|
// 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");
|
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
|
||||||
|
|
||||||
// Try to open it as a directory with O_NOFOLLOW again.
|
// 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)
|
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect_err("opening a directory symlink as a directory should fail")
|
.expect_err("opening a directory symlink as a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_LOOP,
|
||||||
"errno should be ERRNO_LOOP",
|
wasi::ERRNO_NOTDIR
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to open it with just O_NOFOLLOW.
|
// Try to open it with just O_NOFOLLOW.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
||||||
.expect_err("opening a symlink with O_NOFOLLOW should fail")
|
.expect_err("opening a symlink with O_NOFOLLOW should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_LOOP,
|
||||||
"errno should be ERRNO_LOOP",
|
wasi::ERRNO_ACCES
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to open it as a directory without O_NOFOLLOW.
|
// 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");
|
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink");
|
||||||
|
|
||||||
// Try to open it as a directory with O_NOFOLLOW again.
|
// 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)
|
wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect_err("opening a directory symlink as a directory should fail")
|
.expect_err("opening a directory symlink as a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_LOOP,
|
||||||
"errno should be ERRNO_LOOP",
|
wasi::ERRNO_NOTDIR
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to open it with just O_NOFOLLOW.
|
// Try to open it with just O_NOFOLLOW.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
||||||
.expect_err("opening a symlink with NOFOLLOW should fail")
|
.expect_err("opening a symlink with NOFOLLOW should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_LOOP
|
||||||
"errno should be ERRNO_LOOP",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to open it as a directory without O_NOFOLLOW.
|
// Try to open it as a directory without O_NOFOLLOW.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(
|
wasi::path_open(
|
||||||
dir_fd,
|
dir_fd,
|
||||||
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
|
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")
|
.expect_err("opening a symlink to a file as a directory")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
|
||||||
let mut fdstat = wasi::fd_fdstat_get(dir_fd).expect("fd_fdstat_get");
|
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,
|
0,
|
||||||
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
|
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
|
||||||
);
|
);
|
||||||
assert_ne!(
|
|
||||||
fdstat.fs_rights_inheriting & wasi::RIGHTS_PATH_FILESTAT_GET,
|
let fdflags = if TESTCONFIG.support_fdflags_sync() {
|
||||||
0,
|
wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC
|
||||||
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
|
} else {
|
||||||
);
|
wasi::FDFLAGS_APPEND
|
||||||
|
};
|
||||||
|
|
||||||
// Create a file in the scratch directory.
|
// Create a file in the scratch directory.
|
||||||
let file_fd = wasi::path_open(
|
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,
|
wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET,
|
||||||
0,
|
0,
|
||||||
// Pass some flags for later retrieval
|
// Pass some flags for later retrieval
|
||||||
wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC,
|
fdflags,
|
||||||
)
|
)
|
||||||
.expect("opening a file");
|
.expect("opening a file");
|
||||||
assert_gt!(
|
assert_gt!(
|
||||||
@@ -44,26 +45,50 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
|
|||||||
0,
|
0,
|
||||||
"files shouldn't have rights for path_* syscalls even if manually given",
|
"files shouldn't have rights for path_* syscalls even if manually given",
|
||||||
);
|
);
|
||||||
assert_ne!(
|
assert_eq!(
|
||||||
fdstat.fs_flags & (wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC),
|
fdstat.fs_flags & wasi::FDFLAGS_APPEND,
|
||||||
0,
|
wasi::FDFLAGS_APPEND,
|
||||||
"file should have the same flags used to create the file"
|
"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
|
// Check file size
|
||||||
let mut stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
|
let file_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats");
|
||||||
assert_eq!(stat.size, 0, "file size should be 0");
|
assert_eq!(file_stat.size, 0, "file size should be 0");
|
||||||
|
|
||||||
// Check path_filestat_set_times
|
// 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)
|
wasi::path_filestat_set_times(dir_fd, 0, "file", 0, new_mtim, wasi::FSTFLAGS_MTIM)
|
||||||
.expect("path_filestat_set_times should succeed");
|
.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");
|
.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(
|
wasi::path_filestat_set_times(
|
||||||
dir_fd,
|
dir_fd,
|
||||||
0,
|
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")
|
.expect_err("MTIM and MTIM_NOW can't both be set")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_INVAL,
|
wasi::ERRNO_INVAL
|
||||||
"errno should be ERRNO_INVAL"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// check if the times were untouched
|
// 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");
|
.expect("reading file stats after ERRNO_INVAL fd_filestat_set_times");
|
||||||
assert_eq!(stat.mtim, new_mtim, "mtim should not change");
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
unmodified_file_stat.mtim, new_mtim,
|
||||||
|
"mtim should not change"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalid arguments to set_times:
|
||||||
|
assert_errno!(
|
||||||
wasi::path_filestat_set_times(
|
wasi::path_filestat_set_times(
|
||||||
dir_fd,
|
dir_fd,
|
||||||
0,
|
0,
|
||||||
@@ -94,50 +122,11 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
|
|||||||
)
|
)
|
||||||
.expect_err("ATIM & ATIM_NOW can't both be set")
|
.expect_err("ATIM & ATIM_NOW can't both be set")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_INVAL,
|
wasi::ERRNO_INVAL
|
||||||
"errno should be 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::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, "file").expect("removing a file");
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
|
||||||
}
|
}
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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
|
const TEST_RIGHTS: wasi::Rights = wasi::RIGHTS_FD_READ
|
||||||
| wasi::RIGHTS_PATH_LINK_SOURCE
|
| wasi::RIGHTS_PATH_LINK_SOURCE
|
||||||
@@ -102,141 +102,104 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
|
|||||||
// Create a link to a path that already exists
|
// Create a link to a path that already exists
|
||||||
create_file(dir_fd, "link");
|
create_file(dir_fd, "link");
|
||||||
|
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
|
wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
|
||||||
.expect_err("creating a link to existing path should fail")
|
.expect_err("creating a link to existing path should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
wasi::ERRNO_EXIST
|
||||||
"errno should be ERRNO_EXIST"
|
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "link").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "link").expect("removing a file");
|
||||||
|
|
||||||
// Create a link to itself
|
// Create a link to itself
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_link(dir_fd, 0, "file", dir_fd, "file")
|
wasi::path_link(dir_fd, 0, "file", dir_fd, "file")
|
||||||
.expect_err("creating a link to itself should fail")
|
.expect_err("creating a link to itself should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
wasi::ERRNO_EXIST
|
||||||
"errno should be ERRNO_EXIST"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a link where target is a directory
|
// Create a link where target is a directory
|
||||||
wasi::path_create_directory(dir_fd, "link").expect("creating a dir");
|
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")
|
wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
|
||||||
.expect_err("creating a link where target is a directory should fail")
|
.expect_err("creating a link where target is a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
wasi::ERRNO_EXIST
|
||||||
"errno should be ERRNO_EXIST"
|
|
||||||
);
|
);
|
||||||
wasi::path_remove_directory(dir_fd, "link").expect("removing a dir");
|
wasi::path_remove_directory(dir_fd, "link").expect("removing a dir");
|
||||||
|
|
||||||
// Create a link to a directory
|
// Create a link to a directory
|
||||||
wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
|
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")
|
wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link")
|
||||||
.expect_err("creating a link to a directory should fail")
|
.expect_err("creating a link to a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_PERM,
|
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");
|
wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory");
|
||||||
|
|
||||||
// Create a link to a file with trailing slash
|
// Create a link to a file with trailing slash
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_link(dir_fd, 0, "file", dir_fd, "link/")
|
wasi::path_link(dir_fd, 0, "file", dir_fd, "link/")
|
||||||
.expect_err("creating a link to a file with trailing slash should fail")
|
.expect_err("creating a link to a file with trailing slash should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a link to a dangling symlink
|
if TESTCONFIG.support_dangling_filesystem() {
|
||||||
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
|
// Create a link to a dangling symlink
|
||||||
|
wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
|
||||||
|
|
||||||
// This should succeed, because we're not following symlinks
|
// This should succeed, because we're not following symlinks
|
||||||
wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link")
|
wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link")
|
||||||
.expect("creating a link to a dangling symlink should succeed");
|
.expect("creating a link to a dangling symlink should succeed");
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
||||||
wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
|
wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
|
||||||
|
|
||||||
// Create a link to a symlink loop
|
// Create a link to a symlink loop
|
||||||
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink loop");
|
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink loop");
|
||||||
|
|
||||||
wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link")
|
wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link")
|
||||||
.expect("creating a link to a symlink loop should succeed");
|
.expect("creating a link to a symlink loop should succeed");
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
||||||
wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
|
wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
|
||||||
|
|
||||||
// Create a link where target is a dangling symlink
|
// Create a link where target is a dangling symlink
|
||||||
wasi::path_symlink("target", dir_fd, "symlink").expect("creating 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")
|
wasi::path_link(dir_fd, 0, "file", dir_fd, "symlink")
|
||||||
.expect_err("creating a link where target is a dangling symlink")
|
.expect_err("creating a link where target is a dangling symlink")
|
||||||
|
.raw_error(),
|
||||||
|
wasi::ERRNO_EXIST
|
||||||
|
);
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Symlink following with path_link is rejected
|
||||||
|
assert_errno!(
|
||||||
|
wasi::path_link(
|
||||||
|
dir_fd,
|
||||||
|
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
|
||||||
|
"symlink",
|
||||||
|
dir_fd,
|
||||||
|
"link",
|
||||||
|
)
|
||||||
|
.expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
wasi::ERRNO_INVAL
|
||||||
"errno should be ERRNO_EXIST"
|
);
|
||||||
);
|
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
|
|
||||||
|
|
||||||
// Create a link to a file following symlinks
|
// Clean up.
|
||||||
wasi::path_symlink("file", dir_fd, "symlink").expect("creating a valid symlink");
|
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
||||||
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!(
|
|
||||||
wasi::path_link(
|
|
||||||
dir_fd,
|
|
||||||
wasi::LOOKUPFLAGS_SYMLINK_FOLLOW,
|
|
||||||
"symlink",
|
|
||||||
dir_fd,
|
|
||||||
"link",
|
|
||||||
)
|
|
||||||
.expect_err("creating a link to a dangling symlink following symlinks should fail")
|
|
||||||
.raw_error(),
|
|
||||||
wasi::ERRNO_NOENT,
|
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
|
||||||
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() {
|
fn main() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_open_create_existing(dir_fd: wasi::Fd) {
|
||||||
create_file(dir_fd, "file");
|
create_file(dir_fd, "file");
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(
|
wasi::path_open(
|
||||||
dir_fd,
|
dir_fd,
|
||||||
0,
|
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")
|
.expect_err("trying to create a file that already exists")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
wasi::ERRNO_EXIST
|
||||||
"errno should be ERRNO_EXIST"
|
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_dirfd_not_dir(dir_fd: wasi::Fd) {
|
||||||
// Open a file.
|
// Open a file.
|
||||||
let file_fd =
|
let file_fd =
|
||||||
wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("opening a file");
|
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.
|
// 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)
|
wasi::path_open(file_fd, 0, "foo", wasi::OFLAGS_CREAT, 0, 0, 0)
|
||||||
.expect_err("non-directory base fd should get ERRNO_NOTDIR")
|
.expect_err("non-directory base fd should get ERRNO_NOTDIR")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR"
|
|
||||||
);
|
);
|
||||||
wasi::fd_close(file_fd).expect("closing a file");
|
wasi::fd_close(file_fd).expect("closing a file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_open_missing(dir_fd: wasi::Fd) {
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(
|
wasi::path_open(
|
||||||
dir_fd,
|
dir_fd, 0, "file", 0, // not passing O_CREAT here
|
||||||
0,
|
0, 0, 0,
|
||||||
"file",
|
|
||||||
0, // not passing O_CREAT here
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.expect_err("trying to open a file that doesn't exist")
|
.expect_err("trying to open a file that doesn't exist")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::{env, process};
|
use std::{env, process};
|
||||||
use wasi_tests::open_scratch_directory;
|
use wasi_tests::{assert_errno, create_file, drop_rights, fd_get_rights, open_scratch_directory};
|
||||||
use wasi_tests::{create_file, drop_rights, fd_get_rights};
|
|
||||||
|
|
||||||
const TEST_FILENAME: &'static str = "file";
|
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
|
// Since we no longer have the right to fd_read, trying to read a file
|
||||||
// should be an error.
|
// should be an error.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_read(fd, &[iovec])
|
wasi::fd_read(fd, &[iovec])
|
||||||
.expect_err("reading bytes from file should fail")
|
.expect_err("reading bytes from file should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTCAPABLE,
|
wasi::ERRNO_NOTCAPABLE
|
||||||
"the errno should be ENOTCAPABLE"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_rename(dir_fd: wasi::Fd) {
|
||||||
// First, try renaming a dir to nonexistent path
|
// 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");
|
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory");
|
||||||
|
|
||||||
// Check that source directory doesn't exist anymore
|
// 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)
|
wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect_err("opening a nonexistent path as a directory should fail")
|
.expect_err("opening a nonexistent path as a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that target directory exists
|
// Check that target directory exists
|
||||||
@@ -31,69 +30,89 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
|
|||||||
wasi::fd_close(fd).expect("closing a file");
|
wasi::fd_close(fd).expect("closing a file");
|
||||||
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
||||||
|
|
||||||
// Now, try renaming renaming a dir to existing empty dir
|
// Yes, renaming a dir to an empty dir is a property guaranteed by rename(2)
|
||||||
wasi::path_create_directory(dir_fd, "source").expect("creating a directory");
|
// and its fairly important that it is atomic.
|
||||||
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
// But, we haven't found a way to emulate it on windows. So, sometimes this
|
||||||
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory");
|
// 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
|
// 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)
|
wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect_err("opening a nonexistent path as a directory")
|
.expect_err("opening a nonexistent path as a directory")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Check that target directory exists
|
// Check that target directory exists
|
||||||
fd = wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
fd = wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
|
||||||
.expect("opening renamed path as a directory");
|
.expect("opening renamed path as a directory");
|
||||||
assert_gt!(
|
assert_gt!(
|
||||||
fd,
|
fd,
|
||||||
libc::STDERR_FILENO as wasi::Fd,
|
libc::STDERR_FILENO as wasi::Fd,
|
||||||
"file descriptor range check",
|
"file descriptor range check",
|
||||||
);
|
);
|
||||||
|
|
||||||
wasi::fd_close(fd).expect("closing a file");
|
wasi::fd_close(fd).expect("closing a file");
|
||||||
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
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
|
// 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, "source").expect("creating a directory");
|
||||||
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
||||||
create_file(dir_fd, "target/file");
|
create_file(dir_fd, "target/file");
|
||||||
|
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_rename(dir_fd, "source", dir_fd, "target")
|
wasi::path_rename(dir_fd, "source", dir_fd, "target")
|
||||||
.expect_err("renaming directory to a nonempty directory")
|
.expect_err("renaming directory to a nonempty directory")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTEMPTY,
|
windows => wasi::ERRNO_ACCES,
|
||||||
"errno should be ERRNO_NOTEMPTY"
|
unix => wasi::ERRNO_NOTEMPTY
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try renaming dir to a file
|
// This is technically a different property, but the root of these divergent behaviors is in
|
||||||
assert_eq!(
|
// 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_errno!(
|
||||||
|
wasi::path_rename(dir_fd, "source", dir_fd, "target/file")
|
||||||
|
.expect_err("renaming a directory to a file")
|
||||||
|
.raw_error(),
|
||||||
|
wasi::ERRNO_NOTDIR
|
||||||
|
);
|
||||||
|
wasi::path_unlink_file(dir_fd, "target/file").expect("removing a file");
|
||||||
|
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")
|
wasi::path_rename(dir_fd, "source", dir_fd, "target/file")
|
||||||
.expect_err("renaming a directory to a file")
|
.expect("windows happens to support renaming a directory to a file");
|
||||||
.raw_error(),
|
wasi::path_remove_directory(dir_fd, "target/file").expect("removing a file");
|
||||||
wasi::ERRNO_NOTDIR,
|
}
|
||||||
"errno should be 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, "target").expect("removing a directory");
|
||||||
wasi::path_remove_directory(dir_fd, "source").expect("removing a directory");
|
|
||||||
|
|
||||||
// Now, try renaming a file to a nonexistent path
|
// Now, try renaming a file to a nonexistent path
|
||||||
create_file(dir_fd, "source");
|
create_file(dir_fd, "source");
|
||||||
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a file");
|
wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a file");
|
||||||
|
|
||||||
// Check that source file doesn't exist anymore
|
// Check that source file doesn't exist anymore
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
|
||||||
.expect_err("opening a nonexistent path should fail")
|
.expect_err("opening a nonexistent path should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that target file exists
|
// 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");
|
.expect("renaming file to another existing file");
|
||||||
|
|
||||||
// Check that source file doesn't exist anymore
|
// Check that source file doesn't exist anymore
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
|
||||||
.expect_err("opening a nonexistent path")
|
.expect_err("opening a nonexistent path")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOENT,
|
wasi::ERRNO_NOENT
|
||||||
"errno should be ERRNO_NOENT"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that target file exists
|
// Check that target file exists
|
||||||
@@ -138,12 +156,12 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
|
|||||||
create_file(dir_fd, "source");
|
create_file(dir_fd, "source");
|
||||||
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
||||||
|
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_rename(dir_fd, "source", dir_fd, "target")
|
wasi::path_rename(dir_fd, "source", dir_fd, "target")
|
||||||
.expect_err("renaming a file to existing directory should fail")
|
.expect_err("renaming a file to existing directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_ISDIR,
|
windows => wasi::ERRNO_ACCES,
|
||||||
"errno should be ERRNO_ISDIR"
|
unix => wasi::ERRNO_ISDIR
|
||||||
);
|
);
|
||||||
|
|
||||||
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -1,42 +1,33 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi::Fd) {
|
||||||
// Test renaming a file with a trailing slash in the name.
|
// Test renaming a file with a trailing slash in the name.
|
||||||
create_file(dir_fd, "source");
|
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")
|
wasi::path_rename(dir_fd, "source/", dir_fd, "target")
|
||||||
.expect_err("renaming a file with a trailing slash in the source name should fail")
|
.expect_err("renaming a file with a trailing slash in the source name should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR"
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_rename(dir_fd, "source", dir_fd, "target/")
|
wasi::path_rename(dir_fd, "source", dir_fd, "target/")
|
||||||
.expect_err("renaming a file with a trailing slash in the destination name should fail")
|
.expect_err("renaming a file with a trailing slash in the destination name should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR"
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_rename(dir_fd, "source/", dir_fd, "target/")
|
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")
|
.expect_err("renaming a file with a trailing slash in the source and destination names should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR"
|
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "source").expect("removing a file");
|
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() {
|
fn main() {
|
||||||
@@ -1,63 +1,65 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
|
||||||
// Link destination shouldn't end with a slash.
|
if TESTCONFIG.support_dangling_filesystem() {
|
||||||
assert_eq!(
|
// Dangling symlink: Link destination shouldn't end with a slash.
|
||||||
wasi::path_symlink("source", dir_fd, "target/")
|
assert_errno!(
|
||||||
.expect_err("link destination ending with a slash should fail")
|
wasi::path_symlink("source", dir_fd, "target/")
|
||||||
.raw_error(),
|
.expect_err("link destination ending with a slash should fail")
|
||||||
wasi::ERRNO_NOENT,
|
.raw_error(),
|
||||||
"errno should be ERRNO_NOENT"
|
wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
|
|
||||||
// Without the trailing slash, this should succeed.
|
// 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_symlink("source", dir_fd, "target")
|
||||||
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
.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.
|
// Link destination already exists, target has trailing slash.
|
||||||
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_symlink("source", dir_fd, "target/")
|
wasi::path_symlink("source", dir_fd, "target/")
|
||||||
.expect_err("link destination already exists")
|
.expect_err("link destination already exists")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
unix => wasi::ERRNO_EXIST,
|
||||||
"errno should be ERRNO_EXIST"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
||||||
|
|
||||||
// Link destination already exists, target has no trailing slash.
|
// Link destination already exists, target has no trailing slash.
|
||||||
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_symlink("source", dir_fd, "target")
|
wasi::path_symlink("source", dir_fd, "target")
|
||||||
.expect_err("link destination already exists")
|
.expect_err("link destination already exists")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
unix => wasi::ERRNO_EXIST,
|
||||||
"errno should be ERRNO_EXIST"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
wasi::path_remove_directory(dir_fd, "target").expect("removing a directory");
|
||||||
|
|
||||||
// Link destination already exists, target has trailing slash.
|
// Link destination already exists, target has trailing slash.
|
||||||
create_file(dir_fd, "target");
|
create_file(dir_fd, "target");
|
||||||
|
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_symlink("source", dir_fd, "target/")
|
wasi::path_symlink("source", dir_fd, "target/")
|
||||||
.expect_err("link destination already exists")
|
.expect_err("link destination already exists")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
unix => wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_EXIST"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
||||||
|
|
||||||
// Link destination already exists, target has no trailing slash.
|
// Link destination already exists, target has no trailing slash.
|
||||||
create_file(dir_fd, "target");
|
create_file(dir_fd, "target");
|
||||||
|
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_symlink("source", dir_fd, "target")
|
wasi::path_symlink("source", dir_fd, "target")
|
||||||
.expect_err("link destination already exists")
|
.expect_err("link destination already exists")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_EXIST,
|
unix => wasi::ERRNO_EXIST,
|
||||||
"errno should be ERRNO_EXIST"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, mem::MaybeUninit, process};
|
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;
|
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();
|
let mut out: Vec<wasi::Event> = Vec::new();
|
||||||
out.resize_with(r#in.len(), || {
|
out.resize_with(r#in.len(), || {
|
||||||
MaybeUninit::<wasi::Event>::zeroed().assume_init()
|
MaybeUninit::<wasi::Event>::zeroed().assume_init()
|
||||||
});
|
});
|
||||||
let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
|
let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?;
|
||||||
.expect("poll_oneoff should succeed");
|
out.truncate(size);
|
||||||
assert_eq!(
|
Ok(out)
|
||||||
size, nexpected,
|
|
||||||
"poll_oneoff should return {} events",
|
|
||||||
nexpected
|
|
||||||
);
|
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn test_empty_poll() {
|
unsafe fn test_empty_poll() {
|
||||||
let r#in = [];
|
let r#in = [];
|
||||||
let mut out: Vec<wasi::Event> = Vec::new();
|
let mut out: Vec<wasi::Event> = Vec::new();
|
||||||
let error = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
|
assert_errno!(
|
||||||
.expect_err("empty poll_oneoff should fail");
|
wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
|
||||||
assert_eq!(
|
.expect_err("empty poll_oneoff should fail")
|
||||||
error.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_INVAL,
|
wasi::ERRNO_INVAL
|
||||||
"error should be EINVAL"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,13 +39,10 @@ unsafe fn test_timeout() {
|
|||||||
u: wasi::SubscriptionUU { clock },
|
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];
|
let event = &out[0];
|
||||||
assert_eq!(
|
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
|
||||||
event.error,
|
|
||||||
wasi::ERRNO_SUCCESS,
|
|
||||||
"the event.error should be set to ESUCCESS"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
event.r#type,
|
event.r#type,
|
||||||
wasi::EVENTTYPE_CLOCK,
|
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) {
|
unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
|
||||||
let fd_readwrite = wasi::SubscriptionFdReadwrite {
|
let fd_readwrite = wasi::SubscriptionFdReadwrite {
|
||||||
file_descriptor: fd,
|
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!(
|
assert_eq!(
|
||||||
out[0].userdata, 1,
|
out[0].userdata, 1,
|
||||||
"the event.userdata should contain fd userdata specified by the user"
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_errno!(out[0].error, error_code);
|
||||||
out[0].error, error_code,
|
|
||||||
"the event.error should be set to {}",
|
|
||||||
error_code
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out[0].r#type,
|
out[0].r#type,
|
||||||
wasi::EVENTTYPE_FD_READ,
|
wasi::EVENTTYPE_FD_READ,
|
||||||
@@ -211,11 +94,7 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
|
|||||||
out[1].userdata, 2,
|
out[1].userdata, 2,
|
||||||
"the event.userdata should contain fd userdata specified by the user"
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_errno!(out[1].error, error_code);
|
||||||
out[1].error, error_code,
|
|
||||||
"the event.error should be set to {}",
|
|
||||||
error_code
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out[1].r#type,
|
out[1].r#type,
|
||||||
wasi::EVENTTYPE_FD_WRITE,
|
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() {
|
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) {
|
unsafe fn test_poll_oneoff(dir_fd: wasi::Fd) {
|
||||||
test_timeout();
|
test_timeout();
|
||||||
test_empty_poll();
|
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_valid_fd(dir_fd);
|
||||||
test_fd_readwrite_invalid_fd();
|
test_fd_readwrite_invalid_fd();
|
||||||
}
|
}
|
||||||
|
|||||||
128
crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs
Normal file
128
crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs
Normal 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() }
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_readlink(dir_fd: wasi::Fd) {
|
||||||
// Create a file in the scratch directory.
|
// Create a file in the scratch directory.
|
||||||
@@ -10,7 +10,7 @@ unsafe fn test_readlink(dir_fd: wasi::Fd) {
|
|||||||
|
|
||||||
// Read link into the buffer
|
// Read link into the buffer
|
||||||
let buf = &mut [0u8; 10];
|
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");
|
.expect("readlink should succeed");
|
||||||
assert_eq!(bufused, 6, "should use 6 bytes of the buffer");
|
assert_eq!(bufused, 6, "should use 6 bytes of the buffer");
|
||||||
assert_eq!(&buf[..6], b"target", "buffer should contain 'target'");
|
assert_eq!(&buf[..6], 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
|
// Read link into smaller buffer than the actual link's length
|
||||||
let buf = &mut [0u8; 4];
|
let buf = &mut [0u8; 4];
|
||||||
bufused = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
|
let err = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
|
||||||
.expect("readlink should succeed");
|
.err()
|
||||||
assert_eq!(bufused, 4, "should use all 4 bytes of the buffer");
|
.expect("readlink with too-small buffer should fail");
|
||||||
assert_eq!(buf, b"targ", "buffer should contain 'targ'");
|
assert_errno!(err.raw_error(), wasi::ERRNO_RANGE);
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "target").expect("removing a 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) }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
|
||||||
// Create a directory in the scratch directory.
|
// 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");
|
create_file(dir_fd, "file");
|
||||||
|
|
||||||
// Test that removing it with no trailing slash fails.
|
// Test that removing it with no trailing slash fails.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_remove_directory(dir_fd, "file")
|
wasi::path_remove_directory(dir_fd, "file")
|
||||||
.expect_err("remove_directory without a trailing slash on a file should fail")
|
.expect_err("remove_directory without a trailing slash on a file should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
wasi::ERRNO_NOTDIR
|
||||||
"errno should be ERRNO_NOTDIR"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test that removing it with a trailing slash fails.
|
// Test that removing it with a trailing slash fails.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_remove_directory(dir_fd, "file/")
|
wasi::path_remove_directory(dir_fd, "file/")
|
||||||
.expect_err("remove_directory with a trailing slash on a file should fail")
|
.expect_err("remove_directory with a trailing slash on a file should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
unix => wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_NOTDIR"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
|
|
||||||
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_remove_nonempty_directory(dir_fd: wasi::Fd) {
|
||||||
// Create a directory in the scratch directory.
|
// 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");
|
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.
|
// Test that attempting to unlink the first directory returns the expected error code.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_remove_directory(dir_fd, "dir")
|
wasi::path_remove_directory(dir_fd, "dir")
|
||||||
.expect_err("remove_directory on a directory should return ENOTEMPTY")
|
.expect_err("remove_directory on a directory should return ENOTEMPTY")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTEMPTY,
|
wasi::ERRNO_NOTEMPTY
|
||||||
"errno should be ERRNO_NOTEMPTY",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Removing the directories.
|
// Removing the directories.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use more_asserts::assert_gt;
|
use more_asserts::assert_gt;
|
||||||
use std::{env, process};
|
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) {
|
unsafe fn test_renumber(dir_fd: wasi::Fd) {
|
||||||
let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as 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");
|
wasi::fd_renumber(fd_from, fd_to).expect("renumbering two descriptors");
|
||||||
|
|
||||||
// Ensure that fd_from is closed
|
// Ensure that fd_from is closed
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::fd_close(fd_from)
|
wasi::fd_close(fd_from)
|
||||||
.expect_err("closing already closed file descriptor")
|
.expect_err("closing already closed file descriptor")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_BADF,
|
wasi::ERRNO_BADF
|
||||||
"errno should be ERRNO_BADF"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that fd_to is still open.
|
// Ensure that fd_to is still open.
|
||||||
|
|||||||
93
crates/test-programs/wasi-tests/src/bin/symlink_create.rs
Normal file
93
crates/test-programs/wasi-tests/src/bin/symlink_create.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
110
crates/test-programs/wasi-tests/src/bin/symlink_filestat.rs
Normal file
110
crates/test-programs/wasi-tests/src/bin/symlink_filestat.rs
Normal 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) }
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_symlink_loop(dir_fd: wasi::Fd) {
|
||||||
// Create a self-referencing symlink.
|
if TESTCONFIG.support_dangling_filesystem() {
|
||||||
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink");
|
// Create a self-referencing symlink.
|
||||||
|
wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink");
|
||||||
|
|
||||||
// Try to open it.
|
// Try to open it.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
|
||||||
.expect_err("opening a self-referencing symlink")
|
.expect_err("opening a self-referencing symlink")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_LOOP,
|
wasi::ERRNO_LOOP
|
||||||
"errno should be ERRNO_LOOP",
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file");
|
wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_truncation_rights(dir_fd: wasi::Fd) {
|
||||||
// Create a file in the scratch directory.
|
// 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
|
// Test that we can't truncate the file without the
|
||||||
// wasi_unstable::RIGHT_PATH_FILESTAT_SET_SIZE right.
|
// 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)
|
wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0)
|
||||||
.expect_err("truncating a file without path_filestat_set_size right")
|
.expect_err("truncating a file without path_filestat_set_size right")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTCAPABLE,
|
wasi::ERRNO_NOTCAPABLE
|
||||||
"errno should be ERRNO_NOTCAPABLE",
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
use std::{env, process};
|
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) {
|
unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
|
||||||
// Create a directory in the scratch directory.
|
// Create a directory in the scratch directory.
|
||||||
wasi::path_create_directory(dir_fd, "dir").expect("creating a directory");
|
wasi::path_create_directory(dir_fd, "dir").expect("creating a directory");
|
||||||
|
|
||||||
// Test that unlinking it fails.
|
// Test that unlinking it fails.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_unlink_file(dir_fd, "dir")
|
wasi::path_unlink_file(dir_fd, "dir")
|
||||||
.expect_err("unlink_file on a directory should fail")
|
.expect_err("unlink_file on a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_ISDIR,
|
macos => wasi::ERRNO_PERM,
|
||||||
"errno should be ERRNO_ISDIR"
|
unix => wasi::ERRNO_ISDIR,
|
||||||
|
windows => wasi::ERRNO_ACCES
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test that unlinking it with a trailing flash fails.
|
// Test that unlinking it with a trailing flash fails.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_unlink_file(dir_fd, "dir/")
|
wasi::path_unlink_file(dir_fd, "dir/")
|
||||||
.expect_err("unlink_file on a directory should fail")
|
.expect_err("unlink_file on a directory should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_ISDIR,
|
macos => wasi::ERRNO_PERM,
|
||||||
"errno should be ERRNO_ISDIR"
|
unix => wasi::ERRNO_ISDIR,
|
||||||
|
windows => wasi::ERRNO_ACCES
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
@@ -30,12 +32,12 @@ unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
|
|||||||
create_file(dir_fd, "file");
|
create_file(dir_fd, "file");
|
||||||
|
|
||||||
// Test that unlinking it with a trailing flash fails.
|
// Test that unlinking it with a trailing flash fails.
|
||||||
assert_eq!(
|
assert_errno!(
|
||||||
wasi::path_unlink_file(dir_fd, "file/")
|
wasi::path_unlink_file(dir_fd, "file/")
|
||||||
.expect_err("unlink_file with a trailing slash should fail")
|
.expect_err("unlink_file with a trailing slash should fail")
|
||||||
.raw_error(),
|
.raw_error(),
|
||||||
wasi::ERRNO_NOTDIR,
|
unix => wasi::ERRNO_NOTDIR,
|
||||||
"errno should be ERRNO_NOTDIR"
|
windows => wasi::ERRNO_NOENT
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test that unlinking it with no trailing flash succeeds.
|
// Test that unlinking it with no trailing flash succeeds.
|
||||||
|
|||||||
69
crates/test-programs/wasi-tests/src/config.rs
Normal file
69
crates/test-programs/wasi-tests/src/config.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
use more_asserts::assert_gt;
|
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
|
// The `wasi` crate version 0.9.0 and beyond, doesn't
|
||||||
// seem to define these constants, so we do it ourselves.
|
// 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");
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ keywords = ["webassembly", "wasm"]
|
|||||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2018"
|
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
|
# 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
|
# vars like `DEP_WASI_COMMON_19_*` for crates that have build scripts and depend
|
||||||
@@ -19,21 +20,17 @@ links = "wasi-common-19"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
thiserror = "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" }
|
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
|
||||||
tracing = "0.1.19"
|
tracing = "0.1.19"
|
||||||
|
cap-std = "0.13"
|
||||||
|
cap-rand = "0.13"
|
||||||
|
bitflags = "1.2"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
yanix = { path = "yanix", version = "0.22.0" }
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winx = { path = "winx", version = "0.22.0" }
|
|
||||||
winapi = "0.3"
|
winapi = "0.3"
|
||||||
cpu-time = "1.0"
|
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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
|
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.
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
let cwd = std::env::current_dir().unwrap();
|
let cwd = std::env::current_dir().unwrap();
|
||||||
let wasi = cwd.join("WASI");
|
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());
|
println!("cargo:wasi={}", wasi.display());
|
||||||
|
// and available to our own crate as WASI_ROOT:
|
||||||
println!("cargo:rustc-env=WASI_ROOT={}", wasi.display());
|
println!("cargo:rustc-env=WASI_ROOT={}", wasi.display());
|
||||||
}
|
}
|
||||||
|
|||||||
35
crates/wasi-common/cap-std-sync/Cargo.toml
Normal file
35
crates/wasi-common/cap-std-sync/Cargo.toml
Normal 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"
|
||||||
46
crates/wasi-common/cap-std-sync/src/clocks.rs
Normal file
46
crates/wasi-common/cap-std-sync/src/clocks.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
332
crates/wasi-common/cap-std-sync/src/dir.rs
Normal file
332
crates/wasi-common/cap-std-sync/src/dir.rs
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
212
crates/wasi-common/cap-std-sync/src/file.rs
Normal file
212
crates/wasi-common/cap-std-sync/src/file.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
128
crates/wasi-common/cap-std-sync/src/lib.rs
Normal file
128
crates/wasi-common/cap-std-sync/src/lib.rs
Normal 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() }))
|
||||||
|
}
|
||||||
15
crates/wasi-common/cap-std-sync/src/sched.rs
Normal file
15
crates/wasi-common/cap-std-sync/src/sched.rs
Normal 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())
|
||||||
|
}
|
||||||
190
crates/wasi-common/cap-std-sync/src/sched/unix.rs
Normal file
190
crates/wasi-common/cap-std-sync/src/sched/unix.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
256
crates/wasi-common/cap-std-sync/src/sched/windows.rs
Normal file
256
crates/wasi-common/cap-std-sync/src/sched/windows.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
217
crates/wasi-common/cap-std-sync/src/stdio.rs
Normal file
217
crates/wasi-common/cap-std-sync/src/stdio.rs
Normal 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);
|
||||||
22
crates/wasi-common/src/clocks.rs
Normal file
22
crates/wasi-common/src/clocks.rs
Normal 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,
|
||||||
|
}
|
||||||
@@ -1,433 +1,133 @@
|
|||||||
use crate::entry::{Entry, EntryHandle};
|
use crate::clocks::WasiClocks;
|
||||||
use crate::fdpool::FdPool;
|
use crate::dir::{DirCaps, DirEntry, WasiDir};
|
||||||
use crate::handle::Handle;
|
use crate::file::{FileCaps, FileEntry, WasiFile};
|
||||||
use crate::string_array::{PendingString, StringArray, StringArrayError};
|
use crate::sched::WasiSched;
|
||||||
use crate::sys::osdir::OsDir;
|
use crate::string_array::{StringArray, StringArrayError};
|
||||||
use crate::sys::stdio::NullDevice;
|
use crate::table::Table;
|
||||||
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
|
|
||||||
use crate::virtfs::{VirtualDir, VirtualDirEntry};
|
|
||||||
use crate::wasi::types::Fd;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use std::borrow::Borrow;
|
use cap_rand::RngCore;
|
||||||
use std::cell::RefCell;
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
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 {
|
pub struct WasiCtx {
|
||||||
entries: RefCell<EntryTable>,
|
pub args: StringArray,
|
||||||
pub(crate) args: StringArray,
|
pub env: StringArray,
|
||||||
pub(crate) env: StringArray,
|
pub random: RefCell<Box<dyn RngCore>>,
|
||||||
|
pub clocks: WasiClocks,
|
||||||
|
pub sched: Box<dyn WasiSched>,
|
||||||
|
pub table: Rc<RefCell<Table>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WasiCtx {
|
impl WasiCtx {
|
||||||
/// Make a new `WasiCtx` with some default settings.
|
pub fn builder(
|
||||||
///
|
random: RefCell<Box<dyn RngCore>>,
|
||||||
/// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process.
|
clocks: WasiClocks,
|
||||||
///
|
sched: Box<dyn WasiSched>,
|
||||||
/// - Environment variables are inherited from the host process.
|
table: Rc<RefCell<Table>>,
|
||||||
///
|
) -> WasiCtxBuilder {
|
||||||
/// To override these behaviors, use `WasiCtxBuilder`.
|
WasiCtxBuilder(WasiCtx {
|
||||||
pub fn new<S: AsRef<[u8]>>(args: impl IntoIterator<Item = S>) -> WasiCtxBuilderResult<Self> {
|
args: StringArray::new(),
|
||||||
WasiCtxBuilder::new()
|
env: StringArray::new(),
|
||||||
.args(args)
|
random,
|
||||||
.inherit_stdio()
|
clocks,
|
||||||
.inherit_env()
|
sched,
|
||||||
.build()
|
table,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
|
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
|
||||||
pub(crate) fn contains_entry(&self, fd: Fd) -> bool {
|
self.table()
|
||||||
self.entries.borrow().contains(&fd)
|
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an immutable `Entry` corresponding to the specified raw WASI `fd`.
|
pub fn insert_dir(
|
||||||
pub(crate) fn get_entry(&self, fd: Fd) -> Result<Rc<Entry>, Error> {
|
&self,
|
||||||
match self.entries.borrow().get(&fd) {
|
fd: u32,
|
||||||
Some(entry) => Ok(entry),
|
dir: Box<dyn WasiDir>,
|
||||||
None => Err(Error::Badf),
|
caps: DirCaps,
|
||||||
}
|
file_caps: FileCaps,
|
||||||
|
path: PathBuf,
|
||||||
|
) {
|
||||||
|
self.table().insert_at(
|
||||||
|
fd,
|
||||||
|
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert the specified `Entry` into the `WasiCtx` object.
|
pub fn table(&self) -> RefMut<Table> {
|
||||||
///
|
self.table.borrow_mut()
|
||||||
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arg(mut self, arg: &str) -> Result<Self, StringArrayError> {
|
||||||
|
self.0.args.push(arg.to_owned())?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env(mut self, var: &str, value: &str) -> Result<Self, StringArrayError> {
|
||||||
|
self.0.env.push(format!("{}={}", var, value))?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(crate) fn args(&self) -> &impl StringArrayWriter {
|
|
||||||
&self.args
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn env(&self) -> &impl StringArrayWriter {
|
|
||||||
&self.env
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
184
crates/wasi-common/src/dir.rs
Normal file
184
crates/wasi-common/src/dir.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +1,47 @@
|
|||||||
use cfg_if::cfg_if;
|
//! `wasi_common::Error` is now `anyhow::Error`.
|
||||||
use thiserror::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.
|
/// 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
|
/// 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.
|
/// the crate. Not all values are represented presently.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum ErrorKind {
|
||||||
#[error("Wiggle GuestError: {0}")]
|
/// Errno::Noent: No such file or directory
|
||||||
Guest(#[from] wiggle::GuestError),
|
#[error("Noent: No such file or directory")]
|
||||||
#[error("TryFromIntError: {0}")]
|
Noent,
|
||||||
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:
|
|
||||||
/// Errno::TooBig: Argument list too long
|
/// Errno::TooBig: Argument list too long
|
||||||
#[error("TooBig: Argument list too long")]
|
#[error("TooBig: Argument list too long")]
|
||||||
TooBig,
|
TooBig,
|
||||||
/// Errno::Acces: Permission denied
|
|
||||||
#[error("Acces: Permission denied")]
|
|
||||||
Acces,
|
|
||||||
/// Errno::Badf: Bad file descriptor
|
/// Errno::Badf: Bad file descriptor
|
||||||
#[error("Badf: Bad file descriptor")]
|
#[error("Badf: Bad file descriptor")]
|
||||||
Badf,
|
Badf,
|
||||||
/// Errno::Busy: Device or resource busy
|
|
||||||
#[error("Busy: Device or resource busy")]
|
|
||||||
Busy,
|
|
||||||
/// Errno::Exist: File exists
|
/// Errno::Exist: File exists
|
||||||
#[error("Exist: File exists")]
|
#[error("Exist: File exists")]
|
||||||
Exist,
|
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
|
/// Errno::Ilseq: Illegal byte sequence
|
||||||
#[error("Ilseq: Illegal byte sequence")]
|
#[error("Ilseq: Illegal byte sequence")]
|
||||||
Ilseq,
|
Ilseq,
|
||||||
@@ -56,144 +49,93 @@ pub enum Error {
|
|||||||
#[error("Inval: Invalid argument")]
|
#[error("Inval: Invalid argument")]
|
||||||
Inval,
|
Inval,
|
||||||
/// Errno::Io: I/O error
|
/// Errno::Io: I/O error
|
||||||
#[error("Io: I/o error")]
|
#[error("Io: I/O error")]
|
||||||
Io,
|
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
|
/// Errno::Nametoolong: Filename too long
|
||||||
#[error("Nametoolong: Filename too long")]
|
#[error("Nametoolong: Filename too long")]
|
||||||
Nametoolong,
|
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.
|
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
|
||||||
#[error("Notdir: Not a directory or a symbolic link to a directory")]
|
#[error("Notdir: Not a directory or a symbolic link to a directory")]
|
||||||
Notdir,
|
Notdir,
|
||||||
/// Errno::Notempty: Directory not empty.
|
|
||||||
#[error("Notempty: Directory not empty")]
|
|
||||||
Notempty,
|
|
||||||
/// Errno::Notsup: Not supported, or operation not supported on socket.
|
/// Errno::Notsup: Not supported, or operation not supported on socket.
|
||||||
#[error("Notsup: Not supported, or operation not supported on socket")]
|
#[error("Notsup: Not supported, or operation not supported on socket")]
|
||||||
Notsup,
|
Notsup,
|
||||||
/// Errno::Overflow: Value too large to be stored in data type.
|
/// Errno::Overflow: Value too large to be stored in data type.
|
||||||
#[error("Overflow: Value too large to be stored in data type")]
|
#[error("Overflow: Value too large to be stored in data type")]
|
||||||
Overflow,
|
Overflow,
|
||||||
/// Errno::Pipe: Broken pipe
|
/// Errno::Range: Result too large
|
||||||
#[error("Pipe: Broken pipe")]
|
#[error("Range: Result too large")]
|
||||||
Pipe,
|
Range,
|
||||||
/// Errno::Perm: Operation not permitted
|
|
||||||
#[error("Perm: Operation not permitted")]
|
|
||||||
Perm,
|
|
||||||
/// Errno::Spipe: Invalid seek
|
/// Errno::Spipe: Invalid seek
|
||||||
#[error("Spipe: Invalid seek")]
|
#[error("Spipe: Invalid seek")]
|
||||||
Spipe,
|
Spipe,
|
||||||
/// Errno::Notcapable: Extension: Capabilities insufficient
|
/// Errno::NotCapable: Not capable
|
||||||
#[error("Notcapable: cabailities insufficient")]
|
#[error("Not capable")]
|
||||||
Notcapable,
|
NotCapable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::convert::Infallible> for Error {
|
pub trait ErrorExt {
|
||||||
fn from(_err: std::convert::Infallible) -> Self {
|
fn trap(msg: impl Into<String>) -> Self;
|
||||||
unreachable!("should be impossible: From<Infallible>")
|
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
|
impl ErrorExt for Error {
|
||||||
cfg_if! {
|
fn trap(msg: impl Into<String>) -> Self {
|
||||||
if #[cfg(windows)] {
|
anyhow::anyhow!(msg.into())
|
||||||
use winapi::shared::winerror;
|
}
|
||||||
use std::io;
|
fn not_found() -> Self {
|
||||||
impl From<io::Error> for Error {
|
ErrorKind::Noent.into()
|
||||||
fn from(err: io::Error) -> Self {
|
}
|
||||||
match err.raw_os_error() {
|
fn too_big() -> Self {
|
||||||
Some(code) => match code as u32 {
|
ErrorKind::TooBig.into()
|
||||||
winerror::ERROR_BAD_ENVIRONMENT => Self::TooBig,
|
}
|
||||||
winerror::ERROR_FILE_NOT_FOUND => Self::Noent,
|
fn badf() -> Self {
|
||||||
winerror::ERROR_PATH_NOT_FOUND => Self::Noent,
|
ErrorKind::Badf.into()
|
||||||
winerror::ERROR_TOO_MANY_OPEN_FILES => Self::Nfile,
|
}
|
||||||
winerror::ERROR_ACCESS_DENIED => Self::Acces,
|
fn exist() -> Self {
|
||||||
winerror::ERROR_SHARING_VIOLATION => Self::Acces,
|
ErrorKind::Exist.into()
|
||||||
winerror::ERROR_PRIVILEGE_NOT_HELD => Self::Notcapable,
|
}
|
||||||
winerror::ERROR_INVALID_HANDLE => Self::Badf,
|
fn illegal_byte_sequence() -> Self {
|
||||||
winerror::ERROR_INVALID_NAME => Self::Noent,
|
ErrorKind::Ilseq.into()
|
||||||
winerror::ERROR_NOT_ENOUGH_MEMORY => Self::Nomem,
|
}
|
||||||
winerror::ERROR_OUTOFMEMORY => Self::Nomem,
|
fn invalid_argument() -> Self {
|
||||||
winerror::ERROR_DIR_NOT_EMPTY => Self::Notempty,
|
ErrorKind::Inval.into()
|
||||||
winerror::ERROR_NOT_READY => Self::Busy,
|
}
|
||||||
winerror::ERROR_BUSY => Self::Busy,
|
fn io() -> Self {
|
||||||
winerror::ERROR_NOT_SUPPORTED => Self::Notsup,
|
ErrorKind::Io.into()
|
||||||
winerror::ERROR_FILE_EXISTS => Self::Exist,
|
}
|
||||||
winerror::ERROR_BROKEN_PIPE => Self::Pipe,
|
fn name_too_long() -> Self {
|
||||||
winerror::ERROR_BUFFER_OVERFLOW => Self::Nametoolong,
|
ErrorKind::Nametoolong.into()
|
||||||
winerror::ERROR_NOT_A_REPARSE_POINT => Self::Inval,
|
}
|
||||||
winerror::ERROR_NEGATIVE_SEEK => Self::Inval,
|
fn not_dir() -> Self {
|
||||||
winerror::ERROR_DIRECTORY => Self::Notdir,
|
ErrorKind::Notdir.into()
|
||||||
winerror::ERROR_ALREADY_EXISTS => Self::Exist,
|
}
|
||||||
_ => Self::UnexpectedIo(err),
|
fn not_supported() -> Self {
|
||||||
},
|
ErrorKind::Notsup.into()
|
||||||
None => Self::UnexpectedIo(err),
|
}
|
||||||
}
|
fn overflow() -> Self {
|
||||||
}
|
ErrorKind::Overflow.into()
|
||||||
}
|
}
|
||||||
|
fn range() -> Self {
|
||||||
} else {
|
ErrorKind::Range.into()
|
||||||
use std::io;
|
}
|
||||||
impl From<io::Error> for Error {
|
fn seek_pipe() -> Self {
|
||||||
fn from(err: io::Error) -> Self {
|
ErrorKind::Spipe.into()
|
||||||
match err.raw_os_error() {
|
}
|
||||||
Some(code) => match code {
|
fn not_capable() -> Self {
|
||||||
libc::EPIPE => Self::Pipe,
|
ErrorKind::NotCapable.into()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
177
crates/wasi-common/src/file.rs
Normal file
177
crates/wasi-common/src/file.rs
Normal 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,
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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::*;
|
|
||||||
@@ -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?
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,45 +1,73 @@
|
|||||||
#![deny(
|
//! ## The `WasiFile` and `WasiDir` traits
|
||||||
// missing_docs,
|
//!
|
||||||
trivial_numeric_casts,
|
//! The WASI specification only defines one `handle` type, `fd`, on which all
|
||||||
unused_extern_crates,
|
//! operations on both files and directories (aka dirfds) are defined. We
|
||||||
unstable_features,
|
//! believe this is a design mistake, and are architecting wasi-common to make
|
||||||
clippy::use_self
|
//! this straightforward to correct in future snapshots of WASI. Wasi-common
|
||||||
)]
|
//! internally treats files and directories as two distinct resource types in
|
||||||
#![warn(unused_import_braces)]
|
//! the table - `Box<dyn WasiFile>` and `Box<dyn WasiDir>`. The snapshot 0 and
|
||||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
|
//! 1 interfaces via `fd` will attempt to downcast a table element to one or
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
//! both of these interfaces depending on what is appropriate - e.g.
|
||||||
#![cfg_attr(
|
//! `fd_close` operates on both files and directories, `fd_read` only operates
|
||||||
feature = "cargo-clippy",
|
//! on files, and `fd_readdir` only operates on directories.
|
||||||
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 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 ctx;
|
||||||
mod entry;
|
pub mod dir;
|
||||||
mod error;
|
mod error;
|
||||||
mod fdpool;
|
pub mod file;
|
||||||
pub mod fs;
|
pub mod pipe;
|
||||||
mod handle;
|
pub mod random;
|
||||||
mod path;
|
pub mod sched;
|
||||||
mod sandboxed_tty_writer;
|
|
||||||
pub(crate) mod sched;
|
|
||||||
pub mod snapshots;
|
pub mod snapshots;
|
||||||
mod string_array;
|
mod string_array;
|
||||||
mod sys;
|
pub mod table;
|
||||||
pub mod virtfs;
|
|
||||||
pub mod wasi;
|
|
||||||
|
|
||||||
pub use ctx::{WasiCtx, WasiCtxBuilder, WasiCtxBuilderError};
|
pub use cap_rand::RngCore;
|
||||||
pub use error::{Error, Result};
|
pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock};
|
||||||
pub use handle::{Handle, HandleRights};
|
pub use ctx::{WasiCtx, WasiCtxBuilder};
|
||||||
pub use sys::osdir::OsDir;
|
pub use dir::WasiDir;
|
||||||
pub use sys::osfile::OsFile;
|
pub use error::{Error, ErrorExt, ErrorKind};
|
||||||
pub use sys::osother::OsOther;
|
pub use file::WasiFile;
|
||||||
pub use sys::preopen_dir;
|
pub use sched::{Poll, WasiSched};
|
||||||
pub use virtfs::{FileContents, VirtualDirEntry};
|
pub use string_array::StringArrayError;
|
||||||
|
pub use table::Table;
|
||||||
|
|||||||
@@ -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(".")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
321
crates/wasi-common/src/pipe.rs
Normal file
321
crates/wasi-common/src/pipe.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
crates/wasi-common/src/random.rs
Normal file
52
crates/wasi-common/src/random.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,87 @@
|
|||||||
use crate::entry::EntryHandle;
|
use crate::clocks::WasiMonotonicClock;
|
||||||
pub use crate::wasi::types::{
|
use crate::file::WasiFile;
|
||||||
Clockid, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, Subclockflags,
|
use crate::Error;
|
||||||
SubscriptionClock, Timestamp, Userdata,
|
use cap_std::time::{Duration, Instant};
|
||||||
};
|
use std::cell::Ref;
|
||||||
#[derive(Debug, Copy, Clone)]
|
pub mod subscription;
|
||||||
pub struct ClockEventData {
|
|
||||||
pub delay: u128, // delay is expressed in nanoseconds
|
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
|
||||||
pub userdata: Userdata,
|
|
||||||
|
pub trait WasiSched {
|
||||||
|
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
|
||||||
|
fn sched_yield(&self) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub struct Userdata(u64);
|
||||||
pub struct FdEventData {
|
impl From<u64> for Userdata {
|
||||||
pub handle: EntryHandle,
|
fn from(u: u64) -> Userdata {
|
||||||
pub r#type: Eventtype,
|
Userdata(u)
|
||||||
pub userdata: Userdata,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
crates/wasi-common/src/sched/subscription.rs
Normal file
79
crates/wasi-common/src/sched/subscription.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,27 @@
|
|||||||
pub mod wasi_snapshot_preview1;
|
//! One goal of `wasi-common` is for multiple WASI snapshots to provide an
|
||||||
pub mod wasi_unstable;
|
//! 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;
|
||||||
|
|||||||
925
crates/wasi-common/src/snapshots/preview_0.rs
Normal file
925
crates/wasi-common/src/snapshots/preview_0.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
1503
crates/wasi-common/src/snapshots/preview_1.rs
Normal file
1503
crates/wasi-common/src/snapshots/preview_1.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +1,48 @@
|
|||||||
use crate::Error;
|
use crate::{Error, ErrorExt};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::ffi::{CString, OsString};
|
|
||||||
use wiggle::GuestPtr;
|
use wiggle::GuestPtr;
|
||||||
|
|
||||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
pub struct StringArray {
|
||||||
pub enum PendingString {
|
elems: Vec<String>,
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum StringArrayError {
|
pub enum StringArrayError {
|
||||||
/// Provided sequence of bytes contained an unexpected NUL byte.
|
#[error("Number of elements exceeds 2^32")]
|
||||||
#[error("provided sequence contained an unexpected NUL byte")]
|
NumberElements,
|
||||||
Nul(#[from] std::ffi::NulError),
|
#[error("Element size exceeds 2^32")]
|
||||||
/// Too many elements: must fit into u32
|
ElementSize,
|
||||||
#[error("too many elements")]
|
#[error("Cumulative size exceeds 2^32")]
|
||||||
NumElements,
|
CumulativeSize,
|
||||||
/// 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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StringArray {
|
|
||||||
elems: Vec<CString>,
|
|
||||||
pub number_elements: u32,
|
|
||||||
pub cumulative_size: u32,
|
|
||||||
}
|
|
||||||
impl StringArray {
|
impl StringArray {
|
||||||
pub fn from_pending_vec(elems: Vec<PendingString>) -> Result<Self, StringArrayError> {
|
pub fn new() -> Self {
|
||||||
let elems = elems
|
StringArray { elems: Vec::new() }
|
||||||
.into_iter()
|
|
||||||
.map(|arg| arg.into_string())
|
|
||||||
.collect::<Result<Vec<String>, StringArrayError>>()?;
|
|
||||||
Self::from_strings(elems)
|
|
||||||
}
|
}
|
||||||
pub fn from_pending_map(
|
|
||||||
elems: HashMap<PendingString, PendingString>,
|
pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> {
|
||||||
) -> Result<Self, StringArrayError> {
|
if self.elems.len() + 1 > std::u32::MAX as usize {
|
||||||
let mut pairs = Vec::new();
|
return Err(StringArrayError::NumberElements);
|
||||||
for (k, v) in elems.into_iter() {
|
|
||||||
let mut pair = k.into_string()?;
|
|
||||||
pair.push('=');
|
|
||||||
pair.push_str(&v.into_string()?);
|
|
||||||
pairs.push(pair);
|
|
||||||
}
|
}
|
||||||
Self::from_strings(pairs)
|
if elem.as_bytes().len() + 1 > std::u32::MAX as usize {
|
||||||
|
return Err(StringArrayError::ElementSize);
|
||||||
|
}
|
||||||
|
if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize {
|
||||||
|
return Err(StringArrayError::CumulativeSize);
|
||||||
|
}
|
||||||
|
self.elems.push(elem);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn from_strings(elems: Vec<String>) -> Result<Self, StringArrayError> {
|
|
||||||
let elems = elems
|
pub fn number_elements(&self) -> u32 {
|
||||||
.into_iter()
|
self.elems.len() as u32
|
||||||
.map(|s| CString::new(s))
|
}
|
||||||
.collect::<Result<Vec<CString>, _>>()?;
|
|
||||||
let number_elements = elems
|
pub fn cumulative_size(&self) -> u32 {
|
||||||
.len()
|
self.elems
|
||||||
.try_into()
|
.iter()
|
||||||
.map_err(|_| StringArrayError::NumElements)?;
|
.map(|e| e.as_bytes().len() + 1)
|
||||||
let mut cumulative_size: u32 = 0;
|
.sum::<usize>() as u32
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
elems,
|
|
||||||
number_elements,
|
|
||||||
cumulative_size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to_guest<'a>(
|
pub fn write_to_guest<'a>(
|
||||||
@@ -123,22 +50,24 @@ impl StringArray {
|
|||||||
buffer: &GuestPtr<'a, u8>,
|
buffer: &GuestPtr<'a, u8>,
|
||||||
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
|
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let element_heads = element_heads.as_array(self.number_elements);
|
let element_heads = element_heads.as_array(self.number_elements());
|
||||||
let buffer = buffer.as_array(self.cumulative_size);
|
let buffer = buffer.as_array(self.cumulative_size());
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
|
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
|
||||||
let bytes = elem.as_bytes_with_nul();
|
let bytes = elem.as_bytes();
|
||||||
let len: u32 = bytes.len().try_into()?;
|
let len = bytes.len() as u32;
|
||||||
let elem_buffer = buffer
|
{
|
||||||
.get_range(cursor..(cursor + len))
|
let elem_buffer = buffer
|
||||||
.ok_or(Error::Inval)?; // Elements don't fit in buffer provided
|
.get_range(cursor..(cursor + len))
|
||||||
elem_buffer.copy_from_slice(bytes)?;
|
.ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided
|
||||||
head?.write(
|
elem_buffer.copy_from_slice(bytes)?;
|
||||||
elem_buffer
|
}
|
||||||
.get(0)
|
buffer
|
||||||
.expect("all elem buffers at least length 1"),
|
.get(cursor + len)
|
||||||
)?;
|
.ok_or(Error::invalid_argument())?
|
||||||
cursor += len;
|
.write(0)?; // 0 terminate
|
||||||
|
head?.write(buffer.get(cursor).expect("already validated"))?;
|
||||||
|
cursor += len + 1;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub(crate) mod osdir;
|
|
||||||
pub(crate) mod path;
|
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::SYNC;
|
|
||||||
@@ -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
Reference in New Issue
Block a user