diff --git a/Cargo.lock b/Cargo.lock index ea0545bb901..653f89cc940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,18 +39,21 @@ dependencies = [ [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" dependencies = [ "libc", ] [[package]] name = "ansi_term" -version = "0.7.5" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30275ad0ad84ec1c06dde3b3f7d23c6006b7d76d61a85e7060b426b747eff70d" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] name = "any_ascii" @@ -60,9 +63,9 @@ checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e" [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "arbitrary" @@ -107,9 +110,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -219,17 +222,11 @@ dependencies = [ "glob", ] -[[package]] -name = "build_const" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "bytecheck" @@ -282,8 +279,8 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb" dependencies = [ - "clap 3.2.23", - "heck 0.4.0", + "clap 3.2.17", + "heck", "indexmap", "log", "proc-macro2", @@ -343,9 +340,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" dependencies = [ "atty", "bitflags", @@ -360,9 +357,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -489,6 +486,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -510,9 +523,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" dependencies = [ "libc", ] @@ -969,12 +982,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - [[package]] name = "encoding_rs" version = "0.8.31" @@ -1107,19 +1114,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "form_urlencoded" -version = "1.1.0" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "percent-encoding", + "foreign-types-shared", ] [[package]] -name = "fuchsia-cprng" +name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] [[package]] name = "futures" @@ -1369,9 +1385,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -1448,7 +1464,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.3", ] [[package]] @@ -1486,6 +1502,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "hyper" version = "0.14.22" @@ -1553,6 +1581,56 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.3", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1662,6 +1740,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itertools" version = "0.10.5" @@ -1724,9 +1808,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libfuzzer-sys" @@ -1750,8 +1834,23 @@ dependencies = [ ] [[package]] -name = "link-cplusplus" -version = "1.0.7" +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "llvm-sys" +version = "120.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" dependencies = [ @@ -1779,9 +1878,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -1825,6 +1924,15 @@ dependencies = [ "syn", ] +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1864,6 +1972,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minifb" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "mime_guess" version = "2.0.4" @@ -1917,14 +2031,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", + "windows-sys 0.36.1", ] [[package]] @@ -1942,6 +2056,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.24.2" @@ -2042,9 +2174,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "oorandom" @@ -2052,6 +2184,51 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.39" @@ -2070,9 +2247,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.3.1" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "output_vt100" @@ -2120,6 +2297,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -2128,9 +2317,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" +checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" dependencies = [ "thiserror", "ucd-trie", @@ -2148,6 +2337,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.26" @@ -2156,9 +2351,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b" dependencies = [ "num-traits", "plotters-backend", @@ -2566,28 +2761,25 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", - "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.20.7", - "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.5", "winreg", ] @@ -2732,6 +2924,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2801,6 +3003,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -2845,9 +3070,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -2884,9 +3109,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -2895,9 +3120,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa 1.0.4", "ryu", @@ -2911,7 +3136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.4", + "itoa 1.0.3", "ryu", "serde", ] @@ -2967,9 +3192,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -2985,6 +3210,30 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.7" @@ -3016,6 +3265,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -3187,6 +3446,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -3196,6 +3465,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + [[package]] name = "termtree" version = "0.2.4" @@ -3354,19 +3632,31 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "winapi", ] [[package]] -name = "tokio-rustls" -version = "0.23.4" +name = "tokio-macros" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ - "rustls 0.20.7", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", "tokio", - "webpki 0.22.0", ] [[package]] @@ -3398,6 +3688,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3429,6 +3725,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers 0.0.1", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -3437,7 +3777,7 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "matchers", + "matchers 0.1.0", "once_cell", "regex", "sharded-slab", @@ -3453,7 +3793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" dependencies = [ "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.15", "wasm-bindgen", ] @@ -3463,6 +3803,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "trybuild" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "trybuild" version = "1.0.71" @@ -3529,6 +3875,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.5" @@ -3550,6 +3902,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.10" @@ -3595,6 +3956,36 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "version-compare" version = "0.1.0" @@ -3622,6 +4013,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -3643,23 +4040,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wapm-toml" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60213ef08e950dfda77b5497ffc32a63d70dbb4ba075ba6d1787de9d98fc431" -dependencies = [ - "anyhow", - "semver 1.0.14", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "serde_yaml", - "thiserror", - "toml", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3779,9 +4159,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.19.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5816e88e8ea7335016aa62eb0485747f786136d505a9b3890f8c400211d9b5f" +checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" dependencies = [ "leb128", ] @@ -3805,6 +4185,7 @@ dependencies = [ "anyhow", "bytes", "cfg-if 1.0.0", + "derivative", "hashbrown 0.11.2", "indexmap", "js-sys", @@ -3916,8 +4297,7 @@ dependencies = [ "atty", "bytesize", "cfg-if 1.0.0", - "chrono", - "clap 3.2.23", + "clap 3.2.17", "colored 2.0.0", "dirs 4.0.0", "distance", @@ -3992,7 +4372,7 @@ dependencies = [ "atty", "bytesize", "cfg-if 1.0.0", - "clap 3.2.23", + "clap 3.2.17", "colored 2.0.0", "distance", "fern", @@ -4195,6 +4575,9 @@ name = "wasmer-vbus" version = "3.0.0-rc.2" dependencies = [ "thiserror", + "tracing", + "typetag", + "wasmer", "wasmer-vfs", ] @@ -4202,7 +4585,8 @@ dependencies = [ name = "wasmer-vfs" version = "3.0.0-rc.2" dependencies = [ - "anyhow", + "async-trait", + "lazy_static", "libc", "serde", "slab", @@ -4220,6 +4604,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "corosensei", + "derivative", "enum-iterator", "indexmap", "lazy_static", @@ -4231,6 +4616,7 @@ dependencies = [ "scopeguard", "serde", "thiserror", + "tracing", "wasmer-types", "winapi", ] @@ -4239,6 +4625,7 @@ dependencies = [ name = "wasmer-vnet" version = "3.0.0-rc.2" dependencies = [ + "async-trait", "bytes", "thiserror", "wasmer-vfs", @@ -4248,31 +4635,54 @@ dependencies = [ name = "wasmer-wasi" version = "3.0.0-rc.2" dependencies = [ - "anyhow", + "async-trait", "bincode", "bytes", "cfg-if 1.0.0", "chrono", + "cooked-waker", "derivative", + "futures", "generational-arena", "getrandom", + "hex", + "lazy_static", "libc", + "linked_hash_set", + "rand", + "reqwest", "serde", - "serde_cbor", + "serde_derive", + "serde_json", + "serde_yaml", + "sha2", + "shellexpand", + "term_size", + "termios", "thiserror", + "tokio", "tracing", + "tracing-subscriber 0.2.25", "tracing-wasm", "typetag", + "urlencoding", + "waker-fn", "wasm-bindgen", "wasm-bindgen-test", "wasmer", - "wasmer-emscripten", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-compiler-llvm", + "wasmer-compiler-singlepass", + "wasmer-types", "wasmer-vbus", "wasmer-vfs", "wasmer-vnet", "wasmer-wasi-local-networking", "wasmer-wasi-types", "webc", + "webc-vfs", + "weezl", "winapi", ] @@ -4293,7 +4703,9 @@ dependencies = [ name = "wasmer-wasi-local-networking" version = "3.0.0-rc.2" dependencies = [ + "async-trait", "bytes", + "tokio", "tracing", "wasmer-vfs", "wasmer-vnet", @@ -4414,7 +4826,7 @@ dependencies = [ "test-generator", "test-log", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.15", "wasi-test-generator", "wasmer", "wasmer-cache", @@ -4437,21 +4849,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" -version = "0.93.0" +version = "0.89.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a4460aa3e271fa180b6a5d003e728f3963fb30e3ba0fa7c9634caa06049328" +checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" dependencies = [ "indexmap", ] [[package]] name = "wasmprinter" -version = "0.2.42" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c9f096ba095329c6aa55b7e9cafa26c5b50e9ab7fc2415fd0b26cb80dca8f05" +checksum = "aa9e5ee2f56cc8a5da489558114e8c118e5a8416d96aefe63dcf1b5b05b858c6" dependencies = [ "anyhow", - "wasmparser 0.93.0", + "wasmparser 0.89.1", ] [[package]] @@ -4474,23 +4886,23 @@ dependencies = [ [[package]] name = "wast" -version = "48.0.0" +version = "46.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84825b5ac7164df8260c9e2b2e814075334edbe7ac426f2469b93a5eeac23cce" +checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.19.0", + "wasm-encoder 0.16.0", ] [[package]] name = "wat" -version = "1.0.50" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129da4a03ec6d2a815f42c88f641824e789d5be0d86d2f90aa8a218c7068e0be" +checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" dependencies = [ - "wast 48.0.0", + "wast 46.0.0", ] [[package]] @@ -4578,9 +4990,7 @@ dependencies = [ [[package]] name = "webc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef87e7b955d5d1feaa8697ae129f1a9ce8859e151574ad3baceae9413b48d2f0" +version = "0.1.0" dependencies = [ "anyhow", "base64", @@ -4590,7 +5000,7 @@ dependencies = [ "memchr", "memmap2", "path-clean", - "rand 0.8.5", + "rand", "serde", "serde_cbor", "serde_json", @@ -4599,6 +5009,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "webc-vfs" +version = "0.1.0" +dependencies = [ + "anyhow", + "wasmer-vfs", + "webc", +] + [[package]] name = "webpki" version = "0.21.4" @@ -4629,8 +5048,14 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.22.5" +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "which" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" dependencies = [ @@ -4788,6 +5213,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "x11-dl" version = "2.20.0" @@ -4831,9 +5265,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/docs/migration_to_3.0.0.md b/docs/migration_to_3.0.0.md index dc8d2b45ea2..46ef126db22 100644 --- a/docs/migration_to_3.0.0.md +++ b/docs/migration_to_3.0.0.md @@ -196,7 +196,7 @@ import_object.define("env", "host_function", host_function); let instance = Instance::new(&mut store, &module, &import_object).expect("Could not instantiate module."); ``` -For WASI, don't forget to initialize the `WasiEnv` (it will import the memory) +For WASI, don't forget to initialize it ```rust let mut wasi_env = WasiState::new("hello").finalize()?; diff --git a/examples/imports_function_env.rs b/examples/imports_function_env.rs index dc870625e69..0b235f93f49 100644 --- a/examples/imports_function_env.rs +++ b/examples/imports_function_env.rs @@ -80,8 +80,8 @@ fn main() -> Result<(), Box> { fn get_counter(env: FunctionEnvMut) -> i32 { *env.data().counter.lock().unwrap() } - fn add_to_counter(mut env: FunctionEnvMut, add: i32) -> i32 { - let mut counter_ref = env.data_mut().counter.lock().unwrap(); + fn add_to_counter(env: FunctionEnvMut, add: i32) -> i32 { + let mut counter_ref = env.data().counter.lock().unwrap(); *counter_ref += add; *counter_ref diff --git a/examples/wasi.rs b/examples/wasi.rs index e3232061148..b8880b5d5a5 100644 --- a/examples/wasi.rs +++ b/examples/wasi.rs @@ -39,7 +39,7 @@ fn main() -> Result<(), Box> { println!("Creating `WasiEnv`..."); // First, we create the `WasiEnv` - let wasi_env = WasiState::new("hello") + let mut wasi_env = WasiState::new("hello") // .args(&["world"]) // .env("KEY", "Value") .finalize(&mut store)?; @@ -50,10 +50,9 @@ fn main() -> Result<(), Box> { let import_object = wasi_env.import_object(&mut store, &module)?; let instance = Instance::new(&mut store, &module, &import_object)?; - println!("Attach WASI memory..."); - // Attach the memory export - let memory = instance.exports.get_memory("memory")?; - wasi_env.data_mut(&mut store).set_memory(memory.clone()); + println!("Initializing WASI environment..."); + // Initialize the WASI environment (which will attach memory) + wasi_env.initialize(&mut store, &instance).unwrap(); println!("Call WASI `_start` function..."); // And we just call the `_start` function! diff --git a/examples/wasi_pipes.rs b/examples/wasi_pipes.rs index e4319b71688..1a7317773e8 100644 --- a/examples/wasi_pipes.rs +++ b/examples/wasi_pipes.rs @@ -49,10 +49,9 @@ fn main() -> Result<(), Box> { let import_object = wasi_env.import_object(&mut store, &module)?; let instance = Instance::new(&mut store, &module, &import_object)?; - println!("Attach WASI memory..."); - // Attach the memory export - let memory = instance.exports.get_memory("memory")?; - wasi_env.data_mut(&mut store).set_memory(memory.clone()); + println!("Initializing WASI environment..."); + // Initialize the WASI environment (which will attach memory) + wasi_env.initialize(&mut store, &instance).unwrap(); let msg = "racecar go zoom"; println!("Writing \"{}\" to the WASI stdin...", msg); diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index deb89be9c1c..6123eebdecd 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -27,6 +27,7 @@ cfg-if = "1.0" thiserror = "1.0" more-asserts = "0.2" bytes = "1" +derivative = { version = "^2" } # - Optional shared dependencies. wat = { version = "1.0", optional = true } tracing = { version = "0.1", optional = true } diff --git a/lib/api/src/js/export.rs b/lib/api/src/js/export.rs index 0accfc240b4..94a887a9308 100644 --- a/lib/api/src/js/export.rs +++ b/lib/api/src/js/export.rs @@ -6,7 +6,12 @@ use js_sys::WebAssembly::{Memory, Table}; use serde::{Deserialize, Serialize}; use std::fmt; use wasm_bindgen::{JsCast, JsValue}; -use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType, WASM_PAGE_SIZE}; +use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType, Pages, WASM_PAGE_SIZE, StoreSnapshot}; +use crate::MemoryView; +#[cfg(feature="tracing")] +use tracing::trace; + +pub use wasmer_types::MemoryError; /// Represents linear memory that is managed by the javascript runtime #[derive(Clone, Debug, PartialEq)] @@ -25,7 +30,8 @@ struct DummyBuffer { } impl VMMemory { - pub(crate) fn new(memory: Memory, ty: MemoryType) -> Self { + /// Creates a new memory directly from a WebAssembly javascript object + pub fn new(memory: Memory, ty: MemoryType) -> Self { Self { memory, ty } } @@ -33,6 +39,54 @@ impl VMMemory { pub(crate) fn try_clone(&self) -> Option { Some(self.clone()) } + + /// Copies this memory to a new memory + pub fn fork(&self) -> Result { + let new_memory = crate::Memory::new_internal(self.ty.clone())?; + + #[cfg(feature="tracing")] + trace!("memory copy started"); + + let src = MemoryView::new_raw(&self.memory); + let amount = src.data_size() as usize; + let mut dst = MemoryView::new_raw(&new_memory); + let dst_size = dst.data_size() as usize; + + if amount > dst_size { + let delta = amount - dst_size; + let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1; + + let our_js_memory: &crate::js::externals::memory::JSMemory = JsCast::unchecked_from_js_ref(&new_memory); + our_js_memory.grow(pages as u32).map_err(|err| { + if err.is_instance_of::() { + let cur_pages = dst_size; + MemoryError::CouldNotGrow { + current: Pages(cur_pages as u32), + attempted_delta: Pages(pages as u32), + } + } else { + MemoryError::Generic(err.as_string().unwrap()) + } + })?; + + dst = MemoryView::new_raw(&new_memory); + } + + src.copy_to_memory(amount as u64, &dst) + .map_err(|err| { + wasmer_types::MemoryError::Generic(format!("failed to copy the memory - {}", err)) + })?; + + #[cfg(feature="tracing")] + trace!("memory copy finished (size={})", dst.size().bytes().0); + + Ok( + Self { + memory: new_memory, + ty: self.ty.clone(), + } + ) + } } #[derive(Clone, Debug, PartialEq)] @@ -45,6 +99,31 @@ impl VMGlobal { pub(crate) fn new(global: Global, ty: GlobalType) -> Self { Self { global, ty } } + + /// Saves the global value into the snapshot + pub fn save_snapshot(&self, index: usize, snapshot: &mut StoreSnapshot) { + if let Some(val) = self.global.as_f64() { + let entry = snapshot.globals + .entry(index as u32) + .or_default(); + *entry = val as u128; + } + } + + /// Restores the global value from the snapshot + pub fn restore_snapshot(&mut self, index: usize, snapshot: &StoreSnapshot) { + let index = index as u32; + if let Some(entry) = snapshot.globals.get(&index) { + if let Some(existing) = self.global.as_f64() { + let existing = existing as u128; + if existing == *entry { + return; + } + } + let value = JsValue::from_f64(*entry as _); + self.global.set_value(&value); + } + } } unsafe impl Send for VMGlobal {} diff --git a/lib/api/src/js/exports.rs b/lib/api/src/js/exports.rs index 024ab8b9696..339095bafa0 100644 --- a/lib/api/src/js/exports.rs +++ b/lib/api/src/js/exports.rs @@ -53,6 +53,9 @@ pub enum ExportError { /// This error arises when an export is missing #[error("Missing export {0}")] Missing(String), + /// This error arises when an export is missing + #[error("Serialization failed {0}")] + SerializationFailed(String), } /// Exports is a special kind of map that allows easily unwrapping diff --git a/lib/api/src/js/externals/function.rs b/lib/api/src/js/externals/function.rs index 770c462ae60..d7542770439 100644 --- a/lib/api/src/js/externals/function.rs +++ b/lib/api/src/js/externals/function.rs @@ -61,6 +61,16 @@ pub struct Function { pub(crate) handle: StoreHandle, } +impl Into +for StoreHandle +{ + fn into(self) -> Function { + Function { + handle: self + } + } +} + impl Function { /// Creates a new host `Function` (dynamic) with the provided signature. /// @@ -393,6 +403,10 @@ impl Function { store: &mut impl AsStoreMut, params: &[Value], ) -> Result, RuntimeError> { + #[allow(unused_unsafe)] + let params: Vec<_> = unsafe { + params.iter().map(|a| a.as_raw_value(&store.as_store_ref())).collect() + }; let arr = js_sys::Array::new_with_length(params.len() as u32); // let raw_env = env.as_raw() as *mut u8; @@ -402,11 +416,28 @@ impl Function { let js_value = param.as_jsvalue(&store.as_store_ref()); arr.set(i as u32, js_value); } - let result = js_sys::Reflect::apply( - &self.handle.get(store.as_store_ref().objects()).function, - &wasm_bindgen::JsValue::NULL, - &arr, - )?; + + let result = { + let mut r; + loop { + r = js_sys::Reflect::apply( + &self.handle.get(store.as_store_ref().objects()).function, + &wasm_bindgen::JsValue::NULL, + &arr, + ); + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { + match callback(store_mut) { + Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } + Ok(wasmer_types::OnCalledAction::Finish) => { break; } + Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) }, + Err(trap) => { return Err(RuntimeError::user(trap)) }, + } + } + break; + } + r? + }; let result_types = self.handle.get(store.as_store_ref().objects()).ty.results(); match result_types.len() { @@ -1134,16 +1165,18 @@ mod inner { T: Send + 'static, Func: Fn(FunctionEnvMut<'_, T>, $( $x , )*) -> RetsAsResult + 'static, { - // let env: &Env = unsafe { &*(ptr as *const u8 as *const Env) }; - let func: &Func = &*(&() as *const () as *const Func); let mut store = StoreMut::from_raw(store_ptr as *mut _); let mut store2 = StoreMut::from_raw(store_ptr as *mut _); - let result = panic::catch_unwind(AssertUnwindSafe(|| { - let handle: StoreHandle = StoreHandle::from_internal(store2.objects_mut().id(), InternalStoreHandle::from_index(handle_index).unwrap()); - let env: FunctionEnvMut = FunctionEnv::from_handle(handle).into_mut(&mut store2); - func(env, $( FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) ),* ).into_result() - })); + let result = { + // let env: &Env = unsafe { &*(ptr as *const u8 as *const Env) }; + let func: &Func = &*(&() as *const () as *const Func); + panic::catch_unwind(AssertUnwindSafe(|| { + let handle: StoreHandle = StoreHandle::from_internal(store2.objects_mut().id(), InternalStoreHandle::from_index(handle_index).unwrap()); + let env: FunctionEnvMut = FunctionEnv::from_handle(handle).into_mut(&mut store2); + func(env, $( FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)) ),* ).into_result() + })) + }; match result { Ok(Ok(result)) => return result.into_c_struct(&mut store), @@ -1425,4 +1458,4 @@ mod inner { } } */ -} +} \ No newline at end of file diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index 4fc8da25a05..a9e860e0060 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -11,7 +11,7 @@ use tracing::warn; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use wasmer_types::Pages; +use wasmer_types::{Pages, WASM_PAGE_SIZE}; use super::MemoryView; @@ -86,6 +86,11 @@ impl Memory { /// let m = Memory::new(&store, MemoryType::new(1, None, false)).unwrap(); /// ``` pub fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result { + let vm_memory = VMMemory::new(Self::new_internal(ty.clone())?, ty); + Ok(Self::from_vm_export(store, vm_memory)) + } + + pub(crate) fn new_internal(ty: MemoryType) -> Result { let descriptor = js_sys::Object::new(); js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap(); if let Some(max) = ty.maximum { @@ -96,31 +101,9 @@ impl Memory { let js_memory = js_sys::WebAssembly::Memory::new(&descriptor) .map_err(|_e| MemoryError::Generic("Error while creating the memory".to_owned()))?; - let vm_memory = VMMemory::new(js_memory, ty); - let handle = StoreHandle::new(store.objects_mut(), vm_memory); - Ok(Self::from_vm_extern(store, handle.internal_handle())) - } - - /// Creates a new host `Memory` from provided JavaScript memory. - pub fn new_raw( - store: &mut impl AsStoreMut, - js_memory: js_sys::WebAssembly::Memory, - ty: MemoryType, - ) -> Result { - let vm_memory = VMMemory::new(js_memory, ty); - let handle = StoreHandle::new(store.objects_mut(), vm_memory); - Ok(Self::from_vm_extern(store, handle.internal_handle())) - } - - /// Create a memory object from an existing memory and attaches it to the store - pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self { - let handle = StoreHandle::new(new_store.objects_mut(), memory); - Self::from_vm_extern(new_store, handle.internal_handle()) - } - - /// To `VMExtern`. - pub(crate) fn to_vm_extern(&self) -> VMExtern { - VMExtern::Memory(self.handle.internal_handle()) + Ok( + js_memory + ) } /// Creates a new host `Memory` from provided JavaScript memory. @@ -211,6 +194,44 @@ impl Memory { Ok(Pages(new_pages)) } + /// Copies the memory to a new store and returns a memory reference to it + pub fn copy_to_store( + &self, + store: &impl AsStoreRef, + new_store: &mut impl AsStoreMut, + ) -> Result + { + // Create the new memory using the parameters of the existing memory + let view = self.view(store); + let ty = self.ty(store); + let amount = view.data_size() as usize; + + let new_memory = Self::new(new_store, ty)?; + let mut new_view = new_memory.view(&new_store); + let new_view_size = new_view.data_size() as usize; + if amount > new_view_size { + let delta = amount - new_view_size; + let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1; + new_memory.grow(new_store, Pages(pages as u32))?; + new_view = new_memory.view(&new_store); + } + + // Copy the bytes + view.copy_to_memory(amount as u64, &new_view) + .map_err(|err| { + MemoryError::Generic(err.to_string()) + })?; + + // Return the new memory + Ok(new_memory) + } + + pub(crate) fn from_vm_export(store: &mut impl AsStoreMut, vm_memory: VMMemory) -> Self { + Self { + handle: StoreHandle::new(store.objects_mut(), vm_memory), + } + } + pub(crate) fn from_vm_extern( store: &mut impl AsStoreMut, internal: InternalStoreHandle, @@ -228,6 +249,12 @@ impl Memory { mem.try_clone() } + /// Copies this memory to a new memory + pub fn fork(&mut self, store: &impl AsStoreRef) -> Result { + let mem = self.handle.get(store.as_store_ref().objects()); + mem.fork() + } + /// Checks whether this `Global` can be used with the given context. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { self.handle.store_id() == store.as_store_ref().objects().id() diff --git a/lib/api/src/js/externals/memory_view.rs b/lib/api/src/js/externals/memory_view.rs index a219572207e..782775280b1 100644 --- a/lib/api/src/js/externals/memory_view.rs +++ b/lib/api/src/js/externals/memory_view.rs @@ -27,11 +27,14 @@ pub struct MemoryView<'a> { impl<'a> MemoryView<'a> { pub(crate) fn new(memory: &Memory, store: &impl AsStoreRef) -> Self { - let buffer = memory + let memory = memory .handle - .get(store.as_store_ref().objects()) - .memory - .buffer(); + .get(store.as_store_ref().objects()); + Self::new_raw(&memory.memory) + } + + pub(crate) fn new_raw(memory: &js_sys::WebAssembly::Memory) -> Self { + let buffer = memory.buffer(); let size = js_sys::Reflect::get(&buffer, &"byteLength".into()) .unwrap() @@ -250,4 +253,35 @@ impl<'a> MemoryView<'a> { view.set_index(offset, val); Ok(()) } + + /// Copies the memory and returns it as a vector of bytes + pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + let mut new_memory = Vec::new(); + let mut offset = 0; + let mut chunk = [0u8; 40960]; + while offset < self.data_size() { + let remaining = self.data_size() - offset; + let sublen = remaining.min(chunk.len() as u64) as usize; + self.read(offset, &mut chunk[..sublen])?; + new_memory.extend_from_slice(&chunk[..sublen]); + offset += sublen as u64; + } + Ok(new_memory) + } + + /// Copies the memory to another new memory object + pub fn copy_to_memory(&self, amount: u64, new_memory: &Self) -> Result<(), MemoryAccessError> { + let mut offset = 0; + let mut chunk = [0u8; 40960]; + while offset < amount { + let remaining = amount - offset; + let sublen = remaining.min(chunk.len() as u64) as usize; + self.read(offset, &mut chunk[..sublen])?; + + new_memory.write(offset, &chunk[..sublen])?; + + offset += sublen as u64; + } + Ok(()) + } } diff --git a/lib/api/src/js/instance.rs b/lib/api/src/js/instance.rs index 5e1ddbf2e45..68b94651a83 100644 --- a/lib/api/src/js/instance.rs +++ b/lib/api/src/js/instance.rs @@ -63,11 +63,11 @@ impl Instance { module: &Module, imports: &Imports, ) -> Result { - let instance: WebAssembly::Instance = module + let (instance, externs): (StoreHandle, Vec) = module .instantiate(&mut store, imports) .map_err(|e| InstantiationError::Start(e))?; - let self_instance = Self::from_module_and_instance(store, module, instance)?; + let self_instance = Self::from_module_and_instance(store, module, externs, instance)?; //self_instance.init_envs(&imports.iter().map(Extern::to_export).collect::>())?; Ok(self_instance) } @@ -106,11 +106,11 @@ impl Instance { pub fn from_module_and_instance( mut store: &mut impl AsStoreMut, module: &Module, - instance: WebAssembly::Instance, + externs: Vec, + instance: StoreHandle, ) -> Result { - use crate::js::externals::VMExtern; - let instance_exports = instance.exports(); - let exports = module + let instance_exports = instance.get(store.as_store_ref().objects()).exports(); + let mut exports = module .exports() .map(|export_type| { let name = export_type.name(); @@ -123,7 +123,15 @@ impl Instance { Ok((name.to_string(), extern_)) }) .collect::>()?; - let handle = StoreHandle::new(store.as_store_mut().objects_mut(), instance); + + // If the memory is imported then also export it for backwards compatibility reasons + // (many will assume the memory is always exported) - later we can remove this + if exports.get_memory("memory").is_err() { + if let Some(memory) = externs.iter().filter(|a| a.ty(store).memory().is_some()).next() { + exports.insert("memory", memory.clone()); + } + } + Ok(Self { _handle: handle, module: module.clone(), diff --git a/lib/api/src/js/mem_access.rs b/lib/api/src/js/mem_access.rs index ac0a543436f..5a9142e48e4 100644 --- a/lib/api/src/js/mem_access.rs +++ b/lib/api/src/js/mem_access.rs @@ -328,6 +328,24 @@ impl<'a, T: ValueType> WasmSlice<'a, T> { } Ok(vec) } + + /// Reads this `WasmSlice` into a `BytesMut` + #[inline] + pub fn read_to_bytes(self) -> Result { + let len = self.len.try_into().expect("WasmSlice length overflow"); + let mut ret = bytes::BytesMut::with_capacity(len); + let bytes = unsafe { + slice::from_raw_parts_mut( + ret.as_mut_ptr() as *mut MaybeUninit, + len * mem::size_of::(), + ) + }; + self.buffer.read_uninit(self.offset, bytes)?; + unsafe { + ret.set_len(len); + } + Ok(ret) + } } impl<'a, T: ValueType> fmt::Debug for WasmSlice<'a, T> { diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index 7b3e4155f16..c5ef7947d02 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -80,7 +80,7 @@ pub mod vm { pub use wasmer_types::is_wasm; pub use wasmer_types::{ Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, Pages, ValueType, WASM_MAX_PAGES, - WASM_MIN_PAGES, WASM_PAGE_SIZE, + WASM_MIN_PAGES, WASM_PAGE_SIZE, OnCalledAction, StoreSnapshot }; #[cfg(feature = "wat")] diff --git a/lib/api/src/js/module.rs b/lib/api/src/js/module.rs index 47b751aaf42..8f65c3c5a3f 100644 --- a/lib/api/src/js/module.rs +++ b/lib/api/src/js/module.rs @@ -14,6 +14,7 @@ use std::borrow::Cow; use std::fmt; use std::io; use std::path::Path; +use bytes::Bytes; #[cfg(feature = "std")] use thiserror::Error; use wasm_bindgen::JsValue; @@ -21,6 +22,8 @@ use wasmer_types::{ ExportsIterator, ExternType, FunctionType, GlobalType, ImportsIterator, MemoryType, Mutability, Pages, TableType, Type, }; +#[cfg(feature = "tracing")] +use tracing::{debug, warn}; #[derive(Debug)] #[cfg_attr(feature = "std", derive(Error))] @@ -108,6 +111,32 @@ pub struct Module { raw_bytes: Option, } +pub trait IntoBytes +{ + fn into_bytes(self) -> Bytes; +} + +impl IntoBytes +for Bytes { + fn into_bytes(self) -> Bytes { + self + } +} + +impl IntoBytes +for Vec { + fn into_bytes(self) -> Bytes { + Bytes::from(self) + } +} + +impl IntoBytes +for &[u8] { + fn into_bytes(self) -> Bytes { + Bytes::from(self.to_vec()) + } +} + impl Module { /// Creates a new WebAssembly Module given the configuration /// in the store. @@ -169,15 +198,20 @@ impl Module { /// # } /// ``` #[allow(unreachable_code)] - pub fn new(_store: &impl AsStoreRef, bytes: impl AsRef<[u8]>) -> Result { + pub fn new(_store: &impl AsStoreRef, bytes: impl IntoBytes) -> Result { + #[allow(unused_mut)] + let mut bytes = bytes.into_bytes(); #[cfg(feature = "wat")] - let bytes = wat::parse_bytes(bytes.as_ref()).map_err(|e| { - CompileError::Wasm(WasmError::Generic(format!( - "Error when converting wat: {}", - e - ))) - })?; - Self::from_binary(_store, bytes.as_ref()) + if bytes.starts_with(b"\0asm") == false { + let parsed_bytes = wat::parse_bytes(bytes.as_ref()).map_err(|e| { + CompileError::Wasm(WasmError::Generic(format!( + "Error when converting wat: {}", + e + ))) + })?; + bytes = Bytes::from(parsed_bytes.to_vec()); + } + Self::from_binary(_store, bytes) } /// Creates a new WebAssembly module from a file path. @@ -193,7 +227,8 @@ impl Module { /// Opposed to [`Module::new`], this function is not compatible with /// the WebAssembly text format (if the "wat" feature is enabled for /// this crate). - pub fn from_binary(_store: &impl AsStoreRef, binary: &[u8]) -> Result { + pub fn from_binary(_store: &impl AsStoreRef, binary: impl IntoBytes) -> Result { + let binary = binary.into_bytes(); // // Self::validate(store, binary)?; unsafe { Self::from_binary_unchecked(_store, binary) } @@ -206,16 +241,28 @@ impl Module { /// This is safe since the JS vm should be safe already. /// We maintain the `unsafe` to preserve the same API as Wasmer pub unsafe fn from_binary_unchecked( - _store: &impl AsStoreRef, - binary: &[u8], + store: &impl AsStoreRef, + binary: impl IntoBytes, ) -> Result { - let js_bytes = Uint8Array::view(binary); + let binary = binary.into_bytes(); + let js_bytes = Uint8Array::view(&binary[..]); let module = WebAssembly::Module::new(&js_bytes.into()).unwrap(); + Self::from_js_module(store, module, Bytes::from(binary)) + } + + /// Creates a new WebAssembly module skipping any kind of validation from a javascript module + /// + pub unsafe fn from_js_module( + _store: &impl AsStoreRef, + module: WebAssembly::Module, + binary: impl IntoBytes, + ) -> Result { + let binary = binary.into_bytes(); // The module is now validated, so we can safely parse it's types #[cfg(feature = "wasm-types-polyfill")] let (type_hints, name) = { - let info = crate::js::module_info_polyfill::translate_module(binary).unwrap(); + let info = crate::js::module_info_polyfill::translate_module(&binary[..]).unwrap(); ( Some(ModuleTypeHints { @@ -241,7 +288,7 @@ impl Module { type_hints, name, #[cfg(feature = "js-serializable-module")] - raw_bytes: Some(binary.into_bytes()), + raw_bytes: Some(binary), }) } @@ -251,8 +298,9 @@ impl Module { /// This validation is normally pretty fast and checks the enabled /// WebAssembly features in the Store Engine to assure deterministic /// validation of the Module. - pub fn validate(_store: &impl AsStoreRef, binary: &[u8]) -> Result<(), CompileError> { - let js_bytes = unsafe { Uint8Array::view(binary) }; + pub fn validate(_store: &impl AsStoreRef, binary: impl IntoBytes) -> Result<(), CompileError> { + let binary = binary.into_bytes(); + let js_bytes = unsafe { Uint8Array::view(&binary[..]) }; match WebAssembly::validate(&js_bytes.into()) { Ok(true) => Ok(()), _ => Err(CompileError::Validate("Invalid Wasm file".to_owned())), @@ -273,10 +321,64 @@ impl Module { InstantiationError::DifferentStores, ))); } - - let imports_js_obj = imports.as_jsvalue(store).into(); - Ok(WebAssembly::Instance::new(&self.module, &imports_js_obj) - .map_err(|e: JsValue| -> RuntimeError { e.into() })?) + let imports_object = js_sys::Object::new(); + let mut import_externs: Vec = vec![]; + for import_type in self.imports() { + let resolved_import = imports.get_export(import_type.module(), import_type.name()); + #[allow(unused_variables)] + if let wasmer_types::ExternType::Memory(mem_ty) = import_type.ty() { + if resolved_import.is_some() { + #[cfg(feature = "tracing")] + debug!("imported shared memory {:?}", &mem_ty); + } else { + #[cfg(feature = "tracing")] + warn!( + "Error while importing {0:?}.{1:?}: memory. Expected {2:?}", + import_type.module(), + import_type.name(), + import_type.ty(), + ); + } + } + if let Some(import) = resolved_import { + let val = js_sys::Reflect::get(&imports_object, &import_type.module().into())?; + if !val.is_undefined() { + // If the namespace is already set + js_sys::Reflect::set( + &val, + &import_type.name().into(), + &import.as_jsvalue(&store.as_store_ref()), + )?; + } else { + // If the namespace doesn't exist + let import_namespace = js_sys::Object::new(); + js_sys::Reflect::set( + &import_namespace, + &import_type.name().into(), + &import.as_jsvalue(&store.as_store_ref()), + )?; + js_sys::Reflect::set( + &imports_object, + &import_type.module().into(), + &import_namespace.into(), + )?; + } + import_externs.push(import); + } else { + #[cfg(feature = "tracing")] + warn!("import not found {}:{}", import_type.module(), import_type.name()); + } + // in case the import is not found, the JS Wasm VM will handle + // the error for us, so we don't need to handle it + } + Ok(( + StoreHandle::new( + store.as_store_mut().objects_mut(), + WebAssembly::Instance::new(&self.module, &imports_object) + .map_err(|e: JsValue| -> RuntimeError { e.into() })?, + ), + import_externs, + )) } /// Returns the name of the current module. @@ -415,7 +517,8 @@ impl Module { let imports = WebAssembly::Module::imports(&self.module); let iter = imports .iter() - .map(move |val| { + .enumerate() + .map(move |(i, val)| { let module = Reflect::get(val.as_ref(), &"module".into()) .unwrap() .as_string() @@ -428,24 +531,34 @@ impl Module { .unwrap() .as_string() .unwrap(); - let extern_type = match kind.as_str() { - "function" => { - let func_type = FunctionType::new(vec![], vec![]); - ExternType::Function(func_type) - } - "global" => { - let global_type = GlobalType::new(Type::I32, Mutability::Const); - ExternType::Global(global_type) - } - "memory" => { - let memory_type = MemoryType::new(Pages(1), None, false); - ExternType::Memory(memory_type) - } - "table" => { - let table_type = TableType::new(Type::FuncRef, 1, None); - ExternType::Table(table_type) + let type_hint = self + .type_hints + .as_ref() + .map(|hints| hints.imports.get(i).unwrap().clone()); + let extern_type = if let Some(hint) = type_hint { + hint + } else { + match kind.as_str() { + "function" => { + let func_type = FunctionType::new(vec![], vec![]); + ExternType::Function(func_type) + } + "global" => { + let global_type = GlobalType::new(Type::I32, Mutability::Const); + ExternType::Global(global_type) + } + "memory" => { + // The javascript API does not yet expose these properties so without + // the type_hints we don't know what memory to import. + let memory_type = MemoryType::new(Pages(1), None, false); + ExternType::Memory(memory_type) + } + "table" => { + let table_type = TableType::new(Type::FuncRef, 1, None); + ExternType::Table(table_type) + } + _ => unimplemented!(), } - _ => unimplemented!(), }; ImportType::new(&module, &field, extern_type) }) @@ -557,26 +670,26 @@ impl Module { ExportsIterator::new(iter, exports.length() as usize) } - /// Get the custom sections of the module given a `name`. - /// - /// # Important - /// - /// Following the WebAssembly spec, one name can have multiple - /// custom sections. That's why an iterator (rather than one element) - /// is returned. - pub fn custom_sections<'a>(&'a self, name: &'a str) -> impl Iterator> + 'a { - // TODO: implement on JavaScript - DefaultCustomSectionsIterator {} + /// Returns true if the module is still ok - this will be + /// false if the module was passed between threads in a + /// way that it became undefined (JS does not share objects + /// between threads except via a post_message()) + pub fn is_ok(&self) -> bool { + let val = JsValue::from(&self.module); + !val.is_undefined() && + !val.is_null() } -} -pub struct DefaultCustomSectionsIterator {} - -impl Iterator for DefaultCustomSectionsIterator { - type Item = Box<[u8]>; - fn next(&mut self) -> Option { - None - } + // /// Get the custom sections of the module given a `name`. + // /// + // /// # Important + // /// + // /// Following the WebAssembly spec, one name can have multiple + // /// custom sections. That's why an iterator (rather than one element) + // /// is returned. + // pub fn custom_sections<'a>(&'a self, name: &'a str) -> impl Iterator> + 'a { + // unimplemented!(); + // } } impl fmt::Debug for Module { diff --git a/lib/api/src/js/native.rs b/lib/api/src/js/native.rs index 69b350652a1..8efbcaedbf9 100644 --- a/lib/api/src/js/native.rs +++ b/lib/api/src/js/native.rs @@ -15,9 +15,11 @@ use crate::js::{FromToNativeWasmType, RuntimeError, WasmTypeList}; // use std::panic::{catch_unwind, AssertUnwindSafe}; use crate::js::export::VMFunction; use crate::js::types::param_from_js; +use crate::js::types::AsJs; use js_sys::Array; use std::iter::FromIterator; use wasm_bindgen::JsValue; +use wasmer_types::RawValue; /// A WebAssembly function that can be called natively /// (using the Native ABI). @@ -64,11 +66,34 @@ macro_rules! impl_native_traits { pub fn call(&self, mut store: &mut impl AsStoreMut, $( $x: $x, )* ) -> Result where $( $x: FromToNativeWasmType + crate::js::NativeWasmTypeInto, )* { - let params_list: Vec = vec![ $( JsValue::from_f64($x.into_raw(&mut store))),* ]; - let results = self.handle.get(store.as_store_ref().objects()).function.apply( - &JsValue::UNDEFINED, - &Array::from_iter(params_list.iter()) - )?; + #[allow(unused_unsafe)] + let params_list: Vec = unsafe { + vec![ $( RawValue { f64: $x.into_raw(store) } ),* ] + }; + let params_list: Vec = params_list + .into_iter() + .map(|a| a.as_jsvalue(&store.as_store_ref())) + .collect(); + let results = { + let mut r; + loop { + r = self.handle.get(store.as_store_ref().objects()).function.apply( + &JsValue::UNDEFINED, + &Array::from_iter(params_list.iter()) + ); + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { + match callback(store_mut) { + Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } + Ok(wasmer_types::OnCalledAction::Finish) => { break; } + Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) }, + Err(trap) => { return Err(RuntimeError::user(trap)) }, + } + } + break; + } + r? + }; let mut rets_list_array = Rets::empty_array(); let mut_rets = rets_list_array.as_mut() as *mut [f64] as *mut f64; match Rets::size() { @@ -92,7 +117,6 @@ macro_rules! impl_native_traits { } Ok(unsafe { Rets::from_array(store, rets_list_array) }) } - } #[allow(unused_parens)] diff --git a/lib/api/src/js/store.rs b/lib/api/src/js/store.rs index 66d6d58ec8c..5115ecdffa4 100644 --- a/lib/api/src/js/store.rs +++ b/lib/api/src/js/store.rs @@ -1,10 +1,12 @@ use std::fmt; +use wasmer_types::OnCalledAction; /// We require the context to have a fixed memory address for its lifetime since /// various bits of the VM have raw pointers that point back to it. Hence we /// wrap the actual context in a box. pub(crate) struct StoreInner { pub(crate) objects: StoreObjects, + pub(crate) on_called: Option Result>>>, } /// The store represents all global state that can be manipulated by @@ -27,6 +29,7 @@ impl Store { Self { inner: Box::new(StoreInner { objects: Default::default(), + on_called: None, }), } } @@ -37,6 +40,11 @@ impl Store { pub fn same(_a: &Self, _b: &Self) -> bool { true } + + /// Returns the ID of this store + pub fn id(&self) -> StoreId { + self.inner.objects.id() + } } impl PartialEq for Store { @@ -102,6 +110,11 @@ impl<'a> StoreRef<'a> { pub fn same(a: &Self, b: &Self) -> bool { a.inner.objects.id() == b.inner.objects.id() } + + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> wasmer_types::StoreSnapshot { + self.inner.objects.save_snapshot() + } } /// A temporary handle to a [`Context`]. @@ -117,6 +130,16 @@ impl<'a> StoreMut<'a> { a.inner.objects.id() == b.inner.objects.id() } + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> wasmer_types::StoreSnapshot { + self.inner.objects.save_snapshot() + } + + /// Restores a snapshot back into the store + pub fn restore_snapshot(&mut self, snapshot: &wasmer_types::StoreSnapshot) { + self.inner.objects.restore_snapshot(snapshot); + } + pub(crate) fn as_raw(&self) -> *mut StoreInner { self.inner as *const StoreInner as *mut StoreInner } @@ -124,6 +147,16 @@ impl<'a> StoreMut<'a> { pub(crate) unsafe fn from_raw(raw: *mut StoreInner) -> Self { Self { inner: &mut *raw } } + + /// Sets the unwind callback which will be invoked when the call finishes + pub fn on_called( + &mut self, + callback: F, + ) + where F: FnOnce(StoreMut<'_>) -> Result> + Send + Sync + 'static, + { + self.inner.on_called.replace(Box::new(callback)); + } } /// Helper trait for a value that is convertible to a [`StoreRef`]. @@ -286,6 +319,22 @@ mod objects { (&mut high[0], &mut low[a.index()]) } } + + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> wasmer_types::StoreSnapshot { + let mut ret = wasmer_types::StoreSnapshot::default(); + for (index, global) in self.globals.iter().enumerate() { + global.save_snapshot(index, &mut ret); + } + ret + } + + /// Serializes the mutable things into a snapshot + pub fn restore_snapshot(&mut self, snapshot: &wasmer_types::StoreSnapshot) { + for (index, global) in self.globals.iter_mut().enumerate() { + global.restore_snapshot(index, snapshot); + } + } } /// Handle to an object managed by a context. @@ -359,6 +408,11 @@ mod objects { self.id } + /// Overrides the store id with a new ID + pub fn set_store_id(&mut self, id: StoreId) { + self.id = id; + } + /// Constructs a `StoreHandle` from a `StoreId` and an `InternalStoreHandle`. /// /// # Safety @@ -446,6 +500,7 @@ mod objects { Instance(NonNull), } + #[allow(dead_code)] impl MaybeInstanceOwned { /// Returns underlying pointer to the VM data. #[allow(dead_code)] diff --git a/lib/api/src/js/types.rs b/lib/api/src/js/types.rs index c7a4efd9f53..b86eada0ca7 100644 --- a/lib/api/src/js/types.rs +++ b/lib/api/src/js/types.rs @@ -54,3 +54,11 @@ impl AsJs for Value { } } } + +impl AsJs for wasmer_types::RawValue { + fn as_jsvalue(&self, _store: &impl AsStoreRef) -> JsValue { + unsafe { + JsValue::from_f64(self.f64) + } + } +} diff --git a/lib/api/src/js/value.rs b/lib/api/src/js/value.rs index 6eb2cb512aa..f6292e1e902 100644 --- a/lib/api/src/js/value.rs +++ b/lib/api/src/js/value.rs @@ -3,6 +3,7 @@ use std::fmt; use std::string::{String, ToString}; use wasmer_types::Type; +use wasmer_types::RawValue; //use crate::ExternRef; use crate::js::externals::function::Function; @@ -106,6 +107,13 @@ impl Value { } } + /// Converts the `Value` into a `RawValue`. + pub unsafe fn as_raw_value(&self, store: &impl AsStoreRef) -> RawValue { + RawValue { + f64: self.as_raw(store) + } + } + /// Converts a `f64` to a `Value`. /// /// # Safety diff --git a/lib/api/src/js/wasm_bindgen_polyfill.rs b/lib/api/src/js/wasm_bindgen_polyfill.rs index 1b5dad63a12..80f89a6a702 100644 --- a/lib/api/src/js/wasm_bindgen_polyfill.rs +++ b/lib/api/src/js/wasm_bindgen_polyfill.rs @@ -16,6 +16,7 @@ extern "C" { /// of the given type and value. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global) + #[allow(unused_doc_comments)] #[wasm_bindgen(constructor, js_namespace = WebAssembly, catch)] pub fn new(global_descriptor: &Object, value: &JsValue) -> Result; @@ -23,6 +24,7 @@ extern "C" { /// returns the value of the global. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global) + #[allow(unused_doc_comments)] #[wasm_bindgen(method, getter, structural, js_namespace = WebAssembly)] pub fn value(this: &Global) -> JsValue; diff --git a/lib/api/src/sys/exports.rs b/lib/api/src/sys/exports.rs index 571419deefb..0141f8cd9ea 100644 --- a/lib/api/src/sys/exports.rs +++ b/lib/api/src/sys/exports.rs @@ -57,6 +57,9 @@ pub enum ExportError { /// This error arises when an export is missing #[error("Missing export {0}")] Missing(String), + /// This error arises when an export is missing + #[error("Serialization failed {0}")] + SerializationFailed(String), } /// Exports is a special kind of map that allows easily unwrapping diff --git a/lib/api/src/sys/externals/function.rs b/lib/api/src/sys/externals/function.rs index 8392e9b4203..774e94c9e09 100644 --- a/lib/api/src/sys/externals/function.rs +++ b/lib/api/src/sys/externals/function.rs @@ -41,6 +41,16 @@ pub struct Function { pub(crate) handle: StoreHandle, } +impl Into +for StoreHandle +{ + fn into(self) -> Function { + Function { + handle: self + } + } +} + impl Function { /// Creates a new host `Function` (dynamic) with the provided signature. /// @@ -401,24 +411,59 @@ impl Function { *slot = arg.as_raw(store); } + // Invoke the call + self.call_wasm_raw(store, trampoline, values_vec, results)?; + Ok(()) + } + + #[cfg(feature = "compiler")] + fn call_wasm_raw( + &self, + store: &mut impl AsStoreMut, + trampoline: VMTrampoline, + mut params: Vec, + results: &mut [Value], + ) -> Result<(), RuntimeError> { + // Call the trampoline. - let vm_function = self.handle.get(store.as_store_ref().objects()); - if let Err(error) = unsafe { - wasmer_call_trampoline( - store.as_store_ref().signal_handler(), - vm_function.anyfunc.as_ptr().as_ref().vmctx, - trampoline, - vm_function.anyfunc.as_ptr().as_ref().func_ptr, - values_vec.as_mut_ptr() as *mut u8, - ) - } { + let result = { + let mut r; + loop { + let vm_function = self.handle.get(store.as_store_ref().objects()); + r = unsafe { + wasmer_call_trampoline( + store.as_store_ref().signal_handler(), + vm_function.anyfunc.as_ptr().as_ref().vmctx, + trampoline, + vm_function.anyfunc.as_ptr().as_ref().func_ptr, + params.as_mut_ptr() as *mut u8, + ) + }; + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { + match callback(store_mut) { + Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } + Ok(wasmer_types::OnCalledAction::Finish) => { break; } + Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) }, + Err(trap) => { + return Err(RuntimeError::user(trap)) + }, + } + } + break; + } + r + }; + if let Err(error) = result + { return Err(RuntimeError::from_trap(error)); } // Load the return values out of `values_vec`. + let signature = self.ty(store); for (index, &value_type) in signature.results().iter().enumerate() { unsafe { - results[index] = Value::from_raw(store, value_type, values_vec[index]); + results[index] = Value::from_raw(store, value_type, params[index]); } } @@ -517,6 +562,27 @@ impl Function { Ok(results.into_boxed_slice()) } + #[doc(hidden)] + #[allow(missing_docs)] + #[cfg(feature = "compiler")] + pub fn call_raw( + &self, + store: &mut impl AsStoreMut, + params: Vec, + ) -> Result, RuntimeError> { + let trampoline = unsafe { + self.handle + .get(store.as_store_ref().objects()) + .anyfunc + .as_ptr() + .as_ref() + .call_trampoline + }; + let mut results = vec![Value::null(); self.result_arity(store)]; + self.call_wasm_raw(store, trampoline, params, &mut results)?; + Ok(results.into_boxed_slice()) + } + pub(crate) fn vm_funcref(&self, store: &impl AsStoreRef) -> VMFuncRef { let vm_function = self.handle.get(store.as_store_ref().objects()); if vm_function.kind == VMFunctionKind::Dynamic { @@ -1283,7 +1349,7 @@ mod inner { let mut store = StoreMut::from_raw(env.raw_store as *mut _); let result = on_host_stack(|| { // println!("func wrapper1"); - panic::catch_unwind(AssertUnwindSafe(|| { + panic::catch_unwind(AssertUnwindSafe(|| { $( let $x = FromToNativeWasmType::from_native(NativeWasmTypeInto::from_abi(&mut store, $x)); )* @@ -1649,4 +1715,4 @@ mod inner { } } */ -} +} \ No newline at end of file diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index 64ab9a136e2..b1c19c11ec1 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -10,7 +10,7 @@ use std::mem::MaybeUninit; use std::slice; #[cfg(feature = "tracing")] use tracing::warn; -use wasmer_types::{Pages, LinearMemory}; +use wasmer_types::{Pages, LinearMemory, WASM_PAGE_SIZE}; use wasmer_vm::{InternalStoreHandle, MemoryError, StoreHandle, VMExtern, VMMemory}; use super::MemoryView; @@ -133,6 +133,38 @@ impl Memory { self.handle.get_mut(store.objects_mut()).grow(delta.into()) } + /// Copies the memory to a new store and returns a memory reference to it + pub fn copy_to_store( + &self, + store: &impl AsStoreRef, + new_store: &mut impl AsStoreMut, + ) -> Result + { + // Create the new memory using the parameters of the existing memory + let view = self.view(store); + let ty = self.ty(store); + let amount = view.data_size() as usize; + + let new_memory = Self::new(new_store, ty)?; + let mut new_view = new_memory.view(&new_store); + let new_view_size = new_view.data_size() as usize; + if amount > new_view_size { + let delta = amount - new_view_size; + let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1; + new_memory.grow(new_store, Pages(pages as u32))?; + new_view = new_memory.view(&new_store); + } + + // Copy the bytes + view.copy_to_memory(amount as u64, &new_view) + .map_err(|err| { + MemoryError::Generic(err.to_string()) + })?; + + // Return the new memory + Ok(new_memory) + } + pub(crate) fn from_vm_extern( store: &impl AsStoreRef, internal: InternalStoreHandle, diff --git a/lib/api/src/sys/externals/memory_view.rs b/lib/api/src/sys/externals/memory_view.rs index a638acb6b97..16150b6cd0d 100644 --- a/lib/api/src/sys/externals/memory_view.rs +++ b/lib/api/src/sys/externals/memory_view.rs @@ -158,4 +158,35 @@ impl<'a> MemoryView<'a> { self.write(offset, &buf)?; Ok(()) } + + /// Copies the memory and returns it as a vector of bytes + pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + let mut new_memory = Vec::new(); + let mut offset = 0; + let mut chunk = [0u8; 40960]; + while offset < self.data_size() { + let remaining = self.data_size() - offset; + let sublen = remaining.min(chunk.len() as u64) as usize; + self.read(offset, &mut chunk[..sublen])?; + new_memory.extend_from_slice(&chunk[..sublen]); + offset += sublen as u64; + } + Ok(new_memory) + } + + /// Copies the memory to another new memory object + pub fn copy_to_memory(&self, amount: u64, new_memory: &Self) -> Result<(), MemoryAccessError> { + let mut offset = 0; + let mut chunk = [0u8; 40960]; + while offset < amount { + let remaining = amount - offset; + let sublen = remaining.min(chunk.len() as u64) as usize; + self.read(offset, &mut chunk[..sublen])?; + + new_memory.write(offset, &chunk[..sublen])?; + + offset += sublen as u64; + } + Ok(()) + } } diff --git a/lib/api/src/sys/imports.rs b/lib/api/src/sys/imports.rs index 80e51f7367d..82d44eb78da 100644 --- a/lib/api/src/sys/imports.rs +++ b/lib/api/src/sys/imports.rs @@ -176,6 +176,36 @@ impl Imports { } Ok(ret) } + + /// Iterates through all the imports in this structure + pub fn iter<'a>(&'a self) -> ImportsIterator<'a> { + ImportsIterator::new(self) + } +} + +pub struct ImportsIterator<'a> { + iter: std::collections::hash_map::Iter<'a, (String, String), Extern> +} + +impl<'a> ImportsIterator<'a> +{ + fn new(imports: &'a Imports) -> Self { + let iter = imports.map.iter(); + Self { iter } + } +} + +impl<'a> Iterator +for ImportsIterator<'a> { + type Item = (&'a str, &'a str, &'a Extern); + + fn next(&mut self) -> Option { + self.iter + .next() + .map(|(k, v)| { + (k.0.as_str(), k.1.as_str(), v) + }) + } } impl IntoIterator for &Imports { diff --git a/lib/api/src/sys/instance.rs b/lib/api/src/sys/instance.rs index ab8e9d5c293..3cc967530ac 100644 --- a/lib/api/src/sys/instance.rs +++ b/lib/api/src/sys/instance.rs @@ -115,11 +115,11 @@ impl Instance { module: &Module, imports: &Imports, ) -> Result { - let imports = imports + let externs = imports .imports_for_module(module) .map_err(InstantiationError::Link)?; - let mut handle = module.instantiate(store, &imports)?; - let exports = module + let mut handle = module.instantiate(store, &externs)?; + let mut exports = module .exports() .map(|export| { let name = export.name().to_string(); @@ -128,6 +128,14 @@ impl Instance { (name, extern_) }) .collect::(); + + // If the memory is imported then also export it for backwards compatibility reasons + // (many will assume the memory is always exported) - later we can remove this + if exports.get_memory("memory").is_err() { + if let Some(memory) = externs.iter().filter(|a| a.ty(store).memory().is_some()).next() { + exports.insert("memory", memory.clone()); + } + } let instance = Self { _handle: StoreHandle::new(store.objects_mut(), handle), @@ -154,9 +162,9 @@ impl Instance { module: &Module, externs: &[Extern], ) -> Result { - let imports = externs.to_vec(); - let mut handle = module.instantiate(store, &imports)?; - let exports = module + let externs = externs.to_vec(); + let mut handle = module.instantiate(store, &externs)?; + let mut exports = module .exports() .map(|export| { let name = export.name().to_string(); @@ -166,6 +174,14 @@ impl Instance { }) .collect::(); + // If the memory is imported then also export it for backwards compatibility reasons + // (many will assume the memory is always exported) - later we can remove this + if exports.get_memory("memory").is_err() { + if let Some(memory) = externs.iter().filter(|a| a.ty(store).memory().is_some()).next() { + exports.insert("memory", memory.clone()); + } + } + let instance = Self { _handle: StoreHandle::new(store.objects_mut(), handle), module: module.clone(), diff --git a/lib/api/src/sys/mem_access.rs b/lib/api/src/sys/mem_access.rs index 28204577a22..d0e274a833b 100644 --- a/lib/api/src/sys/mem_access.rs +++ b/lib/api/src/sys/mem_access.rs @@ -330,6 +330,24 @@ impl<'a, T: ValueType> WasmSlice<'a, T> { } Ok(vec) } + + /// Reads this `WasmSlice` into a `BytesMut` + #[inline] + pub fn read_to_bytes(self) -> Result { + let len = self.len.try_into().expect("WasmSlice length overflow"); + let mut ret = bytes::BytesMut::with_capacity(len); + let bytes = unsafe { + slice::from_raw_parts_mut( + ret.as_mut_ptr() as *mut MaybeUninit, + len * mem::size_of::(), + ) + }; + self.buffer.read_uninit(self.offset, bytes)?; + unsafe { + ret.set_len(len); + } + Ok(ret) + } } impl<'a, T: ValueType> fmt::Debug for WasmSlice<'a, T> { diff --git a/lib/api/src/sys/mod.rs b/lib/api/src/sys/mod.rs index e88c05b7a8c..ef37ee1ad07 100644 --- a/lib/api/src/sys/mod.rs +++ b/lib/api/src/sys/mod.rs @@ -42,7 +42,7 @@ pub use wasmer_derive::ValueType; pub use wasmer_types::is_wasm; pub use wasmer_types::{ CpuFeature, ExportType, ExternType, FunctionType, GlobalType, ImportType, MemoryType, - Mutability, TableType, Target, Type, + Mutability, TableType, Target, Type, OnCalledAction, StoreSnapshot }; pub use wasmer_types::{ diff --git a/lib/api/src/sys/module.rs b/lib/api/src/sys/module.rs index 4b6807178ae..9a7096f121c 100644 --- a/lib/api/src/sys/module.rs +++ b/lib/api/src/sys/module.rs @@ -7,6 +7,7 @@ use std::fmt; use std::io; use std::path::Path; use std::sync::Arc; +use bytes::Bytes; use thiserror::Error; use wasmer_compiler::Artifact; use wasmer_compiler::ArtifactCreate; @@ -56,43 +57,49 @@ pub struct Module { module_info: Arc, } -pub trait IntoBytes { +pub trait IntoBytes +{ fn into_bytes(self) -> Bytes; } -impl IntoBytes for Bytes { +impl IntoBytes +for Bytes { fn into_bytes(self) -> Bytes { self } } -impl IntoBytes for Vec { +impl IntoBytes +for Vec { fn into_bytes(self) -> Bytes { Bytes::from(self) } } -impl IntoBytes for &[u8] { +impl IntoBytes +for &Vec { fn into_bytes(self) -> Bytes { - Bytes::from(self.to_vec()) + Bytes::from(self.clone()) } } -impl IntoBytes for &[u8; N] { +impl IntoBytes +for &[u8] { fn into_bytes(self) -> Bytes { Bytes::from(self.to_vec()) } } -impl IntoBytes for &str { +impl IntoBytes for &[u8; N] { fn into_bytes(self) -> Bytes { - Bytes::from(self.as_bytes().to_vec()) + Bytes::from(self.to_vec()) } } -impl IntoBytes for Cow<'_, [u8]> { +impl IntoBytes +for &str { fn into_bytes(self) -> Bytes { - Bytes::from(self.to_vec()) + Bytes::from(self.as_bytes().to_vec()) } } @@ -158,15 +165,19 @@ impl Module { /// # } /// ``` #[allow(unreachable_code)] - pub fn new(store: &impl AsStoreRef, bytes: impl AsRef<[u8]>) -> Result { + pub fn new(store: &impl AsStoreRef, bytes: impl IntoBytes) -> Result { + let mut bytes = bytes.into_bytes(); #[cfg(feature = "wat")] - let bytes = wat::parse_bytes(bytes.as_ref()).map_err(|e| { - CompileError::Wasm(WasmError::Generic(format!( - "Error when converting wat: {}", - e - ))) - })?; - Self::from_binary(store, bytes.as_ref()) + if bytes.starts_with(b"\0asm") == false { + let parsed_bytes = wat::parse_bytes(&bytes[..]).map_err(|e| { + CompileError::Wasm(WasmError::Generic(format!( + "Error when converting wat: {}", + e + ))) + })?; + bytes = Bytes::from(parsed_bytes.to_vec()); + } + Self::from_binary(store, bytes) } #[cfg(feature = "compiler")] @@ -178,7 +189,7 @@ impl Module { let file_ref = file.as_ref(); let canonical = file_ref.canonicalize()?; let wasm_bytes = std::fs::read(file_ref)?; - let mut module = Self::new(store, &wasm_bytes)?; + let mut module = Self::new(store, wasm_bytes)?; // Set the module name to the absolute path of the filename. // This is useful for debugging the stack traces. let filename = canonical.as_path().to_str().unwrap(); @@ -192,8 +203,9 @@ impl Module { /// Opposed to [`Module::new`], this function is not compatible with /// the WebAssembly text format (if the "wat" feature is enabled for /// this crate). - pub fn from_binary(store: &impl AsStoreRef, binary: &[u8]) -> Result { - Self::validate(store, binary)?; + pub fn from_binary(store: &impl AsStoreRef, binary: impl IntoBytes) -> Result { + let binary = binary.into_bytes(); + Self::validate(store, binary.clone())?; unsafe { Self::from_binary_unchecked(store, binary) } } @@ -207,8 +219,9 @@ impl Module { /// beforehand. pub unsafe fn from_binary_unchecked( store: &impl AsStoreRef, - binary: &[u8], + binary: impl IntoBytes, ) -> Result { + let binary = binary.into_bytes(); let module = Self::compile(store, binary)?; Ok(module) } @@ -220,16 +233,18 @@ impl Module { /// This validation is normally pretty fast and checks the enabled /// WebAssembly features in the Store Engine to assure deterministic /// validation of the Module. - pub fn validate(store: &impl AsStoreRef, binary: &[u8]) -> Result<(), CompileError> { - store.as_store_ref().engine().validate(binary) + pub fn validate(store: &impl AsStoreRef, binary: impl IntoBytes) -> Result<(), CompileError> { + let binary = binary.into_bytes(); + store.as_store_ref().engine().validate(&binary[..]) } #[cfg(feature = "compiler")] - fn compile(store: &impl AsStoreRef, binary: &[u8]) -> Result { + fn compile(store: &impl AsStoreRef, binary: impl IntoBytes) -> Result { + let binary = binary.into_bytes(); let artifact = store .as_store_ref() .engine() - .compile(binary, store.as_store_ref().tunables())?; + .compile(&binary[..], store.as_store_ref().tunables())?; Ok(Self::from_artifact(artifact)) } @@ -250,7 +265,8 @@ impl Module { /// # } /// ``` pub fn serialize(&self) -> Result { - self.artifact.serialize().map(|bytes| bytes.into()) + self.artifact.serialize() + .map(|bytes| bytes.into()) } /// Serializes a module into a file that the `Engine` @@ -299,10 +315,9 @@ impl Module { /// ``` pub unsafe fn deserialize( store: &impl AsStoreRef, - bytes: impl IntoBytes, + bytes: impl AsRef<[u8]>, ) -> Result { - let bytes = bytes.into_bytes(); - let artifact = store.as_store_ref().engine().deserialize(&bytes)?; + let artifact = store.as_store_ref().engine().deserialize(bytes.as_ref())?; Ok(Self::from_artifact(artifact)) } @@ -484,6 +499,16 @@ impl Module { self.module_info.exports() } + /// Returns true if the module is still ok - this will be + /// false if the module was passed between threads in a + /// way that it became undefined (JS does not share objects + /// between threads except via a post_message()) + pub fn is_ok(&self) -> bool { + // As RUST is a type safe language modules in SYS are always ok + true + } + + /// Get the custom sections of the module given a `name`. /// /// # Important diff --git a/lib/api/src/sys/native.rs b/lib/api/src/sys/native.rs index d5d3bc5aaa5..f7ec125976e 100644 --- a/lib/api/src/sys/native.rs +++ b/lib/api/src/sys/native.rs @@ -8,11 +8,15 @@ //! let add_one_native: TypedFunction = add_one.native().unwrap(); //! ``` use std::marker::PhantomData; +use std::cell::Cell; +use crate::StoreMut; use crate::sys::{ AsStoreMut, FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, WasmTypeList, }; -use wasmer_types::RawValue; +use wasmer_types::{ + RawValue, OnCalledAction +}; /// A WebAssembly function that can be called natively /// (using the Native ABI). @@ -45,6 +49,20 @@ impl Clone for TypedFunction } } +impl From> for Function +where + Args: WasmTypeList, + Rets: WasmTypeList, +{ + fn from(other: TypedFunction) -> Self { + other.func + } +} + +thread_local! { + static ON_CALLED: Cell) -> Result>>>> = Cell::new(None); +} + macro_rules! impl_native_traits { ( $( $x:ident ),* ) => { #[allow(unused_parens, non_snake_case)] @@ -57,6 +75,22 @@ macro_rules! impl_native_traits { #[allow(unused_mut)] #[allow(clippy::too_many_arguments)] pub fn call(&self, store: &mut impl AsStoreMut, $( $x: $x, )* ) -> Result { + // Ensure all parameters come from the same context. + if $(!FromToNativeWasmType::is_from_store(&$x, store) ||)* false { + return Err(RuntimeError::new( + "cross-`Context` values are not supported", + )); + } + + let params_list = vec![ $( $x.to_native().into_raw(store) ),* ]; + self.call_raw(store, params_list) + } + + #[doc(hidden)] + #[allow(missing_docs)] + #[allow(unused_mut)] + #[allow(clippy::too_many_arguments)] + pub fn call_raw(&self, store: &mut impl AsStoreMut, mut params_list: Vec ) -> Result { let anyfunc = unsafe { *self.func .handle @@ -65,15 +99,8 @@ macro_rules! impl_native_traits { .as_ptr() .as_ref() }; - // Ensure all parameters come from the same context. - if $(!FromToNativeWasmType::is_from_store(&$x, store) ||)* false { - return Err(RuntimeError::new( - "cross-`Context` values are not supported", - )); - } // TODO: when `const fn` related features mature more, we can declare a single array // of the correct size here. - let mut params_list = [ $( $x.to_native().into_raw(store) ),* ]; let mut rets_list_array = Rets::empty_array(); let rets_list: &mut [RawValue] = rets_list_array.as_mut(); let using_rets_array; @@ -87,15 +114,31 @@ macro_rules! impl_native_traits { } rets_list.as_mut() }; - unsafe { - wasmer_vm::wasmer_call_trampoline( - store.as_store_ref().signal_handler(), - anyfunc.vmctx, - anyfunc.call_trampoline, - anyfunc.func_ptr, - args_rets.as_mut_ptr() as *mut u8, - ) - }?; + + let mut r; + loop { + r = unsafe { + wasmer_vm::wasmer_call_trampoline( + store.as_store_ref().signal_handler(), + anyfunc.vmctx, + anyfunc.call_trampoline, + anyfunc.func_ptr, + args_rets.as_mut_ptr() as *mut u8, + ) + }; + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { + match callback(store_mut) { + Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; } + Ok(wasmer_types::OnCalledAction::Finish) => { break; } + Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) }, + Err(trap) => { return Err(RuntimeError::user(trap)) }, + } + } + break; + } + r?; + let num_rets = rets_list.len(); if !using_rets_array && num_rets > 0 { let src_pointer = params_list.as_ptr(); diff --git a/lib/api/src/sys/ptr.rs b/lib/api/src/sys/ptr.rs index 1f51c203094..8e7935e2834 100644 --- a/lib/api/src/sys/ptr.rs +++ b/lib/api/src/sys/ptr.rs @@ -97,7 +97,7 @@ impl WasmPtr { /// Checks whether the `WasmPtr` is null. #[inline] - pub fn is_null(self) -> bool { + pub fn is_null(&self) -> bool { self.offset.into() == 0 } @@ -142,19 +142,19 @@ impl WasmPtr { /// Creates a `WasmRef` from this `WasmPtr` which allows reading and /// mutating of the value being pointed to. #[inline] - pub fn deref<'a>(self, view: &'a MemoryView) -> WasmRef<'a, T> { + pub fn deref<'a>(&self, view: &'a MemoryView) -> WasmRef<'a, T> { WasmRef::new(view, self.offset.into()) } /// Reads the address pointed to by this `WasmPtr` in a memory. #[inline] - pub fn read(self, view: &MemoryView) -> Result { + pub fn read(&self, view: &MemoryView) -> Result { self.deref(view).read() } /// Writes to the address pointed to by this `WasmPtr` in a memory. #[inline] - pub fn write(self, view: &MemoryView, val: T) -> Result<(), MemoryAccessError> { + pub fn write(&self, view: &MemoryView, val: T) -> Result<(), MemoryAccessError> { self.deref(view).write(val) } @@ -165,7 +165,7 @@ impl WasmPtr { /// address. #[inline] pub fn slice<'a>( - self, + &self, view: &'a MemoryView, len: M::Offset, ) -> Result, MemoryAccessError> { @@ -178,7 +178,7 @@ impl WasmPtr { /// This last value is not included in the returned vector. #[inline] pub fn read_until( - self, + &self, view: &MemoryView, mut end: impl FnMut(&T) -> bool, ) -> Result, MemoryAccessError> { @@ -202,7 +202,7 @@ impl WasmPtr { /// modified. #[inline] pub fn read_utf8_string( - self, + &self, view: &MemoryView, len: M::Offset, ) -> Result { @@ -215,7 +215,7 @@ impl WasmPtr { /// This method is safe to call even if the memory is being concurrently /// modified. #[inline] - pub fn read_utf8_string_with_nul(self, view: &MemoryView) -> Result { + pub fn read_utf8_string_with_nul(&self, view: &MemoryView) -> Result { let vec = self.read_until(view, |&byte| byte == 0)?; Ok(String::from_utf8(vec)?) } diff --git a/lib/api/src/sys/store.rs b/lib/api/src/sys/store.rs index e8d7ea44a37..eaaeea085b2 100644 --- a/lib/api/src/sys/store.rs +++ b/lib/api/src/sys/store.rs @@ -1,22 +1,43 @@ use crate::sys::tunables::BaseTunables; use std::fmt; -use std::sync::{Arc, RwLock}; #[cfg(feature = "compiler")] use wasmer_compiler::{Engine, EngineBuilder, Tunables}; -use wasmer_vm::{init_traps, TrapHandler, TrapHandlerFn}; +use wasmer_types::{OnCalledAction, StoreSnapshot}; +use wasmer_vm::{init_traps, TrapHandler, TrapHandlerFn, StoreId}; +use derivative::Derivative; use wasmer_vm::StoreObjects; /// We require the context to have a fixed memory address for its lifetime since /// various bits of the VM have raw pointers that point back to it. Hence we /// wrap the actual context in a box. +#[derive(Derivative)] +#[derivative(Debug)] pub(crate) struct StoreInner { pub(crate) objects: StoreObjects, + #[derivative(Debug = "ignore")] #[cfg(feature = "compiler")] pub(crate) engine: Engine, + #[derivative(Debug = "ignore")] #[cfg(feature = "compiler")] pub(crate) tunables: Box, + #[derivative(Debug = "ignore")] pub(crate) trap_handler: Option>>, + #[derivative(Debug = "ignore")] + pub(crate) on_called: Option) -> Result>>>, +} + +impl StoreInner +{ + // Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> StoreSnapshot { + self.objects.save_snapshot() + } + + // Serializes the mutable things into a snapshot + pub fn restore_snapshot(&mut self, snapshot: &StoreSnapshot) { + self.objects.restore_snapshot(snapshot); + } } /// The store represents all global state that can be manipulated by @@ -35,7 +56,6 @@ pub struct Store { pub(crate) inner: Box, #[cfg(feature = "compiler")] engine: Engine, - trap_handler: Arc>>>>, } impl Store { @@ -80,9 +100,9 @@ impl Store { engine: engine.cloned(), tunables: Box::new(tunables), trap_handler: None, + on_called: None, }), engine: engine.cloned(), - trap_handler: Arc::new(RwLock::new(None)), } } @@ -105,6 +125,11 @@ impl Store { pub fn same(a: &Self, b: &Self) -> bool { a.engine.id() == b.engine.id() } + + /// Returns the ID of this store + pub fn id(&self) -> StoreId { + self.inner.objects.id() + } } #[cfg(feature = "compiler")] @@ -116,8 +141,8 @@ impl PartialEq for Store { unsafe impl TrapHandler for Store { fn custom_trap_handler(&self, call: &dyn Fn(&TrapHandlerFn) -> bool) -> bool { - if let Some(handler) = self.trap_handler.read().unwrap().as_ref() { - call(handler) + if let Some(handler) = self.inner.trap_handler.as_ref() { + call(handler.as_ref()) } else { false } @@ -238,13 +263,18 @@ impl<'a> StoreRef<'a> { a.inner.engine.id() == b.inner.engine.id() } + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> StoreSnapshot { + self.inner.save_snapshot() + } + /// The signal handler #[inline] pub fn signal_handler(&self) -> Option<*const TrapHandlerFn<'static>> { self.inner .trap_handler .as_ref() - .map(|handler| handler as *const _) + .map(|handler| handler.as_ref() as *const _) } } @@ -274,6 +304,16 @@ impl<'a> StoreMut<'a> { a.inner.engine.id() == b.inner.engine.id() } + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> StoreSnapshot { + self.inner.save_snapshot() + } + + /// Restores a snapshot back into the store + pub fn restore_snapshot(&mut self, snapshot: &StoreSnapshot) { + self.inner.restore_snapshot(snapshot); + } + #[cfg(feature = "compiler")] pub(crate) fn tunables_and_objects_mut(&mut self) -> (&dyn Tunables, &mut StoreObjects) { (self.inner.tunables.as_ref(), &mut self.inner.objects) @@ -284,7 +324,19 @@ impl<'a> StoreMut<'a> { } pub(crate) unsafe fn from_raw(raw: *mut StoreInner) -> Self { - Self { inner: &mut *raw } + Self { + inner: &mut *raw, + } + } + + /// Sets the unwind callback which will be invoked when the call finishes + pub fn on_called( + &mut self, + callback: F, + ) + where F: FnOnce(StoreMut<'_>) -> Result> + Send + Sync + 'static, + { + self.inner.on_called.replace(Box::new(callback)); } } @@ -316,7 +368,9 @@ impl AsStoreRef for StoreMut<'_> { } impl AsStoreMut for StoreMut<'_> { fn as_store_mut(&mut self) -> StoreMut<'_> { - StoreMut { inner: self.inner } + StoreMut { + inner: self.inner, + } } fn objects_mut(&mut self) -> &mut StoreObjects { &mut self.inner.objects diff --git a/lib/api/tests/reference_types.rs b/lib/api/tests/reference_types.rs index 8746936f06f..c44e4e314c1 100644 --- a/lib/api/tests/reference_types.rs +++ b/lib/api/tests/reference_types.rs @@ -47,15 +47,15 @@ pub mod reference_types { } let func_to_call = - Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut| -> i32 { - env.data_mut().0.store(true, Ordering::SeqCst); + Function::new_typed_with_env(&mut store, &env, |env: FunctionEnvMut| -> i32 { + env.data().0.store(true, Ordering::SeqCst); 343 }); let call_set_value: &Function = instance.exports.get_function("call_set_value")?; let results: Box<[Value]> = call_set_value.call(&mut store, &[Value::FuncRef(Some(func_to_call))])?; assert!(env - .as_mut(&mut store.as_store_mut()) + .as_ref(&store.as_store_ref()) .0 .load(Ordering::SeqCst)); assert_eq!(&*results, &[Value::I32(343)]); diff --git a/lib/c-api/src/wasm_c_api/wasi/mod.rs b/lib/c-api/src/wasm_c_api/wasi/mod.rs index 0d5f9aa7d76..780930658f8 100644 --- a/lib/c-api/src/wasm_c_api/wasi/mod.rs +++ b/lib/c-api/src/wasm_c_api/wasi/mod.rs @@ -986,14 +986,6 @@ pub unsafe extern "C" fn wasi_env_new( #[no_mangle] pub extern "C" fn wasi_env_delete(_state: Option>) {} -/// Set the memory on a [`wasi_env_t`]. -#[no_mangle] -pub unsafe extern "C" fn wasi_env_set_memory(env: &mut wasi_env_t, memory: &wasm_memory_t) { - let mut store_mut = env.store.store_mut(); - let wasi_env = env.inner.data_mut(&mut store_mut); - wasi_env.set_memory(memory.extern_.memory()); -} - #[no_mangle] pub unsafe extern "C" fn wasi_env_read_stdout( env: &mut wasi_env_t, @@ -1001,8 +993,8 @@ pub unsafe extern "C" fn wasi_env_read_stdout( buffer_len: usize, ) -> isize { let inner_buffer = slice::from_raw_parts_mut(buffer as *mut _, buffer_len as usize); - let mut store_mut = env.store.store_mut(); - let state = env.inner.data_mut(&mut store_mut).state(); + let store = env.store.store(); + let state = env.inner.data(&store).state(); if let Ok(mut stdout) = state.stdout() { if let Some(stdout) = stdout.as_mut() { @@ -1181,11 +1173,10 @@ pub unsafe extern "C" fn wasi_env_initialize_instance( store: &mut wasm_store_t, instance: &mut wasm_instance_t, ) -> bool { - let mem = c_try!(instance.inner.exports.get_memory("memory"); otherwise false); wasi_env .inner - .data_mut(&mut store.inner.store_mut()) - .set_memory(mem.clone()); + .initialize(&mut store.inner.store_mut(), &instance.inner) + .unwrap(); true } diff --git a/lib/cache/src/filesystem.rs b/lib/cache/src/filesystem.rs index b16e4601a86..376d19139e9 100644 --- a/lib/cache/src/filesystem.rs +++ b/lib/cache/src/filesystem.rs @@ -98,7 +98,13 @@ impl Cache for FileSystemCache { key.to_string() }; let path = self.path.join(filename); - Module::deserialize_from_file(store, path) + let ret = Module::deserialize_from_file(store, path.clone()); + if ret.is_err() { + // If an error occurs while deserializing then we can not trust it anymore + // so delete the cache file + let _ = std::fs::remove_file(path); + } + ret } fn store(&mut self, key: Hash, module: &Module) -> Result<(), Self::SerializeError> { diff --git a/lib/cli-compiler/src/commands/compile.rs b/lib/cli-compiler/src/commands/compile.rs index bcffabff54f..fa9928d3f05 100644 --- a/lib/cli-compiler/src/commands/compile.rs +++ b/lib/cli-compiler/src/commands/compile.rs @@ -104,6 +104,7 @@ impl Compile { &target, memory_styles, table_styles, + None )?; artifact.serialize_to_file(self.output.as_ref())?; eprintln!( diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 45987508bb9..97dfc27745d 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -25,21 +25,20 @@ doc = false required-features = ["headless"] [dependencies] -wasmer = { version = "=3.0.0-rc.2", path = "../api", default-features = false } -wasmer-compiler = { version = "=3.0.0-rc.2", path = "../compiler", features = ["compiler", ] } -wasmer-compiler-cranelift = { version = "=3.0.0-rc.2", path = "../compiler-cranelift", optional = true } -wasmer-compiler-singlepass = { version = "=3.0.0-rc.2", path = "../compiler-singlepass", optional = true } -wasmer-compiler-llvm = { version = "=3.0.0-rc.2", path = "../compiler-llvm", optional = true } -wasmer-emscripten = { version = "=3.0.0-rc.2", path = "../emscripten", optional = true } -wasmer-vm = { version = "=3.0.0-rc.2", path = "../vm" } -wasmer-wasi = { version = "=3.0.0-rc.2", path = "../wasi", optional = true } -wasmer-wasi-experimental-io-devices = { version = "=3.0.0-rc.2", path = "../wasi-experimental-io-devices", optional = true, features = ["link_external_libs"] } -wasmer-wast = { version = "=3.0.0-rc.2", path = "../../tests/lib/wast", optional = true } -wasmer-cache = { version = "=3.0.0-rc.2", path = "../cache", optional = true } -wasmer-types = { version = "=3.0.0-rc.2", path = "../types" } -wasmer-registry = { version = "=3.0.0-rc.2", path = "../registry" } -wasmer-object = { version = "=3.0.0-rc.2", path = "../object", optional = true } -wasmer-vfs = { version = "=3.0.0-rc.2", path = "../vfs", default-features = false, features = ["host-fs"] } +wasmer = { version = "=3.0.0-beta", path = "../api", default-features = false } +wasmer-compiler = { version = "=3.0.0-beta", path = "../compiler", features = ["compiler", ] } +wasmer-compiler-cranelift = { version = "=3.0.0-beta", path = "../compiler-cranelift", optional = true } +wasmer-compiler-singlepass = { version = "=3.0.0-beta", path = "../compiler-singlepass", optional = true } +wasmer-compiler-llvm = { version = "=3.0.0-beta", path = "../compiler-llvm", optional = true } +wasmer-emscripten = { version = "=3.0.0-beta", path = "../emscripten", optional = true } +wasmer-vm = { version = "=3.0.0-beta", path = "../vm", features = [ "tracing" ] } +wasmer-wasi = { version = "=3.0.0-beta", path = "../wasi", features = [], optional = true } +wasmer-wasi-experimental-io-devices = { version = "=3.0.0-beta", path = "../wasi-experimental-io-devices", optional = true, features = ["link_external_libs"] } +wasmer-wast = { version = "=3.0.0-beta", path = "../../tests/lib/wast", optional = true } +wasmer-cache = { version = "=3.0.0-beta", path = "../cache", optional = true } +wasmer-types = { version = "=3.0.0-beta", path = "../types" } +wasmer-object = { version = "=3.0.0-beta", path = "../object", optional = true } +wasmer-vfs = { version = "=3.0.0-beta", path = "../vfs", default-features = false, features = ["host-fs"] } atty = "0.2" colored = "2.0" anyhow = "1.0" @@ -101,6 +100,7 @@ webc_runner = ["wasi", "wasmer-wasi/webc_runner", "wasmer-wasi/webc_runner_rt_wa compiler = [ "wasmer-compiler/translator", "wasmer-compiler/compiler", + "wasmer-wasi/compiler" ] wasmer-artifact-create = ["compiler", "wasmer/wasmer-artifact-load", @@ -136,6 +136,7 @@ singlepass = [ cranelift = [ "wasmer-compiler-cranelift", "compiler", + "wasmer-wasi/compiler-cranelift" ] llvm = [ "wasmer-compiler-llvm", @@ -145,6 +146,7 @@ debug = ["fern", "log", "wasmer-wasi/logging"] disable-all-logging = ["wasmer-wasi/disable-all-logging"] headless = [] headless-minimal = ["headless", "disable-all-logging", "wasi"] +termios = ["wasmer-wasi/host-termios"] # Optional enable-serde = [ diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index 4b06d9b7c81..4036d20cc19 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -524,6 +524,9 @@ impl Run { name, suggestion ), + ExportError::SerializationFailed(err) => { + anyhow!("Failed to serialize the module - {}", err) + } } } })? diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index d913e5119c7..4ff31eaa080 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -1,11 +1,16 @@ use crate::utils::{parse_envvar, parse_mapdir}; use anyhow::Result; -use std::collections::BTreeSet; +use wasmer_vfs::FileSystem; +use wasmer_wasi::fs::{PassthruFileSystem, RootFileSystemBuilder, TtyFile, SpecialFile}; +use wasmer_wasi::types::__WASI_STDIN_FILENO; +use std::collections::HashMap; +use std::sync::Arc; +use std::{collections::BTreeSet, path::Path}; use std::path::PathBuf; use wasmer::{AsStoreMut, FunctionEnv, Instance, Module, RuntimeError, Value}; use wasmer_wasi::{ - get_wasi_versions, import_object_for_all_wasi_versions, is_wasix_module, WasiEnv, WasiError, - WasiState, WasiVersion, + get_wasi_versions, import_object_for_all_wasi_versions, WasiEnv, WasiError, + WasiState, WasiVersion, is_wasix_module, default_fs_backing, PluggableRuntimeImplementation, }; use clap::Parser; @@ -33,6 +38,14 @@ pub struct Wasi { )] pub(crate) env_vars: Vec<(String, String)>, + /// List of other containers this module depends on + #[clap(long = "use", name = "USE")] + uses: Vec, + + /// List of injected atoms + #[clap(long = "map-command", name = "MAPCMD")] + map_commands: Vec, + /// Enable experimental IO devices #[cfg(feature = "experimental-io-devices")] #[cfg_attr( @@ -77,19 +90,59 @@ impl Wasi { /// Helper function for instantiating a module with Wasi imports for the `Run` command. pub fn instantiate( &self, - store: &mut impl AsStoreMut, + mut store: &mut impl AsStoreMut, module: &Module, program_name: String, args: Vec, ) -> Result<(FunctionEnv, Instance)> { let args = args.iter().cloned().map(|arg| arg.into_bytes()); + let map_commands = self.map_commands + .iter() + .map(|map| map.split_once("=").unwrap()) + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect::>(); + + let runtime = Arc::new(PluggableRuntimeImplementation::default()); + let mut wasi_state_builder = WasiState::new(program_name); wasi_state_builder .args(args) .envs(self.env_vars.clone()) - .preopen_dirs(self.pre_opened_directories.clone())? - .map_dirs(self.mapped_dirs.clone())?; + .uses(self.uses.clone()) + .runtime(&runtime) + .map_commands(map_commands.clone()); + + if is_wasix_module(module) + { + // If we preopen anything from the host then shallow copy it over + let root_fs = RootFileSystemBuilder::new() + .with_tty(Box::new(TtyFile::new(runtime.clone(), Box::new(SpecialFile::new(__WASI_STDIN_FILENO))))) + .build(); + if self.mapped_dirs.len() > 0 { + let fs_backing: Arc = + Arc::new(PassthruFileSystem::new(default_fs_backing())); + for (src, dst) in self.mapped_dirs.clone() { + let src = match src.starts_with("/") { + true => src, + false => format!("/{}", src) + }; + root_fs.mount(PathBuf::from(src), &fs_backing, dst)?; + } + } + + // Open the root of the new filesystem + wasi_state_builder + .set_sandbox_fs(root_fs) + .preopen_dir(Path::new("/")) + .unwrap() + .map_dir(".", "/")?; + } else { + wasi_state_builder + .set_fs(default_fs_backing()) + .preopen_dirs(self.pre_opened_directories.clone())? + .map_dirs(self.mapped_dirs.clone())?; + } #[cfg(feature = "experimental-io-devices")] { @@ -99,16 +152,12 @@ impl Wasi { } } - let wasi_env = wasi_state_builder.finalize(store)?; - wasi_env.env.as_mut(store).state.fs.is_wasix.store( - is_wasix_module(module), - std::sync::atomic::Ordering::Release, - ); + let mut wasi_env = wasi_state_builder.finalize(store)?; let mut import_object = import_object_for_all_wasi_versions(store, &wasi_env.env); - import_object.import_shared_memory(module, store); + import_object.import_shared_memory(module, &mut store); + let instance = Instance::new(store, module, &import_object)?; - let memory = instance.exports.get_memory("memory")?; - wasi_env.data_mut(store).set_memory(memory.clone()); + wasi_env.initialize(&mut store, &instance)?; Ok((wasi_env.env, instance)) } diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c index 4fe6a7f9402..89e2eff04d8 100644 --- a/lib/cli/src/commands/wasmer_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -187,23 +187,11 @@ int main(int argc, char *argv[]) { } #ifdef WASI - // Read the exports. - wasm_extern_vec_t exports; - wasm_instance_exports(instance, &exports); - wasm_memory_t* mem = NULL; - for (size_t i = 0; i < exports.size; i++) { - mem = wasm_extern_as_memory(exports.data[i]); - if (mem) { - break; - } - } - - if (!mem) { - fprintf(stderr, "Failed to create instance: Could not find memory in exports\n"); + if (!wasi_env_initialize_instance(wasi_env, store, instance)) { + fprintf(stderr, "Failed to initialize env\n"); print_wasmer_error(); return -1; } - wasi_env_set_memory(wasi_env, mem); own wasm_func_t *start_function = wasi_get_start_function(instance); if (!start_function) { diff --git a/lib/cli/src/commands/wasmer_static_create_exe_main.c b/lib/cli/src/commands/wasmer_static_create_exe_main.c index e2d21d8a529..d6f59e62407 100644 --- a/lib/cli/src/commands/wasmer_static_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_static_create_exe_main.c @@ -179,23 +179,11 @@ int main(int argc, char *argv[]) { } #ifdef WASI - // Read the exports. - wasm_extern_vec_t exports; - wasm_instance_exports(instance, &exports); - wasm_memory_t* mem = NULL; - for (size_t i = 0; i < exports.size; i++) { - mem = wasm_extern_as_memory(exports.data[i]); - if (mem) { - break; - } - } - - if (!mem) { - fprintf(stderr, "Failed to create instance: Could not find memory in exports\n"); + if (!wasi_env_initialize_instance(wasi_env, store, instance)) { + fprintf(stderr, "Failed to initialize env\n"); print_wasmer_error(); return -1; } - wasi_env_set_memory(wasi_env, mem); own wasm_func_t *start_function = wasi_get_start_function(instance); if (!start_function) { diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index 94b7857502d..13068acabee 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -52,6 +52,10 @@ impl CraneliftCompiler { } impl Compiler for CraneliftCompiler { + fn name(&self) -> &str { + "cranelift" + } + /// Get the middlewares for this compiler fn get_middlewares(&self) -> &[Arc] { &self.config.middlewares diff --git a/lib/compiler-cranelift/src/func_environ.rs b/lib/compiler-cranelift/src/func_environ.rs index afb6010d4d9..4cdcc565773 100644 --- a/lib/compiler-cranelift/src/func_environ.rs +++ b/lib/compiler-cranelift/src/func_environ.rs @@ -973,7 +973,7 @@ impl<'module_environment> BaseFuncEnvironment for FuncEnvironment<'module_enviro }, false, ) - } + }, MemoryStyle::Static { bound, offset_guard_size, diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index 7ff8ba78b5e..ac9ea5508a9 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -187,6 +187,10 @@ impl LLVMCompiler { } impl Compiler for LLVMCompiler { + fn name(&self) -> &str { + "llvm" + } + /// Get the middlewares for this compiler fn get_middlewares(&self) -> &[Arc] { &self.config.middlewares diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index a2694c9f727..bd014f70f07 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -56,6 +56,10 @@ impl SinglepassCompiler { } impl Compiler for SinglepassCompiler { + fn name(&self) -> &str { + "singlepass" + } + /// Get the middlewares for this compiler fn get_middlewares(&self) -> &[Arc] { &self.config.middlewares diff --git a/lib/compiler/src/artifact_builders/artifact_builder.rs b/lib/compiler/src/artifact_builders/artifact_builder.rs index 098cd0b8fd4..8270d982e97 100644 --- a/lib/compiler/src/artifact_builders/artifact_builder.rs +++ b/lib/compiler/src/artifact_builders/artifact_builder.rs @@ -17,7 +17,7 @@ use wasmer_types::SerializeError; use wasmer_types::{ CompileError, CpuFeature, CustomSection, Dwarf, FunctionIndex, LocalFunctionIndex, MemoryIndex, MemoryStyle, ModuleInfo, OwnedDataInitializer, Relocation, SectionIndex, SignatureIndex, - TableIndex, TableStyle, Target, + TableIndex, TableStyle, Target, Pages, }; use wasmer_types::{ CompiledFunctionFrameInfo, FunctionBody, SerializableCompilation, SerializableModule, @@ -45,6 +45,7 @@ impl ArtifactBuild { target: &Target, memory_styles: PrimaryMap, table_styles: PrimaryMap, + module_start: Option, ) -> Result { let environ = ModuleEnvironment::new(); let features = inner_engine.features().clone(); @@ -111,6 +112,7 @@ impl ArtifactBuild { compilation: serializable_compilation, compile_info, data_initializers, + module_start, cpu_features: target.cpu_features().as_u64(), }; Ok(Self { serializable }) @@ -136,6 +138,11 @@ impl ArtifactBuild { Self { serializable } } + /// Returns the memory start address for this compiled module + pub fn get_memory_start(&self) -> Option { + self.serializable.module_start.clone() + } + /// Get Functions Bodies ref pub fn get_function_bodies_ref(&self) -> &PrimaryMap { &self.serializable.compilation.function_bodies diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index 3c15bd7cad3..25df3cd0735 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -78,6 +78,9 @@ where /// An implementation of a Compiler from parsed WebAssembly module to Compiled native code. pub trait Compiler: Send { + /// Gets the name of this compiler + fn name(&self) -> &str; + /// Validates a module. /// /// It returns the a succesful Result in case is valid, `CompileError` in case is not. diff --git a/lib/compiler/src/engine/artifact.rs b/lib/compiler/src/engine/artifact.rs index b436c2da8d6..9e55a0c5bb5 100644 --- a/lib/compiler/src/engine/artifact.rs +++ b/lib/compiler/src/engine/artifact.rs @@ -83,6 +83,7 @@ impl Artifact { engine.target(), memory_styles, table_styles, + tunables.module_start(), )?; Self::from_parts(&mut inner_engine, artifact) diff --git a/lib/compiler/src/engine/code_memory.rs b/lib/compiler/src/engine/code_memory.rs index 9c033347032..2cb3ef79995 100644 --- a/lib/compiler/src/engine/code_memory.rs +++ b/lib/compiler/src/engine/code_memory.rs @@ -39,7 +39,7 @@ impl CodeMemory { &mut self.unwind_registry } - /// Allocate a single contiguous block of memory for the functions and custom sections, and copy the data in place. + /// Allocate a single contiguous block of memory at a fixed virtual address for the functions and custom sections, and copy the data in place. #[allow(clippy::type_complexity)] pub fn allocate( &mut self, diff --git a/lib/compiler/src/engine/inner.rs b/lib/compiler/src/engine/inner.rs index 26c1e453db4..2a336a2bfa1 100644 --- a/lib/compiler/src/engine/inner.rs +++ b/lib/compiler/src/engine/inner.rs @@ -36,6 +36,7 @@ pub struct Engine { /// The target for the compiler target: Arc, engine_id: EngineId, + name: String, } impl Engine { @@ -46,9 +47,11 @@ impl Engine { target: Target, features: Features, ) -> Self { + let compiler = compiler_config.compiler(); + let name = format!("engine-{}", compiler.name()); Self { inner: Arc::new(Mutex::new(EngineInner { - compiler: Some(compiler_config.compiler()), + compiler: Some(compiler), features, #[cfg(not(target_arch = "wasm32"))] code_memory: vec![], @@ -57,9 +60,15 @@ impl Engine { })), target: Arc::new(target), engine_id: EngineId::default(), + name, } } + /// Returns the name of this engine + pub fn name(&self) -> &str { + self.name.as_str() + } + /// Create a headless `Engine` /// /// A headless engine is an engine without any compiler attached. @@ -87,6 +96,7 @@ impl Engine { })), target: Arc::new(Target::default()), engine_id: EngineId::default(), + name: format!("engine-headless"), } } @@ -165,6 +175,7 @@ impl Engine { /// # Safety /// /// The file's content must represent a serialized WebAssembly module. + #[allow(dead_code, unused)] pub unsafe fn deserialize_from_file( &self, file_ref: &Path, diff --git a/lib/compiler/src/engine/trap/error.rs b/lib/compiler/src/engine/trap/error.rs index e1c0dab5e62..59ef659d379 100644 --- a/lib/compiler/src/engine/trap/error.rs +++ b/lib/compiler/src/engine/trap/error.rs @@ -45,7 +45,7 @@ struct RuntimeErrorInner { /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`). wasm_trace: Vec, /// The native backtrace - native_trace: Backtrace, + native_trace: Option, } fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) { @@ -191,7 +191,7 @@ impl RuntimeError { inner: Arc::new(RuntimeErrorInner { source, wasm_trace, - native_trace, + native_trace: Some(native_trace), }), } } diff --git a/lib/compiler/src/engine/tunables.rs b/lib/compiler/src/engine/tunables.rs index 207ceec9dca..b9dd376d3b0 100644 --- a/lib/compiler/src/engine/tunables.rs +++ b/lib/compiler/src/engine/tunables.rs @@ -3,7 +3,7 @@ use std::ptr::NonNull; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{ GlobalType, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, MemoryType, - ModuleInfo, TableIndex, TableType, + ModuleInfo, TableIndex, TableType, Pages, }; use wasmer_vm::{InternalStoreHandle, MemoryError, StoreObjects}; use wasmer_vm::{MemoryStyle, TableStyle}; @@ -13,6 +13,11 @@ use wasmer_vm::{VMMemoryDefinition, VMTableDefinition}; /// An engine delegates the creation of memories, tables, and globals /// to a foreign implementor of this trait. pub trait Tunables { + /// Fixed virtual memory address for the compiled module + fn module_start(&self) -> Option { + None + } + /// Construct a `MemoryStyle` for the provided `MemoryType` fn memory_style(&self, memory: &MemoryType) -> MemoryStyle; diff --git a/lib/derive/src/value_type.rs b/lib/derive/src/value_type.rs index 5e9fe23c826..0280f5ead13 100644 --- a/lib/derive/src/value_type.rs +++ b/lib/derive/src/value_type.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use proc_macro_error::abort; use quote::quote; -use syn::{Data, DeriveInput, Fields, Member, Meta, MetaList, NestedMeta}; +use syn::{Data, DeriveInput, Member, Meta, MetaList, NestedMeta, Field}; /// We can only validate types that have a well defined layout. fn check_repr(input: &DeriveInput) { @@ -35,7 +35,7 @@ fn check_repr(input: &DeriveInput) { } /// Zero out any padding bytes between fields. -fn zero_padding(fields: &Fields) -> TokenStream { +fn zero_padding(fields: Vec<&Field>) -> TokenStream { let names: Vec<_> = fields .iter() .enumerate() @@ -93,18 +93,18 @@ pub fn impl_value_type(input: &DeriveInput) -> TokenStream { let struct_name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let fields = match &input.data { - Data::Struct(ds) => &ds.fields, + let zero_padding = match &input.data { + Data::Struct(ds) => zero_padding(ds.fields.iter().collect()), _ => abort!(input, "ValueType can only be derived for structs"), }; - let zero_padding = zero_padding(fields); - quote! { unsafe impl #impl_generics ::wasmer::ValueType for #struct_name #ty_generics #where_clause { #[inline] fn zero_padding_bytes(&self, _bytes: &mut [::core::mem::MaybeUninit]) { - #zero_padding + unsafe { + #zero_padding + } } } } diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index f60f0cc72dc..cbc388d9140 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -25,7 +25,7 @@ use std::sync::{Arc, Mutex, RwLock}; use wasmer::{ imports, namespace, AsStoreMut, ExportError, Exports, Function, FunctionEnv, FunctionEnvMut, FunctionType, Global, Imports, Instance, Memory, MemoryType, Module, Pages, RuntimeError, - Table, TableType, TypedFunction, Value, WasmPtr, + Table, TableType, TypedFunction, Value, WasmPtr, AsStoreRef, }; use wasmer_types::Type as ValType; @@ -131,7 +131,7 @@ impl EmEnv { } } - pub fn set_memory(&mut self, memory: Memory) { + pub fn set_memory(&self, memory: Memory) { let mut w = self.memory.write().unwrap(); *w = Some(memory); } @@ -141,12 +141,13 @@ impl EmEnv { (*self.memory.read().unwrap()).as_ref().cloned().unwrap() } - pub fn set_functions(&mut self, funcs: EmscriptenFunctions) { - self.funcs = Arc::new(Mutex::new(funcs)); + pub fn set_functions(&self, funcs: EmscriptenFunctions) { + let mut w = self.funcs.lock().unwrap(); + *w = funcs; } pub fn set_data( - &mut self, + &self, data: &EmscriptenGlobalsData, mapped_dirs: HashMap, ) { @@ -887,7 +888,7 @@ pub fn run_emscripten_instance( if let Ok(func) = instance.exports.get_typed_function(&env, "setThrew") { emfuncs.set_threw = Some(func); } - env.data_mut().set_functions(emfuncs); + env.data().set_functions(emfuncs); set_up_emscripten(&mut env, instance)?; @@ -928,12 +929,12 @@ fn store_module_arguments(env: &mut FunctionEnvMut, args: Vec<&str>) -> ( } pub fn emscripten_set_up_memory( - store: &mut impl AsStoreMut, + store: &impl AsStoreRef, env: &FunctionEnv, memory: &Memory, globals: &EmscriptenGlobalsData, ) -> Result<(), String> { - env.as_mut(store).set_memory(memory.clone()); + env.as_ref(store).set_memory(memory.clone()); let memory = memory.view(store); let dynamictop_ptr = WasmPtr::::new(globals.dynamictop_ptr).deref(&memory); let dynamic_base = globals.dynamic_base; diff --git a/lib/emscripten/src/memory.rs b/lib/emscripten/src/memory.rs index 2c905e30c88..4294a42cbfc 100644 --- a/lib/emscripten/src/memory.rs +++ b/lib/emscripten/src/memory.rs @@ -110,6 +110,8 @@ pub fn sbrk(mut ctx: FunctionEnvMut, increment: i32) -> i32 { increment, total_memory ); + drop(dynamictop_ptr); + if increment > 0 && new_dynamic_top < old_dynamic_top || new_dynamic_top < 0 { abort_on_cannot_grow_memory_old(ctx); return -1; diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 3f71079c94c..bede706830d 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -69,6 +69,7 @@ mod units; mod utils; mod value; mod vmoffsets; +mod store; pub use crate::compilation::target::{ Aarch64Architecture, Architecture, BinaryFormat, CallingConvention, CpuFeature, Endianness, @@ -104,10 +105,10 @@ pub use value::{RawValue, ValueType}; pub use crate::libcalls::LibCall; pub use crate::memory::{ - MemoryStyle, LinearMemory, VMMemoryDefinition + MemoryStyle, MemoryRole, LinearMemory, VMMemoryDefinition, }; pub use crate::table::TableStyle; -pub use crate::trapcode::TrapCode; +pub use crate::trapcode::{TrapCode, OnCalledAction}; pub use crate::vmoffsets::{TargetSharedSignatureIndex, VMBuiltinFunctionIndex, VMOffsets}; pub use crate::utils::is_wasm; @@ -130,6 +131,8 @@ pub use crate::compilation::symbols::{Symbol, SymbolRegistry}; pub use crate::compilation::trap::TrapInformation; pub use crate::compilation::unwind::CompiledFunctionUnwindInfo; +pub use crate::store::StoreSnapshot; + /// Offset in bytes from the beginning of the function. pub type CodeOffset = u32; diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index 6ebac15e7e8..55ed2c6a8ae 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -63,6 +63,8 @@ pub unsafe trait MemorySize: Copy { + PartialOrd + Clone + Copy + + Sync + + Send + ValueType + Into + From @@ -80,7 +82,8 @@ pub unsafe trait MemorySize: Copy { + TryFrom + Add + Sum - + AddAssign; + + AddAssign + + 'static; /// Type used to pass this value as an argument or return value for a Wasm function. type Native: super::NativeWasmType; @@ -130,6 +133,27 @@ unsafe impl MemorySize for Memory64 { } } +/// Represents different roles that a particular region of memory plays +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum MemoryRole +{ + /// The region is used for storing data (default) + Data, + /// The region is used as a stack + Stack, + /// The region is used to guard against memory access violations + Guard, + /// The region resides on another remote location (holds the reference number for that location) + Remote(u64), +} + +impl Default +for MemoryRole { + fn default() -> Self { + MemoryRole::Data + } +} + /// Represents memory that is used by the WebAsssembly module pub trait LinearMemory where Self: std::fmt::Debug + Send @@ -154,6 +178,15 @@ where Self: std::fmt::Debug + Send /// Attempts to clone this memory (if its clonable) fn try_clone(&self) -> Option>; + + /// Copies this memory to a new memory + fn fork(&mut self) -> Result, MemoryError>; + + /// Marks a region of the memory for a particular role + fn mark_region(&mut self, start: u64, end: u64, role: MemoryRole); + + /// Returns the role of a part of the memory + fn region(&self, pointer: u64) -> MemoryRole; } /// The fields compiled code needs to access to utilize a WebAssembly linear diff --git a/lib/types/src/serialize.rs b/lib/types/src/serialize.rs index 7df4fca9b5f..8bf073597a1 100644 --- a/lib/types/src/serialize.rs +++ b/lib/types/src/serialize.rs @@ -1,3 +1,4 @@ +use crate::Pages; use crate::entity::PrimaryMap; use crate::{ compilation::target::CpuFeature, CompileModuleInfo, CompiledFunctionFrameInfo, CustomSection, @@ -61,6 +62,8 @@ pub struct SerializableModule { pub data_initializers: Box<[OwnedDataInitializer]>, /// CPU Feature flags for this compilation pub cpu_features: u64, + /// The start memory address of this serializable module + pub module_start: Option, } fn to_serialize_error(err: impl std::error::Error) -> SerializeError { diff --git a/lib/types/src/store.rs b/lib/types/src/store.rs new file mode 100644 index 00000000000..4777a9b4d07 --- /dev/null +++ b/lib/types/src/store.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use std::io::Read; + +/// Represents a snapshot of parts of the store that mutate +/// (such as globals and tables) +#[derive(Debug, Default)] +pub struct StoreSnapshot +{ + /// Global values at the time the snapshot was taken + pub globals: HashMap, +} + +impl StoreSnapshot +{ + /// Serializes the snapshot into a set of bytes + pub fn serialize(&self) -> Vec { + let capacity = 32usize * self.globals.len(); + let mut ret = Vec::with_capacity(capacity); + + ret.extend_from_slice(&1u32.to_le_bytes()); + ret.extend_from_slice(&(self.globals.len() as u32).to_le_bytes()); + for (index, val) in self.globals.iter() { + ret.extend_from_slice(&index.to_le_bytes()); + ret.extend_from_slice(&val.to_le_bytes()); + } + ret + } + + /// Deserializes the bytes back into a store snapshot + pub fn deserialize(data: &[u8]) -> std::io::Result { + let mut ret = StoreSnapshot::default(); + + // Read all the sections + let mut reader = data; + loop { + let mut ty_arr = [0u8; 4]; + if let Err(err) = reader.read_exact(&mut ty_arr) { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + break; + } + return Err(err); + } + + let ty = u32::from_le_bytes(ty_arr); + match ty { + 1u32 => { + // Read all the globals + let mut len_arr = [0u8; 4]; + reader.read_exact(&mut len_arr)?; + let len = u32::from_le_bytes(len_arr) as usize; + for _ in 0..len { + // Read the key + let mut key_arr = [0u8; 4]; + reader.read_exact(&mut key_arr)?; + let key = u32::from_le_bytes(key_arr); + + // Read the value + let mut val_arr = [0u8; 16]; + reader.read_exact(&mut val_arr)?; + let val = u128::from_le_bytes(val_arr); + + // Set the value in the snapshot + ret.globals.insert(key, val); + } + }, + _ => { break; } + } + } + + Ok(ret) + } +} diff --git a/lib/types/src/trapcode.rs b/lib/types/src/trapcode.rs index 7403f0f3e56..9572f12c876 100644 --- a/lib/types/src/trapcode.rs +++ b/lib/types/src/trapcode.rs @@ -120,6 +120,19 @@ impl FromStr for TrapCode { } } +/// After the stack is unwound via asyncify what +/// should the call loop do next +#[derive(Debug)] +pub enum OnCalledAction +{ + /// Will call the function again + InvokeAgain, + /// Will return the result of the invocation + Finish, + /// Traps with an error + Trap(Box), +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/vbus/Cargo.toml b/lib/vbus/Cargo.toml index 423b60d9f39..414fbad17b8 100644 --- a/lib/vbus/Cargo.toml +++ b/lib/vbus/Cargo.toml @@ -8,7 +8,11 @@ edition = "2018" [dependencies] thiserror = "1" -wasmer-vfs = { path = "../vfs", version = "=3.0.0-rc.2", default-features = false } +tracing = { version = "0.1" } +typetag = { version = "0.1", optional = true } +slab = { version = "0.4", optional = true } +wasmer = { path = "../api", version = "=3.0.0-beta", default-features = false } +wasmer-vfs = { path = "../vfs", version = "=3.0.0-beta", default-features = false } [features] default = ["mem_fs"] diff --git a/lib/vbus/src/lib.rs b/lib/vbus/src/lib.rs index e51ec11ffac..4ce32b94466 100644 --- a/lib/vbus/src/lib.rs +++ b/lib/vbus/src/lib.rs @@ -1,12 +1,15 @@ use std::fmt; +use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use thiserror::Error; +use wasmer::{Store, FunctionEnvMut}; pub use wasmer_vfs::FileDescriptor; pub use wasmer_vfs::StdioMode; +use wasmer_vfs::VirtualFile; -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] @@ -24,64 +27,75 @@ impl From for CallDescriptor { } } -pub trait VirtualBus: fmt::Debug + Send + Sync + 'static { +pub trait VirtualBus: fmt::Debug + Send + Sync + 'static +where T: SpawnEnvironmentIntrinsics, + T: std::fmt::Debug + Send + Sync + 'static +{ /// Starts a new WAPM sub process - fn new_spawn(&self) -> SpawnOptions; + fn spawn(&self, env: T) -> SpawnOptions { + SpawnOptions::new( + Box::new(DefaultVirtualBusSpawner::default()), + env + ) + } /// Creates a listener thats used to receive BUS commands - fn listen(&self) -> Result>; + fn listen<'a>(&'a self) -> Result<&'a dyn VirtualBusListener> { + Err(VirtualBusError::Unsupported) + } } -pub trait VirtualBusSpawner { +pub trait VirtualBusSpawner { /// Spawns a new WAPM process by its name - fn spawn(&mut self, name: &str, config: &SpawnOptionsConfig) -> Result; + fn spawn<'a>(&self, parent_ctx: Option<&FunctionEnvMut<'a, T>>, name: &str, store: Store, config: SpawnOptionsConfig, fallback: &dyn VirtualBusSpawner) -> Result { + fallback.spawn(parent_ctx, name, store, config, &mut UnsupportedVirtualBusSpawner::default()) + } } -#[derive(Debug, Clone)] -pub struct SpawnOptionsConfig { - reuse: bool, - chroot: bool, - args: Vec, - preopen: Vec, - stdin_mode: StdioMode, - stdout_mode: StdioMode, - stderr_mode: StdioMode, - working_dir: String, - remote_instance: Option, - access_token: Option, -} - -impl SpawnOptionsConfig { - pub const fn reuse(&self) -> bool { - self.reuse +#[derive(Debug, Default)] +pub struct UnsupportedVirtualBusSpawner { } +impl VirtualBusSpawner +for UnsupportedVirtualBusSpawner { + fn spawn<'a>(&self, _parent_ctx: Option<&FunctionEnvMut<'a, T>>, _name: &str, _store: Store, _config: SpawnOptionsConfig, _fallback: &dyn VirtualBusSpawner) -> Result { + Err(VirtualBusError::Unsupported) } +} - pub const fn chroot(&self) -> bool { - self.chroot - } +#[derive(Debug, Clone)] +pub struct SpawnOptionsConfig { + pub reuse: bool, + pub env: T, + pub remote_instance: Option, + pub access_token: Option, +} - pub const fn args(&self) -> &Vec { - &self.args - } +pub trait SpawnEnvironmentIntrinsics { + fn args(&self) -> &Vec; - pub const fn preopen(&self) -> &Vec { - &self.preopen - } + fn preopen(&self) -> &Vec; - pub const fn stdin_mode(&self) -> StdioMode { - self.stdin_mode - } + fn stdin_mode(&self) -> StdioMode; + + fn stdout_mode(&self) -> StdioMode; + + fn stderr_mode(&self) -> StdioMode; + + fn working_dir(&self) -> String; +} - pub const fn stdout_mode(&self) -> StdioMode { - self.stdout_mode +impl SpawnOptionsConfig +where T: SpawnEnvironmentIntrinsics +{ + pub fn reuse(&self) -> bool { + self.reuse } - pub const fn stderr_mode(&self) -> StdioMode { - self.stderr_mode + pub fn env(&self) -> &T { + &self.env } - pub fn working_dir(&self) -> &str { - self.working_dir.as_str() + pub fn env_mut(&mut self) -> &mut T { + &mut self.env } pub fn remote_instance(&self) -> Option<&str> { @@ -93,94 +107,104 @@ impl SpawnOptionsConfig { } } -pub struct SpawnOptions { - spawner: Box, - conf: SpawnOptionsConfig, +pub struct SpawnOptions { + spawner: Box>, + conf: SpawnOptionsConfig, } -impl SpawnOptions { - pub fn new(spawner: Box) -> Self { +impl SpawnOptions +where T: SpawnEnvironmentIntrinsics +{ + pub fn new(spawner: Box>, env: T) -> Self { Self { spawner, conf: SpawnOptionsConfig { reuse: false, - chroot: false, - args: Vec::new(), - preopen: Vec::new(), - stdin_mode: StdioMode::Null, - stdout_mode: StdioMode::Null, - stderr_mode: StdioMode::Null, - working_dir: "/".to_string(), + env, remote_instance: None, access_token: None, }, } } - pub fn options(&mut self, options: SpawnOptionsConfig) -> &mut Self { - self.conf = options; - self - } - pub fn reuse(&mut self, reuse: bool) -> &mut Self { - self.conf.reuse = reuse; - self - } - - pub fn chroot(&mut self, chroot: bool) -> &mut Self { - self.conf.chroot = chroot; - self - } - - pub fn args(&mut self, args: Vec) -> &mut Self { - self.conf.args = args; - self - } - - pub fn preopen(&mut self, preopen: Vec) -> &mut Self { - self.conf.preopen = preopen; - self + pub fn conf(self) -> SpawnOptionsConfig { + self.conf } - pub fn stdin_mode(&mut self, stdin_mode: StdioMode) -> &mut Self { - self.conf.stdin_mode = stdin_mode; + pub fn options(mut self, options: SpawnOptionsConfig) -> Self { + self.conf = options; self } - pub fn stdout_mode(&mut self, stdout_mode: StdioMode) -> &mut Self { - self.conf.stdout_mode = stdout_mode; - self + /// Spawns a new bus instance by its reference name + pub fn spawn<'a>(self, parent_ctx: Option<&FunctionEnvMut<'a, T>>, name: &str, store: Store, fallback: &dyn VirtualBusSpawner) -> Result { + self.spawner.spawn(parent_ctx, name, store, self.conf, fallback) } +} - pub fn stderr_mode(&mut self, stderr_mode: StdioMode) -> &mut Self { - self.conf.stderr_mode = stderr_mode; - self - } +pub struct BusSpawnedProcessJoin { + inst: Box, +} - pub fn working_dir(&mut self, working_dir: String) -> &mut Self { - self.conf.working_dir = working_dir; - self +impl BusSpawnedProcessJoin +{ + pub fn new(process: BusSpawnedProcess) -> Self { + Self { + inst: process.inst + } } +} - pub fn remote_instance(&mut self, remote_instance: String) -> &mut Self { - self.conf.remote_instance = Some(remote_instance); - self - } +impl Future +for BusSpawnedProcessJoin { + type Output = Option; - pub fn access_token(&mut self, access_token: String) -> &mut Self { - self.conf.access_token = Some(access_token); - self + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let inst = Pin::new(self.inst.as_mut()); + match inst.poll_ready(cx) { + Poll::Ready(_) => Poll::Ready(self.inst.exit_code()), + Poll::Pending => Poll::Pending + } } +} - /// Spawns a new bus instance by its reference name - pub fn spawn(&mut self, name: &str) -> Result { - self.spawner.spawn(name, &self.conf) - } +/// Signal handles...well...they process signals +pub trait SignalHandlerAbi +where Self: std::fmt::Debug +{ + /// Processes a signal + fn signal(&self, sig: u8); } #[derive(Debug)] pub struct BusSpawnedProcess { /// Reference to the spawned instance - pub inst: Box, + pub inst: Box, + /// Virtual file used for stdin + pub stdin: Option>, + /// Virtual file used for stdout + pub stdout: Option>, + /// Virtual file used for stderr + pub stderr: Option>, + /// The signal handler for this process (if any) + pub signaler: Option>, +} + +impl BusSpawnedProcess +{ + pub fn exited_process(exit_code: u32) -> Self { + Self { + inst: Box::new( + ExitedProcess { + exit_code + } + ), + stdin: None, + stdout: None, + stderr: None, + signaler: None, + } + } } pub trait VirtualBusScope: fmt::Debug + Send + Sync + 'static { @@ -190,12 +214,31 @@ pub trait VirtualBusScope: fmt::Debug + Send + Sync + 'static { pub trait VirtualBusInvokable: fmt::Debug + Send + Sync + 'static { /// Invokes a service within this instance + #[allow(unused_variables)] fn invoke( &self, - topic: String, + topic_hash: u128, format: BusDataFormat, - buf: &[u8], - ) -> Result>; + buf: Vec, + ) -> Box { + Box::new(UnsupportedBusInvoker::default()) + } +} + +#[derive(Debug, Default)] +struct UnsupportedBusInvoker { } + +impl VirtualBusInvoked +for UnsupportedBusInvoker { + #[allow(unused_variables)] + fn poll_invoked(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>> { + Poll::Ready(Err(VirtualBusError::Unsupported)) + } +} + +pub trait VirtualBusInvoked: fmt::Debug + Unpin + 'static { + //// Returns once the bus has been invoked (or failed) + fn poll_invoked(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>>; } pub trait VirtualBusProcess: @@ -204,29 +247,117 @@ pub trait VirtualBusProcess: /// Returns the exit code if the instance has finished fn exit_code(&self) -> Option; - /// Returns a file descriptor used to read the STDIN - fn stdin_fd(&self) -> Option; - - /// Returns a file descriptor used to write to STDOUT - fn stdout_fd(&self) -> Option; - - /// Returns a file descriptor used to write to STDERR - fn stderr_fd(&self) -> Option; + /// Polls to check if the process is ready yet to receive commands + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()>; } pub trait VirtualBusInvocation: - VirtualBusScope + VirtualBusInvokable + fmt::Debug + Send + Sync + 'static + VirtualBusInvokable + fmt::Debug + Send + Sync + Unpin + 'static { /// Polls for new listen events related to this context fn poll_event(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; } +#[derive(Debug)] +pub struct InstantInvocation +{ + val: Option, + err: Option, + call: Option>, +} + +impl InstantInvocation +{ + pub fn response(format: BusDataFormat, data: Vec) -> Self { + Self { + val: Some(BusInvocationEvent::Response { format, data }), + err: None, + call: None + } + } + + pub fn fault(err: VirtualBusError) -> Self { + Self { + val: None, + err: Some(err), + call: None + } + } + + pub fn call(val: Box) -> Self { + Self { + val: None, + err: None, + call: Some(val) + } + } +} + +impl VirtualBusInvoked +for InstantInvocation +{ + fn poll_invoked(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll>> { + if let Some(err) = self.err.take() { + return Poll::Ready(Err(err)); + } + if let Some(val) = self.val.take() { + return Poll::Ready(Ok(Box::new(InstantInvocation { + val: Some(val), + err: None, + call: None, + }))); + } + match self.call.take() { + Some(val) => { + Poll::Ready(Ok(val)) + }, + None => { + Poll::Ready(Err(VirtualBusError::AlreadyConsumed)) + } + } + } +} + +impl VirtualBusInvocation +for InstantInvocation +{ + fn poll_event(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.val.take() { + Some(val) => { + Poll::Ready(val) + }, + None => { + Poll::Ready(BusInvocationEvent::Fault { fault: VirtualBusError::AlreadyConsumed }) + } + } + } +} + +impl VirtualBusInvokable +for InstantInvocation +{ + fn invoke( + &self, + _topic_hash: u128, + _format: BusDataFormat, + _buf: Vec, + ) -> Box { + Box::new( + InstantInvocation { + val: None, + err: Some(VirtualBusError::InvalidTopic), + call: None + } + ) + } +} + #[derive(Debug)] pub enum BusInvocationEvent { /// The server has sent some out-of-band data to you Callback { /// Topic that this call relates to - topic: String, + topic_hash: u128, /// Format of the data we received format: BusDataFormat, /// Data passed in the call @@ -239,34 +370,43 @@ pub enum BusInvocationEvent { /// Data returned by the call data: Vec, }, + /// The service has responded with a fault + Fault { + /// Fault code that was raised + fault: VirtualBusError + } } -pub trait VirtualBusListener: fmt::Debug + Send + Sync + 'static { +pub trait VirtualBusListener: fmt::Debug + Send + Sync + Unpin + 'static { /// Polls for new calls to this service - fn poll_call(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; + fn poll(self: Pin<&Self>, cx: &mut Context<'_>) -> Poll; } #[derive(Debug)] pub struct BusCallEvent { - /// Topic that this call relates to - pub topic: String, + /// Topic hash that this call relates to + pub topic_hash: u128, /// Reference to the call itself - pub called: Box, + pub called: Box, /// Format of the data we received pub format: BusDataFormat, /// Data passed in the call pub data: Vec, } -pub trait VirtualBusCalled: VirtualBusListener + fmt::Debug + Send + Sync + 'static { +pub trait VirtualBusCalled: fmt::Debug + Send + Sync + 'static +{ + /// Polls for new calls to this service + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; + /// Sends an out-of-band message back to the caller - fn callback(&self, topic: String, format: BusDataFormat, buf: &[u8]) -> Result<()>; + fn callback(&self, topic_hash: u128, format: BusDataFormat, buf: Vec); /// Informs the caller that their call has failed - fn fault(self, fault: BusError) -> Result<()>; + fn fault(self: Box, fault: VirtualBusError); /// Finishes the call and returns a particular response - fn reply(self, format: BusDataFormat, buf: &[u8]) -> Result<()>; + fn reply(&self, format: BusDataFormat, buf: Vec); } /// Format that the supplied data is in @@ -281,29 +421,28 @@ pub enum BusDataFormat { } #[derive(Debug, Default)] -pub struct UnsupportedVirtualBus {} - -impl VirtualBus for UnsupportedVirtualBus { - fn new_spawn(&self) -> SpawnOptions { - SpawnOptions::new(Box::new(UnsupportedVirtualBusSpawner::default())) - } +pub struct DefaultVirtualBus +{ +} - fn listen(&self) -> Result> { - Err(BusError::Unsupported) - } +impl VirtualBus for DefaultVirtualBus +where T: SpawnEnvironmentIntrinsics, + T: std::fmt::Debug + Send + Sync + 'static +{ } #[derive(Debug, Default)] -pub struct UnsupportedVirtualBusSpawner {} +pub struct DefaultVirtualBusSpawner +{ +} -impl VirtualBusSpawner for UnsupportedVirtualBusSpawner { - fn spawn(&mut self, _name: &str, _config: &SpawnOptionsConfig) -> Result { - Err(BusError::Unsupported) - } +impl VirtualBusSpawner for DefaultVirtualBusSpawner +where T: std::fmt::Debug + Send + Sync + 'static +{ } #[derive(Error, Copy, Clone, Debug, PartialEq, Eq)] -pub enum BusError { +pub enum VirtualBusError { /// Failed during serialization #[error("serialization failed")] Serialization, @@ -337,6 +476,9 @@ pub enum BusError { /// Call is unsupported #[error("unsupported")] Unsupported, + /// Not found + #[error("not found")] + NotFound, /// Bad request #[error("bad request")] BadRequest, @@ -362,3 +504,30 @@ pub enum BusError { #[error("unknown error found")] UnknownError, } + +#[derive(Debug)] +pub struct ExitedProcess { + pub exit_code: u32, +} + +impl VirtualBusProcess +for ExitedProcess { + fn exit_code(&self) -> Option + { + Some(self.exit_code.clone()) + } + + fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + Poll::Ready(()) + } +} + +impl VirtualBusScope +for ExitedProcess { + fn poll_finished(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + VirtualBusProcess::poll_ready(self, cx) + } +} + +impl VirtualBusInvokable +for ExitedProcess { } diff --git a/lib/vfs/Cargo.toml b/lib/vfs/Cargo.toml index fe95089cded..f2ee209c78b 100644 --- a/lib/vfs/Cargo.toml +++ b/lib/vfs/Cargo.toml @@ -13,8 +13,8 @@ tracing = { version = "0.1" } typetag = { version = "0.1", optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } slab = { version = "0.4", optional = true } -webc = { version = "3.0.1", optional = true } -anyhow = { version = "1.0.66", optional = true } +async-trait = { version = "^0.1" } +lazy_static = "1.4" [features] default = ["host-fs", "mem-fs", "webc-fs", "static-fs"] diff --git a/lib/vfs/src/host_fs.rs b/lib/vfs/src/host_fs.rs index 0cc9ddec068..511a32e2884 100644 --- a/lib/vfs/src/host_fs.rs +++ b/lib/vfs/src/host_fs.rs @@ -421,11 +421,22 @@ impl VirtualFile for File { self.inner.sync_all().map_err(Into::into) } - fn bytes_available(&self) -> Result { + #[cfg(feature = "sys")] + fn bytes_available(&self) -> Result { host_file_bytes_available(self.inner.try_into_filedescriptor()?) } + + #[cfg(not(feature = "sys"))] + fn bytes_available(&self) -> Result { + Err(FsError::InvalidFd) + } + + fn get_special_fd(&self) -> Option { + None + } } +#[allow(dead_code)] #[cfg(unix)] fn host_file_bytes_available(host_fd: FileDescriptor) -> Result { let mut bytes_found: libc::c_int = 0; @@ -441,6 +452,7 @@ fn host_file_bytes_available(host_fd: FileDescriptor) -> Result { } } +#[allow(dead_code)] #[cfg(not(unix))] fn host_file_bytes_available(_host_fd: FileDescriptor) -> Result { unimplemented!("host_file_bytes_available not yet implemented for non-Unix-like targets. This probably means the program tried to use wasi::poll_oneoff") @@ -533,13 +545,29 @@ impl VirtualFile for Stdout { Ok(()) } + #[cfg(feature = "sys")] fn bytes_available(&self) -> Result { host_file_bytes_available(io::stdout().try_into_filedescriptor()?) } + #[cfg(feature = "sys")] fn get_fd(&self) -> Option { io::stdout().try_into_filedescriptor().ok() } + + #[cfg(feature = "js")] + fn bytes_available(&self) -> Result { + Err(FsError::InvalidFd); + } + + #[cfg(feature = "js")] + fn get_fd(&self) -> Option { + None + } + + fn get_special_fd(&self) -> Option { + Some(1) + } } /// A wrapper type around Stderr that implements `VirtualFile` and @@ -629,13 +657,29 @@ impl VirtualFile for Stderr { Ok(()) } + #[cfg(feature = "sys")] fn bytes_available(&self) -> Result { host_file_bytes_available(io::stderr().try_into_filedescriptor()?) } + #[cfg(feature = "sys")] fn get_fd(&self) -> Option { io::stderr().try_into_filedescriptor().ok() } + + #[cfg(feature = "js")] + fn bytes_available(&self) -> Result { + Err(FsError::InvalidFd); + } + + #[cfg(feature = "js")] + fn get_fd(&self) -> Option { + None + } + + fn get_special_fd(&self) -> Option { + Some(2) + } } /// A wrapper type around Stdin that implements `VirtualFile` and @@ -724,11 +768,27 @@ impl VirtualFile for Stdin { Ok(()) } + #[cfg(feature = "sys")] fn bytes_available(&self) -> Result { host_file_bytes_available(io::stdin().try_into_filedescriptor()?) } + #[cfg(feature = "sys")] fn get_fd(&self) -> Option { io::stdin().try_into_filedescriptor().ok() } + + #[cfg(feature = "js")] + fn bytes_available(&self) -> Result { + Err(FsError::InvalidFd); + } + + #[cfg(feature = "js")] + fn get_fd(&self) -> Option { + None + } + + fn get_special_fd(&self) -> Option { + Some(0) + } } diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index 469e8319fcf..fcb8520a3f6 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -3,6 +3,8 @@ use std::ffi::OsString; use std::fmt; use std::io::{self, Read, Seek, Write}; use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::task::Waker; use thiserror::Error; #[cfg(all(not(feature = "host-fs"), not(feature = "mem-fs")))] @@ -22,7 +24,7 @@ pub mod webc_fs; pub type Result = std::result::Result; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct FileDescriptor(usize); @@ -197,7 +199,9 @@ impl OpenOptions { /// This trait relies on your file closing when it goes out of scope via `Drop` //#[cfg_attr(feature = "enable-serde", typetag::serde)] -pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable { +#[async_trait::async_trait] +pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable +{ /// the last time the file was accessed in nanoseconds as a UNIX timestamp fn last_accessed(&self) -> u64; @@ -242,12 +246,67 @@ pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable { Ok(None) } + /// Polls for when read data is available again + /// Defaults to `None` which means no asynchronous IO support - caller + /// must poll `bytes_available_read` instead + fn poll_read_ready(&self, cx: &mut std::task::Context<'_>, register_root_waker: &Arc) -> std::task::Poll> { + use std::ops::Deref; + match self.bytes_available_read() { + Ok(Some(0)) => { + let waker = cx.waker().clone(); + register_root_waker.deref()(waker); + std::task::Poll::Pending + }, + Ok(Some(a)) => std::task::Poll::Ready(Ok(a)), + Ok(None) => std::task::Poll::Ready(Err(FsError::WouldBlock)), + Err(err) => std::task::Poll::Ready(Err(err)), + } + } + + /// Polls for when the file can be written to again + /// Defaults to `None` which means no asynchronous IO support - caller + /// must poll `bytes_available_write` instead + fn poll_write_ready(&self, cx: &mut std::task::Context<'_>, register_root_waker: &Arc) -> std::task::Poll> { + use std::ops::Deref; + match self.bytes_available_write() { + Ok(Some(0)) => { + let waker = cx.waker().clone(); + register_root_waker.deref()(waker); + std::task::Poll::Pending + }, + Ok(Some(a)) => std::task::Poll::Ready(Ok(a)), + Ok(None) => std::task::Poll::Ready(Err(FsError::WouldBlock)), + Err(err) => std::task::Poll::Ready(Err(err)), + } + } + + /// Polls for when the file can be written to again + /// Defaults to `None` which means no asynchronous IO support - caller + /// must poll `bytes_available_write` instead + fn poll_close_ready(&self, cx: &mut std::task::Context<'_>, register_root_waker: &Arc) -> std::task::Poll<()> { + use std::ops::Deref; + match self.is_open() { + true => { + let waker = cx.waker().clone(); + register_root_waker.deref()(waker); + std::task::Poll::Pending + }, + false => std::task::Poll::Ready(()) + } + } + /// Indicates if the file is opened or closed. This function must not block /// Defaults to a status of being constantly open fn is_open(&self) -> bool { true } + /// Returns a special file descriptor when opening this file rather than + /// generating a new one + fn get_special_fd(&self) -> Option { + None + } + /// Used for polling. Default returns `None` because this method cannot be implemented for most types /// Returns the underlying host fd fn get_fd(&self) -> Option { @@ -263,6 +322,9 @@ pub trait Upcastable { fn upcast_any_box(self: Box) -> Box; } +pub trait ClonableVirtualFile: VirtualFile + Clone { +} + impl Upcastable for T { #[inline] fn upcast_any_ref(&'_ self) -> &'_ dyn Any { @@ -344,8 +406,8 @@ pub enum FsError { #[error("connection is not open")] NotConnected, /// The requested file or directory could not be found - #[error("entity not found")] - EntityNotFound, + #[error("entry not found")] + EntryNotFound, /// The requested device couldn't be accessed #[error("can't access device")] NoDevice, @@ -386,7 +448,7 @@ impl From for FsError { io::ErrorKind::InvalidData => FsError::InvalidData, io::ErrorKind::InvalidInput => FsError::InvalidInput, io::ErrorKind::NotConnected => FsError::NotConnected, - io::ErrorKind::NotFound => FsError::EntityNotFound, + io::ErrorKind::NotFound => FsError::EntryNotFound, io::ErrorKind::PermissionDenied => FsError::PermissionDenied, io::ErrorKind::TimedOut => FsError::TimedOut, io::ErrorKind::UnexpectedEof => FsError::UnexpectedEof, @@ -399,6 +461,39 @@ impl From for FsError { } } +impl Into for FsError { + fn into(self) -> io::Error { + let kind = match self { + FsError::AddressInUse => io::ErrorKind::AddrInUse, + FsError::AddressNotAvailable => io::ErrorKind::AddrNotAvailable, + FsError::AlreadyExists => io::ErrorKind::AlreadyExists, + FsError::BrokenPipe => io::ErrorKind::BrokenPipe, + FsError::ConnectionAborted => io::ErrorKind::ConnectionAborted, + FsError::ConnectionRefused => io::ErrorKind::ConnectionRefused, + FsError::ConnectionReset => io::ErrorKind::ConnectionReset, + FsError::Interrupted => io::ErrorKind::Interrupted, + FsError::InvalidData => io::ErrorKind::InvalidData, + FsError::InvalidInput => io::ErrorKind::InvalidInput, + FsError::NotConnected => io::ErrorKind::NotConnected, + FsError::EntryNotFound => io::ErrorKind::NotFound, + FsError::PermissionDenied => io::ErrorKind::PermissionDenied, + FsError::TimedOut => io::ErrorKind::TimedOut, + FsError::UnexpectedEof => io::ErrorKind::UnexpectedEof, + FsError::WouldBlock => io::ErrorKind::WouldBlock, + FsError::WriteZero => io::ErrorKind::WriteZero, + FsError::IOError => io::ErrorKind::Other, + FsError::BaseNotDirectory => io::ErrorKind::Other, + FsError::NotAFile => io::ErrorKind::Other, + FsError::InvalidFd => io::ErrorKind::Other, + FsError::Lock => io::ErrorKind::Other, + FsError::NoDevice => io::ErrorKind::Other, + FsError::DirectoryNotEmpty => io::ErrorKind::Other, + FsError::UnknownError => io::ErrorKind::Other, + }; + kind.into() + } +} + #[derive(Debug)] pub struct ReadDir { // TODO: to do this properly we need some kind of callback to the core FS abstraction diff --git a/lib/vfs/src/mem_fs/file.rs b/lib/vfs/src/mem_fs/file.rs index 18618e8c717..97e1844e3b5 100644 --- a/lib/vfs/src/mem_fs/file.rs +++ b/lib/vfs/src/mem_fs/file.rs @@ -4,6 +4,7 @@ use super::*; use crate::{FileDescriptor, FsError, Result, VirtualFile}; +use std::borrow::Cow; use std::cmp; use std::convert::TryInto; use std::fmt; @@ -17,13 +18,30 @@ use std::str; /// operations to be executed, and then it is checked that the file /// still exists in the file system. After that, the operation is /// delegated to the file itself. -#[derive(Clone)] pub(super) struct FileHandle { inode: Inode, filesystem: FileSystem, readable: bool, writable: bool, append_mode: bool, + cursor: u64, + arc_file: Option>>, +} + +impl Clone +for FileHandle +{ + fn clone(&self) -> Self { + Self { + inode: self.inode.clone(), + filesystem: self.filesystem.clone(), + readable: self.readable, + writable: self.writable, + append_mode: self.append_mode, + cursor: self.cursor, + arc_file: None, + } + } } impl FileHandle { @@ -33,6 +51,7 @@ impl FileHandle { readable: bool, writable: bool, append_mode: bool, + cursor: u64, ) -> Self { Self { inode, @@ -40,13 +59,47 @@ impl FileHandle { readable, writable, append_mode, + cursor, + arc_file: None, + } + } + + fn lazy_load_arc_file_mut(&mut self) -> Result<&mut dyn VirtualFile> + { + if self.arc_file.is_none() { + let fs = match self.filesystem.inner.read() { + Ok(fs) => fs, + _ => return Err(FsError::EntryNotFound), + }; + + let inode = fs.storage.get(self.inode); + match inode { + Some(Node::ArcFile { fs, path, .. }) => { + self.arc_file.replace( + fs.new_open_options() + .read(self.readable) + .write(self.writable) + .append(self.append_mode) + .open(path.as_path()) + ); + }, + _ => return Err(FsError::EntryNotFound) + } } + Ok( + self.arc_file + .as_mut() + .unwrap() + .as_mut() + .map_err(|err| err.clone())? + .as_mut() + ) } } impl VirtualFile for FileHandle { fn last_accessed(&self) -> u64 { - let fs = match self.filesystem.inner.try_read() { + let fs = match self.filesystem.inner.read() { Ok(fs) => fs, _ => return 0, }; @@ -59,7 +112,7 @@ impl VirtualFile for FileHandle { } fn last_modified(&self) -> u64 { - let fs = match self.filesystem.inner.try_read() { + let fs = match self.filesystem.inner.read() { Ok(fs) => fs, _ => return 0, }; @@ -72,7 +125,7 @@ impl VirtualFile for FileHandle { } fn created_time(&self) -> u64 { - let fs = match self.filesystem.inner.try_read() { + let fs = match self.filesystem.inner.read() { Ok(fs) => fs, _ => return 0, }; @@ -87,7 +140,7 @@ impl VirtualFile for FileHandle { } fn size(&self) -> u64 { - let fs = match self.filesystem.inner.try_read() { + let fs = match self.filesystem.inner.read() { Ok(fs) => fs, _ => return 0, }; @@ -95,6 +148,22 @@ impl VirtualFile for FileHandle { let inode = fs.storage.get(self.inode); match inode { Some(Node::File { file, .. }) => file.len().try_into().unwrap_or(0), + Some(Node::ReadOnlyFile { file, .. }) => file.len().try_into().unwrap_or(0), + Some(Node::CustomFile { file, .. }) => { + let file = file.lock().unwrap(); + file.size().try_into().unwrap_or(0) + }, + Some(Node::ArcFile { fs, path, .. }) => { + match self.arc_file.as_ref() { + Some(file) => file + .as_ref() + .map(|file| file.size()) + .unwrap_or(0), + None => fs.new_open_options().read(self.readable).write(self.writable).append(self.append_mode).open(path.as_path()) + .map(|file| file.size()) + .unwrap_or(0), + } + }, _ => 0, } } @@ -103,7 +172,7 @@ impl VirtualFile for FileHandle { let mut fs = self .filesystem .inner - .try_write() + .write() .map_err(|_| FsError::Lock)?; let inode = fs.storage.get_mut(self.inode); @@ -112,6 +181,20 @@ impl VirtualFile for FileHandle { file.buffer .resize(new_size.try_into().map_err(|_| FsError::UnknownError)?, 0); metadata.len = new_size; + }, + Some(Node::CustomFile { file, metadata, .. }) => { + let mut file = file.lock().unwrap(); + file.set_len(new_size)?; + metadata.len = new_size; + }, + Some(Node::ReadOnlyFile { .. }) => { + return Err(FsError::PermissionDenied) + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.set_len(new_size))??; + } _ => return Err(FsError::NotAFile), } @@ -125,7 +208,7 @@ impl VirtualFile for FileHandle { let fs = self .filesystem .inner - .try_read() + .read() .map_err(|_| FsError::Lock)?; // The inode of the file. @@ -145,7 +228,7 @@ impl VirtualFile for FileHandle { None } }) - } + }, _ => None, }) @@ -159,7 +242,7 @@ impl VirtualFile for FileHandle { let mut fs = self .filesystem .inner - .try_write() + .write() .map_err(|_| FsError::Lock)?; // Remove the file from the storage. @@ -176,12 +259,27 @@ impl VirtualFile for FileHandle { let fs = self .filesystem .inner - .try_read() + .read() .map_err(|_| FsError::Lock)?; let inode = fs.storage.get(self.inode); match inode { - Some(Node::File { file, .. }) => Ok(file.buffer.len() - file.cursor), + Some(Node::File { file, .. }) => Ok(file.buffer.len() - (self.cursor as usize)), + Some(Node::ReadOnlyFile { file, .. }) => Ok(file.buffer.len() - (self.cursor as usize)), + Some(Node::CustomFile { file, .. }) => { + let file = file.lock().unwrap(); + file.bytes_available() + }, + Some(Node::ArcFile { fs, path, .. }) => { + match self.arc_file.as_ref() { + Some(file) => file + .as_ref() + .map(|file| file.bytes_available()) + .map_err(|err| err.clone())?, + None => fs.new_open_options().read(self.readable).write(self.writable).append(self.append_mode).open(path.as_path()) + .map(|file| file.bytes_available())?, + } + } _ => Err(FsError::NotAFile), } } @@ -189,6 +287,37 @@ impl VirtualFile for FileHandle { fn get_fd(&self) -> Option { Some(FileDescriptor(self.inode)) } + + fn get_special_fd(&self) -> Option { + let fs = match self + .filesystem + .inner + .read() + { + Ok(a) => a, + Err(_) => { return None; } + }; + + let inode = fs.storage.get(self.inode); + match inode { + Some(Node::CustomFile { file, .. }) => { + let file = file.lock().unwrap(); + file.get_special_fd() + }, + Some(Node::ArcFile { fs, path, .. }) => { + match self.arc_file.as_ref() { + Some(file) => file + .as_ref() + .map(|file| file.get_special_fd()) + .unwrap_or(None), + None => fs.new_open_options().read(self.readable).write(self.writable).append(self.append_mode).open(path.as_path()) + .map(|file| file.get_special_fd()) + .unwrap_or(None), + } + } + _ => None, + } + } } #[cfg(test)] @@ -413,23 +542,38 @@ impl Read for FileHandle { )); } - let mut fs = - self.filesystem.inner.try_write().map_err(|_| { + let fs = + self.filesystem.inner.read().map_err(|_| { io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") })?; - let inode = fs.storage.get_mut(self.inode); - let file = match inode { - Some(Node::File { file, .. }) => file, + let inode = fs.storage.get(self.inode); + match inode { + Some(Node::File { file, .. }) => file.read(buf, &mut self.cursor), + Some(Node::ReadOnlyFile { file, .. }) => file.read(buf, &mut self.cursor), + Some(Node::CustomFile { file, .. }) => { + let mut file = file.lock().unwrap(); + let _ = file.seek(io::SeekFrom::Start(self.cursor as u64)); + let read = file.read(buf)?; + self.cursor += read as u64; + Ok(read) + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.read(buf)) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))? + }, _ => { return Err(io::Error::new( io::ErrorKind::NotFound, format!("inode `{}` doesn't match a file", self.inode), )) } - }; - - file.read(buf) + } } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { @@ -444,22 +588,37 @@ impl Read for FileHandle { } let mut fs = - self.filesystem.inner.try_write().map_err(|_| { + self.filesystem.inner.write().map_err(|_| { io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") })?; let inode = fs.storage.get_mut(self.inode); - let file = match inode { - Some(Node::File { file, .. }) => file, + match inode { + Some(Node::File { file, .. }) => file.read_to_end(buf, &mut self.cursor), + Some(Node::ReadOnlyFile { file, .. }) => file.read_to_end(buf, &mut self.cursor), + Some(Node::CustomFile { file, .. }) => { + let mut file = file.lock().unwrap(); + let _ = file.seek(io::SeekFrom::Start(self.cursor as u64)); + let read = file.read_to_end(buf)?; + self.cursor += read as u64; + Ok(read) + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.read_to_end(buf)) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))? + }, _ => { return Err(io::Error::new( io::ErrorKind::NotFound, format!("inode `{}` doesn't match a file", self.inode), )) } - }; - - file.read_to_end(buf) + } } fn read_to_string(&mut self, buf: &mut String) -> io::Result { @@ -492,23 +651,38 @@ impl Read for FileHandle { )); } - let mut fs = - self.filesystem.inner.try_write().map_err(|_| { + let fs = + self.filesystem.inner.read().map_err(|_| { io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") })?; - let inode = fs.storage.get_mut(self.inode); - let file = match inode { - Some(Node::File { file, .. }) => file, + let inode = fs.storage.get(self.inode); + match inode { + Some(Node::File { file, .. }) => file.read_exact(buf, &mut self.cursor), + Some(Node::ReadOnlyFile { file, .. }) => file.read_exact(buf, &mut self.cursor), + Some(Node::CustomFile { file, .. }) => { + let mut file = file.lock().unwrap(); + let _ = file.seek(io::SeekFrom::Start(self.cursor as u64)); + file.read_exact(buf)?; + self.cursor += buf.len() as u64; + Ok(()) + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.read_exact(buf)) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))? + }, _ => { return Err(io::Error::new( io::ErrorKind::NotFound, format!("inode `{}` doesn't match a file", self.inode), )) } - }; - - file.read_exact(buf) + } } } @@ -533,22 +707,37 @@ impl Seek for FileHandle { } let mut fs = - self.filesystem.inner.try_write().map_err(|_| { + self.filesystem.inner.write().map_err(|_| { io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") })?; let inode = fs.storage.get_mut(self.inode); - let file = match inode { - Some(Node::File { file, .. }) => file, + match inode { + Some(Node::File { file, .. }) => file.seek(position, &mut self.cursor), + Some(Node::ReadOnlyFile { file, .. }) => file.seek(position, &mut self.cursor), + Some(Node::CustomFile { file, .. }) => { + let mut file = file.lock().unwrap(); + let _ = file.seek(io::SeekFrom::Start(self.cursor as u64)); + let pos = file.seek(position)?; + self.cursor = pos; + Ok(pos) + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.seek(position)) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))? + }, _ => { return Err(io::Error::new( io::ErrorKind::NotFound, format!("inode `{}` doesn't match a file", self.inode), )) } - }; - - file.seek(position) + } } } @@ -565,13 +754,39 @@ impl Write for FileHandle { } let mut fs = - self.filesystem.inner.try_write().map_err(|_| { + self.filesystem.inner.write().map_err(|_| { io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") })?; let inode = fs.storage.get_mut(self.inode); - let (file, metadata) = match inode { - Some(Node::File { file, metadata, .. }) => (file, metadata), + let bytes_written = match inode { + Some(Node::File { file, metadata, .. }) => { + let bytes_written = file.write(buf, &mut self.cursor)?; + metadata.len = file.len().try_into().unwrap(); + bytes_written + }, + Some(Node::ReadOnlyFile { file, metadata, .. }) => { + let bytes_written = file.write(buf, &mut self.cursor)?; + metadata.len = file.len().try_into().unwrap(); + bytes_written + }, + Some(Node::CustomFile { file, metadata, .. }) => { + let mut file = file.lock().unwrap(); + let _ = file.seek(io::SeekFrom::Start(self.cursor as u64)); + let bytes_written = file.write(buf)?; + self.cursor += bytes_written as u64; + metadata.len = file.size().try_into().unwrap(); + bytes_written + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.write(buf)) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))?? + }, _ => { return Err(io::Error::new( io::ErrorKind::NotFound, @@ -579,16 +794,39 @@ impl Write for FileHandle { )) } }; - - let bytes_written = file.write(buf)?; - - metadata.len = file.len().try_into().unwrap(); - Ok(bytes_written) } fn flush(&mut self) -> io::Result<()> { - Ok(()) + let mut fs = + self.filesystem.inner.write().map_err(|_| { + io::Error::new(io::ErrorKind::Other, "failed to acquire a write lock") + })?; + + let inode = fs.storage.get_mut(self.inode); + match inode { + Some(Node::File { file, .. }) => file.flush(), + Some(Node::ReadOnlyFile { file, .. }) => file.flush(), + Some(Node::CustomFile { file, .. }) => { + let mut file = file.lock().unwrap(); + file.flush() + }, + Some(Node::ArcFile { .. }) => { + drop(fs); + self.lazy_load_arc_file_mut() + .map(|file| file.flush()) + .map_err(|_| io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + ))? + }, + _ => { + Err(io::Error::new( + io::ErrorKind::NotFound, + format!("inode `{}` doesn't match a file", self.inode), + )) + } + } } #[allow(clippy::unused_io_amount)] @@ -853,20 +1091,17 @@ impl fmt::Debug for FileHandle { #[derive(Debug)] pub(super) struct File { buffer: Vec, - cursor: usize, } impl File { pub(super) fn new() -> Self { Self { buffer: Vec::new(), - cursor: 0, } } pub(super) fn truncate(&mut self) { self.buffer.clear(); - self.cursor = 0; } pub(super) fn len(&self) -> usize { @@ -874,22 +1109,24 @@ impl File { } } -impl Read for File { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let max_to_read = cmp::min(self.buffer.len() - self.cursor, buf.len()); - let data_to_copy = &self.buffer[self.cursor..][..max_to_read]; +impl File { + pub fn read(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result { + let cur_pos = *cursor as usize; + let max_to_read = cmp::min(self.buffer.len() - cur_pos, buf.len()); + let data_to_copy = &self.buffer[cur_pos..][..max_to_read]; // SAFETY: `buf[..max_to_read]` and `data_to_copy` have the same size, due to // how `max_to_read` is computed. buf[..max_to_read].copy_from_slice(data_to_copy); - self.cursor += max_to_read; + *cursor += max_to_read as u64; Ok(max_to_read) } - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - let data_to_copy = &self.buffer[self.cursor..]; + pub fn read_to_end(&self, buf: &mut Vec, cursor: &mut u64) -> io::Result { + let cur_pos = *cursor as usize; + let data_to_copy = &self.buffer[cur_pos..]; let max_to_read = data_to_copy.len(); // `buf` is too small to contain the data. Let's resize it. @@ -908,33 +1145,34 @@ impl Read for File { // above. buf.copy_from_slice(data_to_copy); - self.cursor += max_to_read; + *cursor += max_to_read as u64; Ok(max_to_read) } - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - if buf.len() > (self.buffer.len() - self.cursor) { + pub fn read_exact(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result<()> { + let cur_pos = *cursor as usize; + if buf.len() > (self.buffer.len() - cur_pos) { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "not enough data available in file", )); } - let max_to_read = cmp::min(buf.len(), self.buffer.len() - self.cursor); - let data_to_copy = &self.buffer[self.cursor..][..max_to_read]; + let max_to_read = cmp::min(buf.len(), self.buffer.len() - cur_pos); + let data_to_copy = &self.buffer[cur_pos..][..max_to_read]; // SAFETY: `buf` and `data_to_copy` have the same size. buf.copy_from_slice(data_to_copy); - self.cursor += data_to_copy.len(); + *cursor += data_to_copy.len() as u64; Ok(()) } } -impl Seek for File { - fn seek(&mut self, position: io::SeekFrom) -> io::Result { +impl File { + pub fn seek(&self, position: io::SeekFrom, cursor: &mut u64) -> io::Result { let to_err = |_| io::ErrorKind::InvalidInput; // Calculate the next cursor. @@ -949,7 +1187,7 @@ impl Seek for File { // Calculate from the current cursor, so `cursor + offset`. io::SeekFrom::Current(offset) => { - TryInto::::try_into(self.cursor).map_err(to_err)? + offset + TryInto::::try_into(*cursor).map_err(to_err)? + offset } }; @@ -963,17 +1201,19 @@ impl Seek for File { // In this implementation, it's an error to seek beyond the // end of the buffer. - self.cursor = cmp::min(self.buffer.len(), next_cursor.try_into().map_err(to_err)?); + let next_cursor = next_cursor.try_into().map_err(to_err)?; + *cursor = cmp::min(self.buffer.len() as u64, next_cursor); - Ok(self.cursor.try_into().map_err(to_err)?) + let cursor = *cursor; + Ok(cursor) } } -impl Write for File { - fn write(&mut self, buf: &[u8]) -> io::Result { - match self.cursor { +impl File { + pub fn write(&mut self, buf: &[u8], cursor: &mut u64) -> io::Result { + match *cursor { // The cursor is at the end of the buffer: happy path! - position if position == self.buffer.len() => { + position if position == self.buffer.len() as u64 => { self.buffer.extend_from_slice(buf); } @@ -992,13 +1232,13 @@ impl Write for File { position => { self.buffer.reserve_exact(buf.len()); - let mut remainder = self.buffer.split_off(position); + let mut remainder = self.buffer.split_off(position as usize); self.buffer.extend_from_slice(buf); self.buffer.append(&mut remainder); } } - self.cursor += buf.len(); + *cursor += buf.len() as u64; Ok(buf.len()) } @@ -1007,3 +1247,105 @@ impl Write for File { Ok(()) } } + +/// Read only file that uses copy-on-write +#[derive(Debug)] +pub(super) struct ReadOnlyFile { + buffer: Cow<'static, [u8]>, +} + +impl ReadOnlyFile { + pub(super) fn new(buffer: Cow<'static, [u8]>) -> Self { + Self { + buffer, + } + } + + pub(super) fn len(&self) -> usize { + self.buffer.len() + } +} + +impl ReadOnlyFile { + pub fn read(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result { + let cur_pos = *cursor as usize; + let max_to_read = cmp::min(self.buffer.len() - cur_pos, buf.len()); + let data_to_copy = &self.buffer[cur_pos..][..max_to_read]; + + // SAFETY: `buf[..max_to_read]` and `data_to_copy` have the same size, due to + // how `max_to_read` is computed. + buf[..max_to_read].copy_from_slice(data_to_copy); + + *cursor += max_to_read as u64; + + Ok(max_to_read) + } + + pub fn read_to_end(&self, buf: &mut Vec, cursor: &mut u64) -> io::Result { + let cur_pos = *cursor as usize; + let data_to_copy = &self.buffer[cur_pos..]; + let max_to_read = data_to_copy.len(); + + // `buf` is too small to contain the data. Let's resize it. + if max_to_read > buf.len() { + // Let's resize the capacity if needed. + if max_to_read > buf.capacity() { + buf.reserve_exact(max_to_read - buf.capacity()); + } + + // SAFETY: The space is reserved, and it's going to be + // filled with `copy_from_slice` below. + unsafe { buf.set_len(max_to_read) } + } + + // SAFETY: `buf` and `data_to_copy` have the same size, see + // above. + buf.copy_from_slice(data_to_copy); + + *cursor += max_to_read as u64; + + Ok(max_to_read) + } + + pub fn read_exact(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result<()> { + let cur_pos = *cursor as usize; + if buf.len() > (self.buffer.len() - cur_pos) { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "not enough data available in file", + )); + } + + let max_to_read = cmp::min(buf.len(), self.buffer.len() - cur_pos); + let data_to_copy = &self.buffer[cur_pos..][..max_to_read]; + + // SAFETY: `buf` and `data_to_copy` have the same size. + buf.copy_from_slice(data_to_copy); + + *cursor += data_to_copy.len() as u64; + + Ok(()) + } +} + +impl ReadOnlyFile { + pub fn seek(&self, _position: io::SeekFrom, _cursor: &mut u64) -> io::Result { + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "file is read-only", + )) + } +} + +impl ReadOnlyFile { + pub fn write(&mut self, _buf: &[u8], _cursor: &mut u64) -> io::Result { + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "file is read-only", + )) + } + + pub fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/lib/vfs/src/mem_fs/file_opener.rs b/lib/vfs/src/mem_fs/file_opener.rs index 4193f5e69eb..d4c470930bf 100644 --- a/lib/vfs/src/mem_fs/file_opener.rs +++ b/lib/vfs/src/mem_fs/file_opener.rs @@ -1,7 +1,10 @@ use super::*; +use super::filesystem::InodeResolution; use crate::{FileType, FsError, Metadata, OpenOptionsConfig, Result, VirtualFile}; +use std::borrow::Cow; use std::io::{self, Seek}; use std::path::Path; +use tracing::*; /// The type that is responsible to open a file. #[derive(Debug, Clone)] @@ -9,12 +12,324 @@ pub struct FileOpener { pub(super) filesystem: FileSystem, } +impl FileOpener +{ + /// Inserts a readonly file into the file system that uses copy-on-write + /// (this is required for zero-copy creation of the same file) + pub fn insert_ro_file( + &mut self, + path: &Path, + contents: Cow<'static, [u8]> + ) -> Result<()> { + let _ = crate::FileSystem::remove_file(&self.filesystem, path); + let (inode_of_parent, maybe_inode_of_file, name_of_file) = + self.insert_inode(path)?; + + let inode_of_parent = match inode_of_parent { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + + match maybe_inode_of_file { + // The file already exists, then it can not be inserted. + Some(_inode_of_file) => return Err(FsError::AlreadyExists), + + // The file doesn't already exist; it's OK to create it if + None => { + // Write lock. + let mut fs = self + .filesystem + .inner + .write() + .map_err(|_| FsError::Lock)?; + + let file = ReadOnlyFile::new(contents); + + // Creating the file in the storage. + let inode_of_file = fs.storage.vacant_entry().key(); + let real_inode_of_file = fs.storage.insert(Node::ReadOnlyFile { + inode: inode_of_file, + name: name_of_file, + file, + metadata: { + let time = time(); + + Metadata { + ft: FileType { + file: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + }); + + assert_eq!( + inode_of_file, real_inode_of_file, + "new file inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs.add_child_to_node(inode_of_parent, inode_of_file)?; + + inode_of_file + } + }; + Ok(()) + } + + /// Inserts a arc file into the file system that references another file + /// in another file system (does not copy the real data) + pub fn insert_arc_file( + &mut self, + path: PathBuf, + fs: Arc + ) -> Result<()> { + let _ = crate::FileSystem::remove_file(&self.filesystem, path.as_path()); + let (inode_of_parent, maybe_inode_of_file, name_of_file) = + self.insert_inode(path.as_path())?; + + let inode_of_parent = match inode_of_parent { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + + match maybe_inode_of_file { + // The file already exists, then it can not be inserted. + Some(_inode_of_file) => return Err(FsError::AlreadyExists), + + // The file doesn't already exist; it's OK to create it if + None => { + // Write lock. + let mut fs_lock = self + .filesystem + .inner + .write() + .map_err(|_| FsError::Lock)?; + + // Creating the file in the storage. + let inode_of_file = fs_lock.storage.vacant_entry().key(); + let real_inode_of_file = fs_lock.storage.insert(Node::ArcFile { + inode: inode_of_file, + name: name_of_file, + fs, + path, + metadata: { + let time = time(); + Metadata { + ft: FileType { + file: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + }); + + assert_eq!( + inode_of_file, real_inode_of_file, + "new file inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?; + + inode_of_file + } + }; + Ok(()) + } + + /// Inserts a arc directory into the file system that references another file + /// in another file system (does not copy the real data) + pub fn insert_arc_directory( + &mut self, + path: PathBuf, + fs: Arc + ) -> Result<()> { + let _ = crate::FileSystem::remove_dir(&self.filesystem, path.as_path()); + let (inode_of_parent, maybe_inode_of_file, name_of_file) = + self.insert_inode(path.as_path())?; + + let inode_of_parent = match inode_of_parent { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + + match maybe_inode_of_file { + // The file already exists, then it can not be inserted. + Some(_inode_of_file) => return Err(FsError::AlreadyExists), + + // The file doesn't already exist; it's OK to create it if + None => { + // Write lock. + let mut fs_lock = self + .filesystem + .inner + .write() + .map_err(|_| FsError::Lock)?; + + // Creating the file in the storage. + let inode_of_file = fs_lock.storage.vacant_entry().key(); + let real_inode_of_file = fs_lock.storage.insert(Node::ArcDirectory { + inode: inode_of_file, + name: name_of_file, + fs, + path, + metadata: { + let time = time(); + Metadata { + ft: FileType { + file: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + }); + + assert_eq!( + inode_of_file, real_inode_of_file, + "new file inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?; + + inode_of_file + } + }; + Ok(()) + } + + /// Inserts a arc file into the file system that references another file + /// in another file system (does not copy the real data) + pub fn insert_custom_file( + &mut self, + path: PathBuf, + file: Box + ) -> Result<()> { + let _ = crate::FileSystem::remove_file(&self.filesystem, path.as_path()); + let (inode_of_parent, maybe_inode_of_file, name_of_file) = + self.insert_inode(path.as_path())?; + + let inode_of_parent = match inode_of_parent { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + + match maybe_inode_of_file { + // The file already exists, then it can not be inserted. + Some(_inode_of_file) => return Err(FsError::AlreadyExists), + + // The file doesn't already exist; it's OK to create it if + None => { + // Write lock. + let mut fs_lock = self + .filesystem + .inner + .write() + .map_err(|_| FsError::Lock)?; + + // Creating the file in the storage. + let inode_of_file = fs_lock.storage.vacant_entry().key(); + let real_inode_of_file = fs_lock.storage.insert(Node::CustomFile { + inode: inode_of_file, + name: name_of_file, + file: Mutex::new(file), + metadata: { + let time = time(); + Metadata { + ft: FileType { + file: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + }); + + assert_eq!( + inode_of_file, real_inode_of_file, + "new file inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?; + + inode_of_file + } + }; + Ok(()) + } + + fn insert_inode( + &mut self, + path: &Path, + ) -> Result<(InodeResolution, Option, OsString)> { + // Read lock. + let fs = self + .filesystem + .inner + .read() + .map_err(|_| FsError::Lock)?; + + // Check the path has a parent. + let parent_of_path = path.parent().ok_or({ + FsError::BaseNotDirectory + })?; + + // Check the file name. + let name_of_file = path + .file_name() + .ok_or(FsError::InvalidInput)? + .to_os_string(); + + // Find the parent inode. + let inode_of_parent = match fs.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, parent_path) => { + return Ok((InodeResolution::Redirect(fs, parent_path), None, name_of_file)); + } + }; + + // Find the inode of the file if it exists. + let maybe_inode_of_file = fs + .as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)? + .map(|(_nth, inode)| inode); + + Ok((InodeResolution::Found(inode_of_parent), maybe_inode_of_file, name_of_file)) + } +} + impl crate::FileOpener for FileOpener { fn open( &mut self, path: &Path, conf: &OpenOptionsConfig, ) -> Result> { + debug!("open: path={}", path.display()); + let read = conf.read(); let mut write = conf.write(); let append = conf.append(); @@ -39,34 +354,20 @@ impl crate::FileOpener for FileOpener { write = false; } - let (inode_of_parent, maybe_inode_of_file, name_of_file) = { - // Read lock. - let fs = self - .filesystem - .inner - .try_read() - .map_err(|_| FsError::Lock)?; - - // Check the path has a parent. - let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; - - // Check the file name. - let name_of_file = path - .file_name() - .ok_or(FsError::InvalidInput)? - .to_os_string(); - - // Find the parent inode. - let inode_of_parent = fs.inode_of_parent(parent_of_path)?; - - // Find the inode of the file if it exists. - let maybe_inode_of_file = fs - .as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)? - .map(|(_nth, inode)| inode); - - (inode_of_parent, maybe_inode_of_file, name_of_file) + let (inode_of_parent, maybe_inode_of_file, name_of_file) = + self.insert_inode(path)?; + + let inode_of_parent = match inode_of_parent { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, mut parent_path) => { + parent_path.push(name_of_file); + return fs.new_open_options() + .options(conf.clone()) + .open(parent_path); + } }; - + + let mut cursor = 0u64; let inode_of_file = match maybe_inode_of_file { // The file already exists, and a _new_ one _must_ be // created; it's not OK. @@ -74,11 +375,21 @@ impl crate::FileOpener for FileOpener { // The file already exists; it's OK. Some(inode_of_file) => { + + let inode_of_file = match inode_of_file { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, path) => { + return fs.new_open_options() + .options(conf.clone()) + .open(path); + } + }; + // Write lock. let mut fs = self .filesystem .inner - .try_write() + .write() .map_err(|_| FsError::Lock)?; let inode = fs.storage.get_mut(inode_of_file); @@ -93,6 +404,59 @@ impl crate::FileOpener for FileOpener { metadata.len = 0; } + // Move the cursor to the end if needed. + if append { + cursor = file.len() as u64; + } + }, + + Some(Node::ReadOnlyFile { metadata, .. }) => { + // Update the accessed time. + metadata.accessed = time(); + + // Truncate if needed. + if truncate || append { + return Err(FsError::PermissionDenied); + } + } + + Some(Node::CustomFile { metadata, file, .. }) => { + // Update the accessed time. + metadata.accessed = time(); + + // Truncate if needed. + let mut file = file.lock().unwrap(); + if truncate { + file.set_len(0)?; + metadata.len = 0; + } + + // Move the cursor to the end if needed. + if append { + cursor = file.size() as u64; + } + } + + Some(Node::ArcFile { metadata, fs, path, .. }) => { + // Update the accessed time. + metadata.accessed = time(); + + let mut file = fs + .new_open_options() + .read(read) + .write(write) + .append(append) + .truncate(truncate) + .create(create) + .create_new(create_new) + .open(path.as_path())?; + + // Truncate if needed. + if truncate { + file.set_len(0)?; + metadata.len = 0; + } + // Move the cursor to the end if needed. if append { file.seek(io::SeekFrom::End(0))?; @@ -117,7 +481,7 @@ impl crate::FileOpener for FileOpener { let mut fs = self .filesystem .inner - .try_write() + .write() .map_err(|_| FsError::Lock)?; let file = File::new(); @@ -155,7 +519,9 @@ impl crate::FileOpener for FileOpener { inode_of_file } - None => return Err(FsError::PermissionDenied), + None if (create_new || create) => return Err(FsError::PermissionDenied), + + None => return Err(FsError::EntryNotFound), }; Ok(Box::new(FileHandle::new( @@ -164,6 +530,7 @@ impl crate::FileOpener for FileOpener { read, write || append || truncate, append, + cursor ))) } } diff --git a/lib/vfs/src/mem_fs/filesystem.rs b/lib/vfs/src/mem_fs/filesystem.rs index 233577db6ed..e8a70a410cc 100644 --- a/lib/vfs/src/mem_fs/filesystem.rs +++ b/lib/vfs/src/mem_fs/filesystem.rs @@ -3,6 +3,7 @@ use super::*; use crate::{DirEntry, FileType, FsError, Metadata, OpenOptions, ReadDir, Result}; use slab::Slab; +use std::collections::VecDeque; use std::convert::identity; use std::ffi::OsString; use std::fmt; @@ -19,30 +20,168 @@ pub struct FileSystem { pub(super) inner: Arc>, } +impl FileSystem +{ + pub fn new_open_options_ext(&self) -> FileOpener { + let opener = FileOpener { + filesystem: self.clone(), + }; + opener + } + + pub fn union(&self, other: &Arc) { + // Iterate all the directories and files in the other filesystem + // and create references back to them in this filesystem + let mut remaining = VecDeque::new(); + remaining.push_back(PathBuf::from("/")); + while let Some(next) = remaining.pop_back() { + if next.file_name().map(|n| n.to_string_lossy().starts_with(".wh.")).unwrap_or(false) { + let rm = next.to_string_lossy(); + let rm = &rm[".wh.".len()..]; + let rm = PathBuf::from(rm); + let _ = crate::FileSystem::remove_dir(self, rm.as_path()); + let _ = crate::FileSystem::remove_file(self, rm.as_path()); + continue; + } + let _ = crate::FileSystem::create_dir(self, next.as_path()); + if let Ok(dir) = other.read_dir(next.as_path()) { + for sub_dir in dir.into_iter() { + if let Ok(sub_dir) = sub_dir { + match sub_dir.file_type() { + Ok(t) if t.is_dir() => { + remaining.push_back(sub_dir.path()); + }, + Ok(t) if t.is_file() => { + if sub_dir.file_name().to_string_lossy().starts_with(".wh.") { + let rm = next.to_string_lossy(); + let rm = &rm[".wh.".len()..]; + let rm = PathBuf::from(rm); + let _ = crate::FileSystem::remove_dir(self, rm.as_path()); + let _ = crate::FileSystem::remove_file(self, rm.as_path()); + continue; + } + let _ = self.new_open_options_ext() + .insert_arc_file(sub_dir.path(), + other.clone()); + }, + _ => { } + } + } + } + } + } + + } + + pub fn mount(&self, path: PathBuf, other: &Arc, dst: PathBuf) -> Result<()> { + if crate::FileSystem::read_dir(self, path.as_path()).is_ok() { + return Err(FsError::AlreadyExists); + } + + let (inode_of_parent, name_of_directory) = { + // Read lock. + let guard = self.inner.read().map_err(|_| FsError::Lock)?; + + // Canonicalize the path without checking the path exists, + // because it's about to be created. + let path = guard.canonicalize_without_inode(path.as_path())?; + + // Check the path has a parent. + let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; + + // Check the directory name. + let name_of_directory = path + .file_name() + .ok_or(FsError::InvalidInput)? + .to_os_string(); + + // Find the parent inode. + let inode_of_parent = match guard.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::AlreadyExists); + } + }; + + (inode_of_parent, name_of_directory) + }; + + { + // Write lock. + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; + + // Creating the directory in the storage. + let inode_of_directory = fs.storage.vacant_entry().key(); + let real_inode_of_directory = fs.storage.insert(Node::ArcDirectory { + inode: inode_of_directory, + name: name_of_directory, + fs: other.clone(), + path: dst, + metadata: { + let time = time(); + + Metadata { + ft: FileType { + dir: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + }); + + assert_eq!( + inode_of_directory, real_inode_of_directory, + "new directory inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs.add_child_to_node(inode_of_parent, inode_of_directory)?; + } + + Ok(()) + } +} + impl crate::FileSystem for FileSystem { fn read_dir(&self, path: &Path) -> Result { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; + let guard = self.inner.read().map_err(|_| FsError::Lock)?; // Canonicalize the path. - let (path, inode_of_directory) = fs.canonicalize(path)?; + let (path, inode_of_directory) = guard.canonicalize(path)?; + let inode_of_directory = match inode_of_directory { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, path) => { + return fs.read_dir(path.as_path()); + } + }; // Check it's a directory and fetch the immediate children as `DirEntry`. - let inode = fs.storage.get(inode_of_directory); + let inode = guard.storage.get(inode_of_directory); let children = match inode { - Some(Node::Directory { children, .. }) => children - .iter() - .filter_map(|inode| fs.storage.get(*inode)) - .map(|node| DirEntry { - path: { - let mut entry_path = path.to_path_buf(); - entry_path.push(node.name()); + Some(Node::Directory { children, .. }) => { + children + .iter() + .filter_map(|inode| guard.storage.get(*inode)) + .map(|node| DirEntry { + path: { + let mut entry_path = path.to_path_buf(); + entry_path.push(node.name()); - entry_path - }, - metadata: Ok(node.metadata().clone()), - }) - .collect(), + entry_path + }, + metadata: Ok(node.metadata().clone()), + }) + .collect() + }, + + Some(Node::ArcDirectory { fs, path, .. }) => { + return fs.read_dir(path.as_path()); + } _ => return Err(FsError::InvalidInput), }; @@ -51,13 +190,17 @@ impl crate::FileSystem for FileSystem { } fn create_dir(&self, path: &Path) -> Result<()> { + if self.read_dir(path).is_ok() { + return Err(FsError::AlreadyExists); + } + let (inode_of_parent, name_of_directory) = { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; + let guard = self.inner.read().map_err(|_| FsError::Lock)?; // Canonicalize the path without checking the path exists, // because it's about to be created. - let path = fs.canonicalize_without_inode(path)?; + let path = guard.canonicalize_without_inode(path)?; // Check the path has a parent. let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; @@ -69,14 +212,21 @@ impl crate::FileSystem for FileSystem { .to_os_string(); // Find the parent inode. - let inode_of_parent = fs.inode_of_parent(parent_of_path)?; + let inode_of_parent = match guard.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, mut path) => { + drop(guard); + path.push(name_of_directory); + return fs.create_dir(path.as_path()); + } + }; (inode_of_parent, name_of_directory) }; { // Write lock. - let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?; + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; // Creating the directory in the storage. let inode_of_directory = fs.storage.vacant_entry().key(); @@ -115,10 +265,10 @@ impl crate::FileSystem for FileSystem { fn remove_dir(&self, path: &Path) -> Result<()> { let (inode_of_parent, position, inode_of_directory) = { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; + let guard = self.inner.read().map_err(|_| FsError::Lock)?; // Canonicalize the path. - let (path, _) = fs.canonicalize(path)?; + let (path, _) = guard.canonicalize(path)?; // Check the path has a parent. let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; @@ -130,11 +280,18 @@ impl crate::FileSystem for FileSystem { .to_os_string(); // Find the parent inode. - let inode_of_parent = fs.inode_of_parent(parent_of_path)?; + let inode_of_parent = match guard.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, mut parent_path) => { + drop(guard); + parent_path.push(name_of_directory); + return fs.remove_dir(parent_path.as_path()); + } + }; // Get the child index to remove in the parent node, in // addition to the inode of the directory to remove. - let (position, inode_of_directory) = fs.as_parent_get_position_and_inode_of_directory( + let (position, inode_of_directory) = guard.as_parent_get_position_and_inode_of_directory( inode_of_parent, &name_of_directory, DirectoryMustBeEmpty::Yes, @@ -143,9 +300,16 @@ impl crate::FileSystem for FileSystem { (inode_of_parent, position, inode_of_directory) }; + let inode_of_directory = match inode_of_directory { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, path) => { + return fs.remove_dir(path.as_path()); + } + }; + { // Write lock. - let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?; + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; // Remove the directory from the storage. fs.storage.remove(inode_of_directory); @@ -164,7 +328,7 @@ impl crate::FileSystem for FileSystem { inode_dest, ) = { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; + let fs = self.inner.read().map_err(|_| FsError::Lock)?; let from = fs.canonicalize_without_inode(from)?; let to = fs.canonicalize_without_inode(to)?; @@ -181,8 +345,18 @@ impl crate::FileSystem for FileSystem { let name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string(); // Find the parent inodes. - let inode_of_from_parent = fs.inode_of_parent(parent_of_from)?; - let inode_of_to_parent = fs.inode_of_parent(parent_of_to)?; + let inode_of_from_parent = match fs.inode_of_parent(parent_of_from)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + let inode_of_to_parent = match fs.inode_of_parent(parent_of_to)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; // Find the inode of the dest file if it exists let maybe_position_and_inode_of_file = @@ -201,9 +375,16 @@ impl crate::FileSystem for FileSystem { ) }; + let inode = match inode { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(..) => { + return Err(FsError::InvalidInput); + } + }; + { // Write lock. - let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?; + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; if let Some((position, inode_of_file)) = inode_dest { // Remove the file from the storage. @@ -234,6 +415,10 @@ impl crate::FileSystem for FileSystem { metadata: Metadata { modified, .. }, .. }) => *modified = time(), + Some(Node::ArcDirectory { + metadata: Metadata { modified, .. }, + .. + }) => *modified = time(), _ => return Err(FsError::UnknownError), } } @@ -244,23 +429,30 @@ impl crate::FileSystem for FileSystem { fn metadata(&self, path: &Path) -> Result { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; - - Ok(fs - .storage - .get(fs.inode_of(path)?) - .ok_or(FsError::UnknownError)? - .metadata() - .clone()) + let guard = self.inner.read().map_err(|_| FsError::Lock)?; + match guard.inode_of(path)? { + InodeResolution::Found(inode) => { + Ok(guard + .storage + .get(inode) + .ok_or(FsError::UnknownError)? + .metadata() + .clone()) + }, + InodeResolution::Redirect(fs, path) => { + drop(guard); + fs.metadata(path.as_path()) + } + } } fn remove_file(&self, path: &Path) -> Result<()> { let (inode_of_parent, position, inode_of_file) = { // Read lock. - let fs = self.inner.try_read().map_err(|_| FsError::Lock)?; + let guard = self.inner.read().map_err(|_| FsError::Lock)?; // Canonicalize the path. - let path = fs.canonicalize_without_inode(path)?; + let path = guard.canonicalize_without_inode(path)?; // Check the path has a parent. let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; @@ -272,11 +464,17 @@ impl crate::FileSystem for FileSystem { .to_os_string(); // Find the parent inode. - let inode_of_parent = fs.inode_of_parent(parent_of_path)?; + let inode_of_parent = match guard.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, mut parent_path) => { + parent_path.push(name_of_file); + return fs.remove_file(parent_path.as_path()); + } + }; // Find the inode of the file if it exists, along with its position. let maybe_position_and_inode_of_file = - fs.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?; + guard.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?; match maybe_position_and_inode_of_file { Some((position, inode_of_file)) => (inode_of_parent, position, inode_of_file), @@ -284,9 +482,16 @@ impl crate::FileSystem for FileSystem { } }; + let inode_of_file = match inode_of_file { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, path) => { + return fs.remove_file(path.as_path()); + } + }; + { // Write lock. - let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?; + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; // Remove the file from the storage. fs.storage.remove(inode_of_file); @@ -319,9 +524,29 @@ pub(super) struct FileSystemInner { pub(super) storage: Slab, } +#[derive(Debug)] +pub(super) enum InodeResolution +{ + Found(Inode), + Redirect(Arc, PathBuf) +} + +impl InodeResolution +{ + #[allow(dead_code)] + pub fn unwrap(&self) -> Inode { + match self { + Self::Found(a) => *a, + Self::Redirect(..) => { + panic!("failed to unwrap the inode as the resolution is a redirect"); + } + } + } +} + impl FileSystemInner { /// Get the inode associated to a path if it exists. - pub(super) fn inode_of(&self, path: &Path) -> Result { + pub(super) fn inode_of(&self, path: &Path) -> Result { // SAFETY: The root node always exists, so it's safe to unwrap here. let mut node = self.storage.get(ROOT_INODE).unwrap(); let mut components = path.components(); @@ -331,29 +556,47 @@ impl FileSystemInner { _ => return Err(FsError::BaseNotDirectory), } - for component in components { + while let Some(component) = components.next() { node = match node { Node::Directory { children, .. } => children .iter() .filter_map(|inode| self.storage.get(*inode)) .find(|node| node.name() == component.as_os_str()) - .ok_or(FsError::NotAFile)?, + .ok_or(FsError::EntryNotFound)?, + Node::ArcDirectory { fs, path: fs_path, .. } => { + let mut path = fs_path.clone(); + path.push(PathBuf::from(component.as_os_str())); + while let Some(component) = components.next() { + path.push(PathBuf::from(component.as_os_str())); + } + return Ok(InodeResolution::Redirect(fs.clone(), path)) + }, _ => return Err(FsError::BaseNotDirectory), }; } - Ok(node.inode()) + Ok( + InodeResolution::Found(node.inode()) + ) } /// Get the inode associated to a “parent path”. The returned /// inode necessarily represents a directory. - pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result { - let inode_of_parent = self.inode_of(parent_path)?; - - // Ensure it is a directory. - match self.storage.get(inode_of_parent) { - Some(Node::Directory { .. }) => Ok(inode_of_parent), - _ => Err(FsError::BaseNotDirectory), + pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result { + match self.inode_of(parent_path)? { + InodeResolution::Found(inode_of_parent) => { + // Ensure it is a directory. + match self.storage.get(inode_of_parent) { + Some(Node::Directory { .. }) => Ok(InodeResolution::Found(inode_of_parent)), + Some(Node::ArcDirectory { .. }) => Ok(InodeResolution::Found(inode_of_parent)), + _ => { + Err(FsError::BaseNotDirectory) + }, + } + }, + InodeResolution::Redirect(fs, path) => { + return Ok(InodeResolution::Redirect(fs, path)); + } } } @@ -364,7 +607,7 @@ impl FileSystemInner { inode_of_parent: Inode, name_of_directory: &OsString, directory_must_be_empty: DirectoryMustBeEmpty, - ) -> Result<(usize, Inode)> { + ) -> Result<(usize, InodeResolution)> { match self.storage.get(inode_of_parent) { Some(Node::Directory { children, .. }) => children .iter() @@ -378,7 +621,7 @@ impl FileSystemInner { .. } if name.as_os_str() == name_of_directory => { if directory_must_be_empty.no() || children.is_empty() { - Some(Ok((nth, *inode))) + Some(Ok((nth, InodeResolution::Found(*inode)))) } else { Some(Err(FsError::DirectoryNotEmpty)) } @@ -388,7 +631,16 @@ impl FileSystemInner { }) .ok_or(FsError::InvalidInput) .and_then(identity), // flatten - _ => Err(FsError::BaseNotDirectory), + + Some(Node::ArcDirectory { fs, path: fs_path, .. }) => { + let mut path = fs_path.clone(); + path.push(name_of_directory); + Ok((0, InodeResolution::Redirect(fs.clone(), path))) + }, + + _ => { + Err(FsError::BaseNotDirectory) + }, } } @@ -398,21 +650,30 @@ impl FileSystemInner { &self, inode_of_parent: Inode, name_of_file: &OsString, - ) -> Result> { + ) -> Result> { match self.storage.get(inode_of_parent) { Some(Node::Directory { children, .. }) => children .iter() .enumerate() .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { - Node::File { inode, name, .. } if name.as_os_str() == name_of_file => { - Some(Some((nth, *inode))) - } - + Node::File { inode, name, .. } | + Node::ReadOnlyFile { inode, name, .. } | + Node::CustomFile { inode, name, .. } | + Node::ArcFile { inode, name, .. } + if name.as_os_str() == name_of_file => { + Some(Some((nth, InodeResolution::Found(*inode)))) + }, _ => None, }) .or(Some(None)) .ok_or(FsError::InvalidInput), + + Some(Node::ArcDirectory { fs, path: fs_path, .. }) => { + let mut path = fs_path.clone(); + path.push(name_of_file); + Ok(Some((0, InodeResolution::Redirect(fs.clone(), path)))) + }, _ => Err(FsError::BaseNotDirectory), } @@ -425,23 +686,32 @@ impl FileSystemInner { &self, inode_of_parent: Inode, name_of: &OsString, - ) -> Result> { + ) -> Result> { match self.storage.get(inode_of_parent) { Some(Node::Directory { children, .. }) => children .iter() .enumerate() .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { - Node::File { inode, name, .. } | Node::Directory { inode, name, .. } + Node::File { inode, name, .. } | + Node::Directory { inode, name, .. } | + Node::ReadOnlyFile { inode, name, .. } | + Node::CustomFile { inode, name, .. } | + Node::ArcFile { inode, name, .. } if name.as_os_str() == name_of => { - Some(Some((nth, *inode))) - } - + Some(Some((nth, InodeResolution::Found(*inode)))) + }, _ => None, }) .or(Some(None)) .ok_or(FsError::InvalidInput), + + Some(Node::ArcDirectory { fs, path: fs_path, .. }) => { + let mut path = fs_path.clone(); + path.push(name_of); + Ok(Some((0, InodeResolution::Redirect(fs.clone(), path)))) + }, _ => Err(FsError::BaseNotDirectory), } @@ -512,7 +782,7 @@ impl FileSystemInner { /// * A path can contain `..` or `.` components, /// * A path must not contain a Windows prefix (`C:` or `\\server`), /// * A normalized path exists in the file system. - pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, Inode)> { + pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, InodeResolution)> { let new_path = self.canonicalize_without_inode(path)?; let inode = self.inode_of(&new_path)?; @@ -585,7 +855,11 @@ impl fmt::Debug for FileSystemInner { inode = node.inode(), ty = match node { Node::File { .. } => "file", + Node::ReadOnlyFile { .. } => "ro-file", + Node::ArcFile { .. } => "arc-file", + Node::CustomFile { .. } => "custom-file", Node::Directory { .. } => "dir", + Node::ArcDirectory { .. } => "arc-dir", }, name = node.name().to_string_lossy(), indentation_symbol = " ", @@ -1326,49 +1600,49 @@ mod test_filesystem { let fs_inner = fs.inner.read().unwrap(); assert_eq!( - fs_inner.canonicalize(path!("/")), + fs_inner.canonicalize(path!("/")).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/"), ROOT_INODE)), "canonicalizing `/`", ); assert_eq!( - fs_inner.canonicalize(path!("foo")), + fs_inner.canonicalize(path!("foo")).map(|(a, b)| (a, b.unwrap())), Err(FsError::InvalidInput), "canonicalizing `foo`", ); assert_eq!( - fs_inner.canonicalize(path!("/././././foo/")), + fs_inner.canonicalize(path!("/././././foo/")).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/foo"), 1)), "canonicalizing `/././././foo/`", ); assert_eq!( - fs_inner.canonicalize(path!("/foo/bar//")), + fs_inner.canonicalize(path!("/foo/bar//")).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/foo/bar"), 2)), "canonicalizing `/foo/bar//`", ); assert_eq!( - fs_inner.canonicalize(path!("/foo/bar/../bar")), + fs_inner.canonicalize(path!("/foo/bar/../bar")).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/foo/bar"), 2)), "canonicalizing `/foo/bar/../bar`", ); assert_eq!( - fs_inner.canonicalize(path!("/foo/bar/../..")), + fs_inner.canonicalize(path!("/foo/bar/../..")).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/"), ROOT_INODE)), "canonicalizing `/foo/bar/../..`", ); assert_eq!( - fs_inner.canonicalize(path!("/foo/bar/../../..")), + fs_inner.canonicalize(path!("/foo/bar/../../..")).map(|(a, b)| (a, b.unwrap())), Err(FsError::InvalidInput), "canonicalizing `/foo/bar/../../..`", ); assert_eq!( - fs_inner.canonicalize(path!("C:/foo/")), + fs_inner.canonicalize(path!("C:/foo/")).map(|(a, b)| (a, b.unwrap())), Err(FsError::InvalidInput), "canonicalizing `C:/foo/`", ); assert_eq!( fs_inner.canonicalize(path!( "/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt" - )), + )).map(|(a, b)| (a, b.unwrap())), Ok((path!(buf "/foo/bar/baz/qux/hello.txt"), 5)), "canonicalizing a crazily stupid path name", ); diff --git a/lib/vfs/src/mem_fs/mod.rs b/lib/vfs/src/mem_fs/mod.rs index 87737e37036..b667e24b01f 100644 --- a/lib/vfs/src/mem_fs/mod.rs +++ b/lib/vfs/src/mem_fs/mod.rs @@ -3,13 +3,13 @@ mod file_opener; mod filesystem; mod stdio; -use file::{File, FileHandle}; +use file::{File, ReadOnlyFile, FileHandle}; pub use file_opener::FileOpener; pub use filesystem::FileSystem; pub use stdio::{Stderr, Stdin, Stdout}; use crate::Metadata; -use std::ffi::{OsStr, OsString}; +use std::{ffi::{OsStr, OsString}, sync::{Arc, Mutex}, path::PathBuf}; type Inode = usize; const ROOT_INODE: Inode = 0; @@ -22,47 +22,93 @@ enum Node { file: File, metadata: Metadata, }, + ReadOnlyFile { + inode: Inode, + name: OsString, + file: ReadOnlyFile, + metadata: Metadata, + }, + ArcFile { + inode: Inode, + name: OsString, + fs: Arc, + path: PathBuf, + metadata: Metadata, + }, + CustomFile { + inode: Inode, + name: OsString, + file: Mutex>, + metadata: Metadata, + }, Directory { inode: Inode, name: OsString, children: Vec, metadata: Metadata, }, + ArcDirectory { + inode: Inode, + name: OsString, + fs: Arc, + path: PathBuf, + metadata: Metadata, + }, } impl Node { fn inode(&self) -> Inode { *match self { Self::File { inode, .. } => inode, + Self::ReadOnlyFile { inode, .. } => inode, + Self::ArcFile { inode, .. } => inode, + Self::CustomFile { inode, .. } => inode, Self::Directory { inode, .. } => inode, + Self::ArcDirectory { inode, .. } => inode, } } fn name(&self) -> &OsStr { match self { Self::File { name, .. } => name.as_os_str(), + Self::ReadOnlyFile { name, .. } => name.as_os_str(), + Self::ArcFile { name, .. } => name.as_os_str(), + Self::CustomFile { name, .. } => name.as_os_str(), Self::Directory { name, .. } => name.as_os_str(), + Self::ArcDirectory { name, .. } => name.as_os_str(), } } fn metadata(&self) -> &Metadata { match self { Self::File { metadata, .. } => metadata, + Self::ReadOnlyFile { metadata, .. } => metadata, + Self::ArcFile { metadata, .. } => metadata, + Self::CustomFile { metadata, .. } => metadata, Self::Directory { metadata, .. } => metadata, + Self::ArcDirectory { metadata, .. } => metadata, } } fn metadata_mut(&mut self) -> &mut Metadata { match self { Self::File { metadata, .. } => metadata, + Self::ReadOnlyFile { metadata, .. } => metadata, + Self::ArcFile { metadata, .. } => metadata, + Self::CustomFile { metadata, .. } => metadata, Self::Directory { metadata, .. } => metadata, + Self::ArcDirectory { metadata, .. } => metadata, } } fn set_name(&mut self, new_name: OsString) { match self { Self::File { name, .. } => *name = new_name, + Self::ReadOnlyFile { name, .. } => *name = new_name, + Self::ArcFile { name, .. } => *name = new_name, + Self::CustomFile { name, .. } => *name = new_name, Self::Directory { name, .. } => *name = new_name, + Self::ArcDirectory { name, .. } => *name = new_name, } } } diff --git a/lib/vfs/src/mem_fs/stdio.rs b/lib/vfs/src/mem_fs/stdio.rs index db47ef33b41..e709382dd24 100644 --- a/lib/vfs/src/mem_fs/stdio.rs +++ b/lib/vfs/src/mem_fs/stdio.rs @@ -52,6 +52,10 @@ macro_rules! impl_virtualfile_on_std_streams { unimplemented!(); } + fn get_special_fd(&self) -> Option { + None + } + fn get_fd(&self) -> Option { None } diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index 1bb2fa26712..b52568e1ef3 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -25,6 +25,9 @@ scopeguard = "1.1.0" lazy_static = "1.4.0" region = { version = "3.0" } corosensei = { version = "0.1.2" } +derivative = { version = "^2" } +# - Optional shared dependencies. +tracing = { version = "0.1", optional = true } [target.'cfg(target_vendor = "apple")'.dependencies] mach = "0.3.2" diff --git a/lib/vm/src/export.rs b/lib/vm/src/export.rs index 68427062f7c..0bc7c62e85e 100644 --- a/lib/vm/src/export.rs +++ b/lib/vm/src/export.rs @@ -9,6 +9,7 @@ use crate::vmcontext::VMFunctionKind; use crate::{MaybeInstanceOwned, VMCallerCheckedAnyfunc}; use std::any::Any; use wasmer_types::FunctionType; +use derivative::Derivative; /// The value of an export passed from one instance to another. pub enum VMExtern { @@ -26,9 +27,12 @@ pub enum VMExtern { } /// A function export value. +#[derive(Derivative)] +#[derivative(Debug)] pub struct VMFunction { /// Pointer to the `VMCallerCheckedAnyfunc` which contains data needed to /// call the function and check its signature. + #[derivative(Debug = "ignore")] pub anyfunc: MaybeInstanceOwned, /// The function type, used for compatibility checking. @@ -39,5 +43,6 @@ pub struct VMFunction { pub kind: VMFunctionKind, /// Associated data owned by a host function. + #[derivative(Debug = "ignore")] pub host_data: Box, } diff --git a/lib/vm/src/extern_ref.rs b/lib/vm/src/extern_ref.rs index f99a7e93f51..ad59e1d189f 100644 --- a/lib/vm/src/extern_ref.rs +++ b/lib/vm/src/extern_ref.rs @@ -1,11 +1,14 @@ use std::any::Any; - +use derivative::Derivative; use wasmer_types::RawValue; use crate::store::InternalStoreHandle; /// Underlying object referenced by a `VMExternRef`. +#[derive(Derivative)] +#[derivative(Debug)] pub struct VMExternObj { + #[derivative(Debug = "ignore")] contents: Box, } diff --git a/lib/vm/src/function_env.rs b/lib/vm/src/function_env.rs index ccedf04385e..181574f175d 100644 --- a/lib/vm/src/function_env.rs +++ b/lib/vm/src/function_env.rs @@ -1,7 +1,11 @@ use std::any::Any; +use derivative::Derivative; /// Underlying FunctionEnvironment used by a `VMFunction`. +#[derive(Derivative)] +#[derivative(Debug)] pub struct VMFunctionEnvironment { + #[derivative(Debug = "ignore")] contents: Box, } diff --git a/lib/vm/src/global.rs b/lib/vm/src/global.rs index 682a66fb294..d7d06268717 100644 --- a/lib/vm/src/global.rs +++ b/lib/vm/src/global.rs @@ -1,10 +1,14 @@ use crate::{store::MaybeInstanceOwned, vmcontext::VMGlobalDefinition}; use std::{cell::UnsafeCell, ptr::NonNull}; -use wasmer_types::GlobalType; +use wasmer_types::{GlobalType, StoreSnapshot}; +use derivative::Derivative; /// A Global instance +#[derive(Derivative)] +#[derivative(Debug)] pub struct VMGlobal { ty: GlobalType, + #[derivative(Debug = "ignore")] vm_global_definition: MaybeInstanceOwned, } @@ -30,4 +34,43 @@ impl VMGlobal { pub fn vmglobal(&self) -> NonNull { self.vm_global_definition.as_ptr() } + + /// Copies this global + pub fn copy_on_write(&self) -> Self { + unsafe { + Self { + ty: self.ty, + vm_global_definition: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new( + self.vm_global_definition.as_ptr().as_ref().clone() + ))), + } + } + } + + /// Saves the global value into the snapshot + pub fn save_snapshot(&self, index: usize, snapshot: &mut StoreSnapshot) { + let entry = snapshot.globals + .entry(index as u32) + .or_default(); + + let val = unsafe { + self.vm_global_definition.as_ptr().as_ref().val.u128 + }; + *entry = val; + } + + /// Restores the global value from the snapshot + pub fn restore_snapshot(&mut self, index: usize, snapshot: &StoreSnapshot) { + let index = index as u32; + if let Some(entry) = snapshot.globals.get(&index) { + let existing = unsafe { + self.vm_global_definition.as_ptr().as_ref().val.u128 + }; + if existing != *entry { + unsafe { + self.vm_global_definition.as_ptr().as_mut().val.u128 = *entry; + } + } + } + } } diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index d67ec52c4f4..44db6cca8e1 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -11,7 +11,18 @@ use std::cell::UnsafeCell; use std::convert::TryInto; use std::ptr::NonNull; use std::sync::{RwLock, Arc}; -use wasmer_types::{Bytes, MemoryStyle, MemoryType, Pages, MemoryError, LinearMemory, VMMemoryDefinition}; +use wasmer_types::{Bytes, MemoryStyle, MemoryType, Pages, MemoryError, LinearMemory, VMMemoryDefinition, MemoryRole}; + +// Represents a region of memory that plays a particular role +#[derive(Debug, Clone)] +pub struct VMMemoryRegion { + // Start of the memory region + start: u64, + // End of the memory region + end: u64, + // Role that the memory region plays + role: MemoryRole, +} // The memory mapped area #[derive(Debug)] @@ -20,7 +31,9 @@ struct WasmMmap { alloc: Mmap, // The current logical size in wasm pages of this linear memory. size: Pages, - /// The owned memory definition used by the generated code + // List of the regions that have been marked + regions: Vec, + // The owned memory definition used by the generated code vm_memory_definition: MaybeInstanceOwned, } @@ -117,6 +130,49 @@ impl WasmMmap Ok(prev_pages) } + + /// Marks a region of the memory for a particular role + pub fn mark_region(&mut self, start: u64, end: u64, role: MemoryRole) + { + self.regions.push(VMMemoryRegion { + start, + end, + role + }); + } + + /// Returns the role of a part of the memory + pub fn region(&self, pointer: u64) -> MemoryRole + { + for region in self.regions.iter() { + if pointer >= region.start && pointer < region.end { + return region.role; + } + } + MemoryRole::default() + } + + /// Copies the memory + /// (in this case it performs a copy-on-write to save memory) + pub fn fork(&mut self) -> Result + { + let mem_length = self.size.bytes().0; + let mut alloc = self.alloc + .fork(Some(mem_length)) + .map_err(|err| MemoryError::Generic(err))?; + let base_ptr = alloc.as_mut_ptr(); + Ok( + Self { + vm_memory_definition: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMMemoryDefinition { + base: base_ptr, + current_length: mem_length, + }))), + alloc, + size: self.size, + regions: self.regions.clone(), + } + ) + } } /// A linear memory instance. @@ -162,9 +218,9 @@ unsafe impl Sync for VMOwnedMemory { } /// A shared linear memory instance. #[derive(Debug, Clone)] pub struct VMSharedMemory { - // The underlying allocation. + /// The underlying allocation. mmap: Arc>, - // Configuration of this memory + /// Configuration of this memory config: VMMemoryConfig, } @@ -232,7 +288,7 @@ impl VMOwnedMemory { MemoryStyle::Static { bound, .. } => { assert_ge!(*bound, memory.minimum); *bound - } + }, }; let minimum_bytes = minimum_pages.bytes().0; let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap(); @@ -258,6 +314,7 @@ impl VMOwnedMemory { current_length: mem_length, }))) }, + regions: Default::default(), alloc, size: memory.minimum, }; @@ -322,6 +379,28 @@ for VMOwnedMemory fn try_clone(&self) -> Option> { None } + + /// Copies this memory to a new memory + fn fork(&mut self) -> Result, MemoryError> { + Ok( + Box::new( + Self { + mmap: self.mmap.fork()?, + config: self.config.clone(), + } + ) + ) + } + + /// Marks a region of the memory for a particular role + fn mark_region(&mut self, start: u64, end: u64, role: MemoryRole) { + self.mmap.mark_region(start, end, role); + } + + /// Returns the role of a part of the memory + fn region(&self, pointer: u64) -> MemoryRole { + self.mmap.region(pointer) + } } impl Into @@ -404,6 +483,33 @@ for VMSharedMemory fn try_clone(&self) -> Option> { Some(Box::new(self.clone())) } + + /// Copies this memory to a new memory + fn fork(&mut self) -> Result, MemoryError> { + let mut guard = self.mmap.write().unwrap(); + Ok( + Box::new( + Self { + mmap: Arc::new(RwLock::new( + guard.fork()? + )), + config: self.config.clone(), + } + ) + ) + } + + /// Marks a region of the memory for a particular role + fn mark_region(&mut self, start: u64, end: u64, role: MemoryRole) { + let mut guard = self.mmap.write().unwrap(); + guard.mark_region(start, end, role) + } + + /// Returns the role of a part of the memory + fn region(&self, pointer: u64) -> MemoryRole { + let guard = self.mmap.read().unwrap(); + guard.region(pointer) + } } impl Into @@ -416,7 +522,7 @@ for VMSharedMemory /// Represents linear memory that can be either owned or shared #[derive(Debug)] -pub struct VMMemory(Box); +pub struct VMMemory(pub Box); impl Into for Box @@ -461,6 +567,21 @@ for VMMemory fn try_clone(&self) -> Option> { self.0.try_clone() } + + /// Copies this memory to a new memory + fn fork(&mut self) -> Result, MemoryError> { + self.0.fork() + } + + /// Marks a region of the memory for a particular role + fn mark_region(&mut self, start: u64, end: u64, role: MemoryRole) { + self.0.mark_region(start, end, role) + } + + /// Returns the role of a part of the memory + fn region(&self, pointer: u64) -> MemoryRole { + self.0.region(pointer) + } } impl VMMemory diff --git a/lib/vm/src/mmap.rs b/lib/vm/src/mmap.rs index 6b3dcdd19cd..5b598bdfe12 100644 --- a/lib/vm/src/mmap.rs +++ b/lib/vm/src/mmap.rs @@ -9,6 +9,8 @@ use more_asserts::assert_lt; use std::io; use std::ptr; use std::slice; +#[cfg(feature="tracing")] +use tracing::trace; /// Round `size` up to the nearest multiple of `page_size`. fn round_up_to_page_size(size: usize, page_size: usize) -> usize { @@ -25,6 +27,39 @@ pub struct Mmap { // the coordination all happens at the OS layer. ptr: usize, len: usize, + // Backing file that will be closed when the memory mapping goes out of scope + fd: FdGuard, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FdGuard(pub i32); + +impl Default +for FdGuard +{ + fn default() -> Self { + Self(-1) + } +} + +impl Clone +for FdGuard +{ + fn clone(&self) -> Self { + unsafe { + FdGuard(libc::dup(self.0)) + } + } +} + +impl Drop +for FdGuard { + fn drop(&mut self) { + if self.0 >= 0 { + unsafe { libc::close(self.0); } + self.0 = -1; + } + } } impl Mmap { @@ -37,6 +72,7 @@ impl Mmap { Self { ptr: empty.as_ptr() as usize, len: 0, + fd: FdGuard(-1), } } @@ -55,6 +91,7 @@ impl Mmap { accessible_size: usize, mapping_size: usize, ) -> Result { + let page_size = region::page::size(); assert_le!(accessible_size, mapping_size); assert_eq!(mapping_size & (page_size - 1), 0); @@ -66,6 +103,29 @@ impl Mmap { return Ok(Self::new()); } + // Open a temporary file (which is used for swapping) + let fd = unsafe { + let file = if mapping_size > (u32::MAX as usize) { + libc::tmpfile64() + } else { + libc::tmpfile() + }; + if file == ptr::null_mut() { + return Err(format!("failed to create temporary file - {}", io::Error::last_os_error())); + } + FdGuard(libc::fileno(file)) + }; + + // First we initialize it with zeros + if mapping_size > (u32::MAX as usize) { + unsafe { libc::ftruncate64(fd.0, mapping_size as i64); } + } else { + unsafe { libc::ftruncate(fd.0, mapping_size as i64); } + } + + // Compute the flags + let flags = libc::MAP_FILE | libc::MAP_SHARED; + Ok(if accessible_size == mapping_size { // Allocate a single read-write region at once. let ptr = unsafe { @@ -73,8 +133,8 @@ impl Mmap { ptr::null_mut(), mapping_size, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANON, - -1, + flags, + fd.0, 0, ) }; @@ -85,6 +145,7 @@ impl Mmap { Self { ptr: ptr as usize, len: mapping_size, + fd, } } else { // Reserve the mapping size. @@ -93,8 +154,8 @@ impl Mmap { ptr::null_mut(), mapping_size, libc::PROT_NONE, - libc::MAP_PRIVATE | libc::MAP_ANON, - -1, + flags, + fd.0, 0, ) }; @@ -105,6 +166,7 @@ impl Mmap { let mut result = Self { ptr: ptr as usize, len: mapping_size, + fd, }; if accessible_size != 0 { @@ -256,6 +318,114 @@ impl Mmap { pub fn is_empty(&self) -> bool { self.len() == 0 } + + /// Copies the memory to a new swap file (using copy-on-write if available) + #[cfg(not(target_os = "windows"))] + pub fn fork(&mut self, hint_used: Option) -> Result + { + // Empty memory is an edge case + if self.len == 0 { + return Ok(Self::new()); + } + + // First we sync all the data to the backing file + unsafe { libc::fdatasync(self.fd.0); } + + // Open a new temporary file (which is used for swapping for the forked memory) + let fd = unsafe { + let file = if self.len > (u32::MAX as usize) { + libc::tmpfile64() + } else { + libc::tmpfile() + }; + if file == ptr::null_mut() { + return Err(format!("failed to create temporary file - {}", io::Error::last_os_error())); + } + FdGuard(libc::fileno(file)) + }; + + // Attempt to do a shallow copy (needs a backing file system that supports it) + unsafe { + if libc::ioctl(fd.0, 0x94, 9, self.fd.0) != 0 // FICLONE + { + #[cfg(feature="tracing")] + trace!("memory copy started"); + + // Determine host much to copy + let len = match hint_used { + Some(a) => a, + None => self.len + }; + + // The shallow copy failed so we have to do it the hard way + let mut off_in: libc::off64_t = 0; + let mut off_out: libc::off64_t = 0; + let ret = libc::copy_file_range(self.fd.0, &mut off_in, fd.0, &mut off_out, len, 0); + if ret < 0 { + return Err(format!("failed to copy temporary file data - {}", io::Error::last_os_error())); + } + + #[cfg(feature="tracing")] + trace!("memory copy finished (size={})", len); + } + } + + // Compute the flags + let flags = libc::MAP_FILE | libc::MAP_SHARED; + + // Allocate a single read-write region at once. + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + self.len, + libc::PROT_READ | libc::PROT_WRITE, + flags, + fd.0, + 0, + ) + }; + if ptr as isize == -1_isize { + return Err(io::Error::last_os_error().to_string()); + } + + Ok( + Self { + ptr: ptr as usize, + len: self.len, + fd, + } + ) + } + + /// Copies the memory to a new swap file (using copy-on-write if available) + #[cfg(target_os = "windows")] + pub fn fork(&mut self, hint_used: Option) -> Result + { + // Create a new memory which we will copy to + let new_mmap = Self::with_at_least(self.len)?; + + #[cfg(feature="tracing")] + trace!("memory copy started"); + + // Determine host much to copy + let len = match hint_used { + Some(a) => a, + None => self.len + }; + + // Copy the data to the new memory + let dst = new_mmap.ptr as *mut u8; + let src = self.ptr as *const u8; + unsafe { + std::ptr::copy_nonoverlapping(src, dst, len); + } + + #[cfg(feature="tracing")] + trace!("memory copy finished (size={})", len); + Ok( + new_mmap + ) + } } impl Drop for Mmap { diff --git a/lib/vm/src/store.rs b/lib/vm/src/store.rs index 93ae12e5511..e75f1673e84 100644 --- a/lib/vm/src/store.rs +++ b/lib/vm/src/store.rs @@ -7,6 +7,8 @@ use std::{ sync::atomic::{AtomicU64, Ordering}, }; +use wasmer_types::StoreSnapshot; + use crate::VMExternObj; use crate::{InstanceHandle, VMFunction, VMFunctionEnvironment, VMGlobal, VMMemory, VMTable}; @@ -60,7 +62,7 @@ impl_context_object! { } /// Set of objects managed by a context. -#[derive(Default)] +#[derive(Debug, Default)] pub struct StoreObjects { id: StoreId, memories: Vec, @@ -101,6 +103,22 @@ impl StoreObjects { (&mut high[0], &mut low[a.index()]) } } + + /// Serializes the mutable things into a snapshot + pub fn save_snapshot(&self) -> StoreSnapshot { + let mut ret = StoreSnapshot::default(); + for (index, global) in self.globals.iter().enumerate() { + global.save_snapshot(index, &mut ret); + } + ret + } + + /// Serializes the mutable things into a snapshot + pub fn restore_snapshot(&mut self, snapshot: &StoreSnapshot) { + for (index, global) in self.globals.iter_mut().enumerate() { + global.restore_snapshot(index, snapshot); + } + } } /// Handle to an object managed by a context. @@ -176,6 +194,11 @@ impl StoreHandle { self.id } + /// Overrides the store id with a new ID + pub fn set_store_id(&mut self, id: StoreId) { + self.id = id; + } + /// Constructs a `StoreHandle` from a `StoreId` and an `InternalStoreHandle`. /// /// # Safety diff --git a/lib/vm/src/table.rs b/lib/vm/src/table.rs index b29d8c883c4..40e38fa356a 100644 --- a/lib/vm/src/table.rs +++ b/lib/vm/src/table.rs @@ -14,6 +14,7 @@ use std::cell::UnsafeCell; use std::convert::TryFrom; use std::fmt; use std::ptr::NonNull; +use derivative::Derivative; use wasmer_types::TableStyle; use wasmer_types::{TableType, TrapCode, Type as ValType}; @@ -69,13 +70,17 @@ impl Default for TableElement { } /// A table instance. +#[derive(Derivative)] +#[derivative(Debug)] pub struct VMTable { + #[derivative(Debug = "ignore")] vec: Vec, maximum: Option, /// The WebAssembly table description. table: TableType, /// Our chosen implementation style. style: TableStyle, + #[derivative(Debug = "ignore")] vm_table_definition: MaybeInstanceOwned, } @@ -306,6 +311,16 @@ impl VMTable { Ok(()) } + /// Copies the table into a new table + pub fn copy_on_write(&self) -> Result { + let mut ret = Self::new(&self.table, &self.style)?; + ret.copy(self, 0, 0, self.size()) + .map_err(|trap| { + format!("failed to copy the table - {:?}", trap) + })?; + Ok(ret) + } + /// Copy `len` elements from `table[src_index..]` to `table[dst_index..]`. /// /// # Errors diff --git a/lib/vm/src/trap/mod.rs b/lib/vm/src/trap/mod.rs index afa81642a45..1f6e30238ec 100644 --- a/lib/vm/src/trap/mod.rs +++ b/lib/vm/src/trap/mod.rs @@ -14,4 +14,4 @@ pub use traphandlers::{ TrapHandler, TrapHandlerFn, }; pub use traphandlers::{init_traps, resume_panic}; -pub use wasmer_types::TrapCode; +pub use wasmer_types::TrapCode; \ No newline at end of file diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 2899b5d182b..7b7708e81d4 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -1052,4 +1052,4 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { } } } -} +} \ No newline at end of file diff --git a/lib/vnet/Cargo.toml b/lib/vnet/Cargo.toml index 130bf4e7af1..fa84195e633 100644 --- a/lib/vnet/Cargo.toml +++ b/lib/vnet/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" thiserror = "1" wasmer-vfs = { path = "../vfs", version = "=3.0.0-rc.2", default-features = false } bytes = "1" +async-trait = { version = "^0.1" } [features] default = ["mem_fs"] diff --git a/lib/vnet/src/lib.rs b/lib/vnet/src/lib.rs index 499d709b7d5..46ffa0bb570 100644 --- a/lib/vnet/src/lib.rs +++ b/lib/vnet/src/lib.rs @@ -36,51 +36,76 @@ pub struct IpRoute { } /// An implementation of virtual networking -pub trait VirtualNetworking: fmt::Debug + Send + Sync + 'static { +#[async_trait::async_trait] +#[allow(unused_variables)] +pub trait VirtualNetworking: fmt::Debug + Send + Sync + 'static +{ /// Establishes a web socket connection /// (note: this does not use the virtual sockets and is standalone /// functionality that works without the network being connected) - fn ws_connect(&self, url: &str) -> Result>; + async fn ws_connect(&self, url: &str) -> Result> { + Err(NetworkError::Unsupported) + } /// Makes a HTTP request to a remote web resource /// The headers are separated by line breaks /// (note: this does not use the virtual sockets and is standalone /// functionality that works without the network being connected) - fn http_request( + async fn http_request( &self, url: &str, method: &str, headers: &str, gzip: bool, - ) -> Result; + ) -> Result { + Err(NetworkError::Unsupported) + } /// Bridges this local network with a remote network, which is required in /// order to make lower level networking calls (such as UDP/TCP) - fn bridge(&self, network: &str, access_token: &str, security: StreamSecurity) -> Result<()>; + fn bridge(&self, network: &str, access_token: &str, security: StreamSecurity) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Disconnects from the remote network essentially unbridging it - fn unbridge(&self) -> Result<()>; + fn unbridge(&self) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Acquires an IP address on the network and configures the routing tables - fn dhcp_acquire(&self) -> Result>; + async fn dhcp_acquire(&self) -> Result> { + Err(NetworkError::Unsupported) + } /// Adds a static IP address to the interface with a netmask prefix - fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()>; + fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Removes a static (or dynamic) IP address from the interface - fn ip_remove(&self, ip: IpAddr) -> Result<()>; + fn ip_remove(&self, ip: IpAddr) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Clears all the assigned IP addresses for this interface - fn ip_clear(&self) -> Result<()>; + fn ip_clear(&self) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Lists all the IP addresses currently assigned to this interface - fn ip_list(&self) -> Result>; + fn ip_list(&self) -> Result> { + Err(NetworkError::Unsupported) + } /// Returns the hardware MAC address for this interface - fn mac(&self) -> Result<[u8; 6]>; + fn mac(&self) -> Result<[u8; 6]> { + Err(NetworkError::Unsupported) + } /// Adds a default gateway to the routing table - fn gateway_set(&self, ip: IpAddr) -> Result<()>; + fn gateway_set(&self, ip: IpAddr) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Adds a specific route to the routing table fn route_add( @@ -89,61 +114,81 @@ pub trait VirtualNetworking: fmt::Debug + Send + Sync + 'static { via_router: IpAddr, preferred_until: Option, expires_at: Option, - ) -> Result<()>; + ) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Removes a routing rule from the routing table - fn route_remove(&self, cidr: IpAddr) -> Result<()>; + fn route_remove(&self, cidr: IpAddr) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Clears the routing table for this interface - fn route_clear(&self) -> Result<()>; + fn route_clear(&self) -> Result<()> { + Err(NetworkError::Unsupported) + } /// Lists all the routes defined in the routing table for this interface - fn route_list(&self) -> Result>; + fn route_list(&self) -> Result> { + Err(NetworkError::Unsupported) + } /// Creates a low level socket that can read and write Ethernet packets /// directly to the interface - fn bind_raw(&self) -> Result>; + async fn bind_raw(&self) -> Result> { + Err(NetworkError::Unsupported) + } /// Lists for TCP connections on a specific IP and Port combination /// Multiple servers (processes or threads) can bind to the same port if they each set /// the reuse-port and-or reuse-addr flags - fn listen_tcp( + async fn listen_tcp( &self, addr: SocketAddr, only_v6: bool, reuse_port: bool, reuse_addr: bool, - ) -> Result>; - + ) -> Result> { + Err(NetworkError::Unsupported) + } + /// Opens a UDP socket that listens on a specific IP and Port combination /// Multiple servers (processes or threads) can bind to the same port if they each set /// the reuse-port and-or reuse-addr flags - fn bind_udp( + async fn bind_udp( &self, addr: SocketAddr, reuse_port: bool, reuse_addr: bool, - ) -> Result>; + ) -> Result> { + Err(NetworkError::Unsupported) + } /// Creates a socket that can be used to send and receive ICMP packets /// from a paritcular IP address - fn bind_icmp(&self, addr: IpAddr) -> Result>; + async fn bind_icmp(&self, addr: IpAddr) -> Result> { + Err(NetworkError::Unsupported) + } /// Opens a TCP connection to a particular destination IP address and port - fn connect_tcp( + async fn connect_tcp( &self, addr: SocketAddr, peer: SocketAddr, timeout: Option, - ) -> Result>; + ) -> Result> { + Err(NetworkError::Unsupported) + } /// Performs DNS resolution for a specific hostname - fn resolve( + async fn resolve( &self, host: &str, port: Option, dns_server: Option, - ) -> Result>; + ) -> Result> { + Err(NetworkError::Unsupported) + } } /// Holds the interface used to work with a pending HTTP request @@ -193,15 +238,16 @@ pub struct SocketReceiveFrom { pub addr: SocketAddr, } +#[async_trait::async_trait] pub trait VirtualTcpListener: fmt::Debug + Send + Sync + 'static { /// Accepts an connection attempt that was made to this listener - fn accept(&self) -> Result<(Box, SocketAddr)>; + async fn accept(&mut self) -> Result<(Box, SocketAddr)>; - /// Accepts an connection attempt that was made to this listener (or times out) - fn accept_timeout( - &self, - timeout: Duration, - ) -> Result<(Box, SocketAddr)>; + /// Checks how many sockets are waiting to be accepted + fn peek(&mut self) -> Result; + + /// Polls the socket for when there is data to be received + fn poll_accept_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll>; /// Sets the accept timeout fn set_timeout(&mut self, timeout: Option) -> Result<()>; @@ -217,12 +263,25 @@ pub trait VirtualTcpListener: fmt::Debug + Send + Sync + 'static { /// Returns the maximum number of network hops before packets are dropped fn ttl(&self) -> Result; + + /// Determines if the socket is blocking or not + fn set_nonblocking(&mut self, nonblocking: bool) -> Result<()>; + + // Returns true if the socket is nonblocking + fn nonblocking(&self) -> Result; } +#[async_trait::async_trait] pub trait VirtualSocket: fmt::Debug + Send + Sync + 'static { /// Sets how many network hops the packets are permitted for new connections fn set_ttl(&mut self, ttl: u32) -> Result<()>; + /// Determines if the socket is blocking or not + fn set_nonblocking(&mut self, nonblocking: bool) -> Result<()>; + + // Returns true if the socket is nonblocking + fn nonblocking(&self) -> Result; + /// Returns the maximum number of network hops before packets are dropped fn ttl(&self) -> Result; @@ -231,6 +290,12 @@ pub trait VirtualSocket: fmt::Debug + Send + Sync + 'static { /// Returns the status/state of the socket fn status(&self) -> Result; + + /// Polls the socket for when there is data to be received + fn poll_read_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll>; + + /// Polls the socket for when the backpressure allows for writing to the socket + fn poll_write_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll>; } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -250,18 +315,29 @@ pub enum StreamSecurity { } /// Interface used for sending and receiving data from a web socket +#[async_trait::async_trait] pub trait VirtualWebSocket: fmt::Debug + Send + Sync + 'static { /// Sends out a datagram or stream of bytes on this socket - fn send(&mut self, data: Bytes) -> Result; + async fn send(&mut self, data: Bytes) -> Result; /// FLushes all the datagrams fn flush(&mut self) -> Result<()>; /// Recv a packet from the socket - fn recv(&mut self) -> Result; + async fn recv(&mut self) -> Result; + + /// Recv a packet from the socket + fn try_recv(&mut self) -> Result>; + + /// Polls the socket for when there is data to be received + fn poll_read_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll>; + + /// Polls the socket for when the backpressure allows for writing to the socket + fn poll_write_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll>; } /// Connected sockets have a persistent connection to a remote peer +#[async_trait::async_trait] pub trait VirtualConnectedSocket: VirtualSocket + fmt::Debug + Send + Sync + 'static { /// Determines how long the socket will remain in a TIME_WAIT /// after it disconnects (only the one that initiates the close will @@ -274,27 +350,34 @@ pub trait VirtualConnectedSocket: VirtualSocket + fmt::Debug + Send + Sync + 'st fn linger(&self) -> Result>; /// Sends out a datagram or stream of bytes on this socket - fn send(&mut self, data: Bytes) -> Result; + async fn send(&mut self, data: Bytes) -> Result; /// FLushes all the datagrams - fn flush(&mut self) -> Result<()>; + async fn flush(&mut self) -> Result<()>; /// Recv a packet from the socket - fn recv(&mut self) -> Result; + async fn recv(&mut self) -> Result; + + /// Recv a packet from the socket + fn try_recv(&mut self) -> Result>; /// Peeks for a packet from the socket - fn peek(&mut self) -> Result; + async fn peek(&mut self) -> Result; } /// Connectionless sockets are able to send and receive datagrams and stream /// bytes to multiple addresses at the same time (peer-to-peer) +#[async_trait::async_trait] pub trait VirtualConnectionlessSocket: VirtualSocket + fmt::Debug + Send + Sync + 'static { /// Sends out a datagram or stream of bytes on this socket /// to a specific address - fn send_to(&mut self, data: Bytes, addr: SocketAddr) -> Result; + async fn send_to(&mut self, data: Bytes, addr: SocketAddr) -> Result; + + /// Recv a packet from the socket + async fn recv_from(&mut self) -> Result; /// Recv a packet from the socket - fn recv_from(&mut self) -> Result; + fn try_recv_from(&mut self) -> Result>; /// Peeks for a packet from the socket fn peek_from(&mut self) -> Result; @@ -302,20 +385,25 @@ pub trait VirtualConnectionlessSocket: VirtualSocket + fmt::Debug + Send + Sync /// ICMP sockets are low level devices bound to a specific address /// that can send and receive ICMP packets +#[async_trait::async_trait] pub trait VirtualIcmpSocket: VirtualConnectionlessSocket + fmt::Debug + Send + Sync + 'static { } +#[async_trait::async_trait] pub trait VirtualRawSocket: VirtualSocket + fmt::Debug + Send + Sync + 'static { /// Sends out a raw packet on this socket - fn send(&mut self, data: Bytes) -> Result; + async fn send(&mut self, data: Bytes) -> Result; /// FLushes all the datagrams - fn flush(&mut self) -> Result<()>; + async fn flush(&mut self) -> Result<()>; /// Recv a packet from the socket - fn recv(&mut self) -> Result; + async fn recv(&mut self) -> Result; + + /// Recv a packet from the socket + fn try_recv(&mut self) -> Result>; /// Tells the raw socket and its backing switch that all packets /// should be received by this socket even if they are not @@ -337,6 +425,7 @@ pub enum TimeType { Linger, } +#[async_trait::async_trait] pub trait VirtualTcpSocket: VirtualConnectedSocket + fmt::Debug + Send + Sync + 'static { /// Sets the timeout for a specific action on the socket fn set_opt_time(&mut self, ty: TimeType, timeout: Option) -> Result<()>; @@ -377,19 +466,20 @@ pub trait VirtualTcpSocket: VirtualConnectedSocket + fmt::Debug + Send + Sync + /// Causes all the data held in the send buffer to be immediately /// flushed to the destination peer - fn flush(&mut self) -> Result<()>; + async fn flush(&mut self) -> Result<()>; /// Shuts down either the READER or WRITER sides of the socket /// connection. - fn shutdown(&mut self, how: Shutdown) -> Result<()>; + async fn shutdown(&mut self, how: Shutdown) -> Result<()>; } +#[async_trait::async_trait] pub trait VirtualUdpSocket: VirtualConnectedSocket + VirtualConnectionlessSocket + fmt::Debug + Send + Sync + 'static { /// Connects to a destination peer so that the normal /// send/recv operations can be used. - fn connect(&mut self, addr: SocketAddr) -> Result<()>; + async fn connect(&mut self, addr: SocketAddr) -> Result<()>; /// Sets a flag that means that the UDP socket is able /// to receive and process broadcast packets. @@ -452,123 +542,8 @@ pub trait VirtualUdpSocket: #[derive(Debug, Default)] pub struct UnsupportedVirtualNetworking {} +#[async_trait::async_trait] impl VirtualNetworking for UnsupportedVirtualNetworking { - fn ws_connect(&self, _url: &str) -> Result> { - Err(NetworkError::Unsupported) - } - - fn http_request( - &self, - _url: &str, - _method: &str, - _headers: &str, - _gzip: bool, - ) -> Result { - Err(NetworkError::Unsupported) - } - - fn bridge(&self, _network: &str, _access_token: &str, _security: StreamSecurity) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn unbridge(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn dhcp_acquire(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn ip_add(&self, _ip: IpAddr, _prefix: u8) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_remove(&self, _ip: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_clear(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_list(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn mac(&self) -> Result<[u8; 6]> { - Err(NetworkError::Unsupported) - } - - fn gateway_set(&self, _ip: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_add( - &self, - _cidr: IpCidr, - _via_router: IpAddr, - _preferred_until: Option, - _expires_at: Option, - ) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_remove(&self, _cidr: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_clear(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_list(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn bind_raw(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn bind_icmp(&self, _addr: IpAddr) -> Result> { - Err(NetworkError::Unsupported) - } - - fn listen_tcp( - &self, - _addr: SocketAddr, - _only_v6: bool, - _reuse_port: bool, - _reuse_addr: bool, - ) -> Result> { - Err(NetworkError::Unsupported) - } - - fn connect_tcp( - &self, - _addr: SocketAddr, - _peer: SocketAddr, - _timeout: Option, - ) -> Result> { - Err(NetworkError::Unsupported) - } - - fn bind_udp( - &self, - _addr: SocketAddr, - _reuse_port: bool, - _reuse_addr: bool, - ) -> Result> { - Err(NetworkError::Unsupported) - } - - fn resolve( - &self, - _host: &str, - _port: Option, - _dns_server: Option, - ) -> Result> { - Err(NetworkError::Unsupported) - } } #[derive(Error, Copy, Clone, Debug, PartialEq, Eq)] diff --git a/lib/wasi-local-networking/Cargo.toml b/lib/wasi-local-networking/Cargo.toml index 957a51bace4..5dcd6a6b319 100644 --- a/lib/wasi-local-networking/Cargo.toml +++ b/lib/wasi-local-networking/Cargo.toml @@ -18,6 +18,8 @@ wasmer-vnet = { version = "=3.0.0-rc.2", path = "../vnet", default-features = fa wasmer-vfs = { path = "../vfs", version = "=3.0.0-rc.2", default-features = false } tracing = "0.1" bytes = "1.1" +tokio = { version = "1", features = [ "sync", "macros", "io-util", "signal" ], default_features = false } +async-trait = { version = "^0.1" } [features] default = ["host_fs"] diff --git a/lib/wasi-local-networking/src/lib.rs b/lib/wasi-local-networking/src/lib.rs index 35bcb616f1e..8aa6db2bbab 100644 --- a/lib/wasi-local-networking/src/lib.rs +++ b/lib/wasi-local-networking/src/lib.rs @@ -1,10 +1,16 @@ #![allow(unused_variables)] -use bytes::{Bytes, BytesMut}; -use std::io::{Read, Write}; +use bytes::Bytes; +use tokio::io::{AsyncRead, AsyncWriteExt}; +use std::future::Future; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use std::pin::Pin; +use std::ptr; +use std::sync::Mutex; +use std::task::{RawWakerVTable, RawWaker, Waker, Context, Poll}; use std::time::Duration; #[allow(unused_imports, dead_code)] use tracing::{debug, error, info, trace, warn}; +#[allow(unused_imports)] use wasmer_vnet::{ io_err_into_net_error, IpCidr, IpRoute, NetworkError, Result, SocketHttpRequest, SocketReceive, SocketReceiveFrom, SocketStatus, StreamSecurity, TimeType, VirtualConnectedSocket, @@ -15,176 +21,164 @@ use wasmer_vnet::{ #[derive(Debug, Default)] pub struct LocalNetworking {} +#[async_trait::async_trait] #[allow(unused_variables)] impl VirtualNetworking for LocalNetworking { - fn ws_connect(&self, url: &str) -> Result> { - Err(NetworkError::Unsupported) - } - - fn http_request( - &self, - url: &str, - method: &str, - headers: &str, - gzip: bool, - ) -> Result { - Err(NetworkError::Unsupported) - } - - fn bridge(&self, network: &str, access_token: &str, security: StreamSecurity) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn unbridge(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn dhcp_acquire(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_remove(&self, ip: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_clear(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn ip_list(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn mac(&self) -> Result<[u8; 6]> { - Err(NetworkError::Unsupported) - } - - fn gateway_set(&self, ip: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_add( - &self, - cidr: IpCidr, - via_router: IpAddr, - preferred_until: Option, - expires_at: Option, - ) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_remove(&self, cidr: IpAddr) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_clear(&self) -> Result<()> { - Err(NetworkError::Unsupported) - } - - fn route_list(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn bind_raw(&self) -> Result> { - Err(NetworkError::Unsupported) - } - - fn listen_tcp( + async fn listen_tcp( &self, addr: SocketAddr, only_v6: bool, reuse_port: bool, reuse_addr: bool, ) -> Result> { - let listener = std::net::TcpListener::bind(addr) + let listener = tokio::net::TcpListener::bind(addr) + .await .map(|sock| { Box::new(LocalTcpListener { stream: sock, timeout: None, + backlog: Mutex::new(Vec::new()), + nonblocking: false, }) }) .map_err(io_err_into_net_error)?; Ok(listener) } - fn bind_udp( + async fn bind_udp( &self, addr: SocketAddr, _reuse_port: bool, _reuse_addr: bool, ) -> Result> { - let socket = std::net::UdpSocket::bind(addr).map_err(io_err_into_net_error)?; - Ok(Box::new(LocalUdpSocket(socket, addr))) - } - - fn bind_icmp(&self, addr: IpAddr) -> Result> { - Err(NetworkError::Unsupported) + let socket = tokio::net::UdpSocket::bind(addr) + .await + .map_err(io_err_into_net_error)?; + Ok(Box::new(LocalUdpSocket { + socket: LocalUdpSocketMode::Async(socket), + addr, + nonblocking: false + })) } - fn connect_tcp( + async fn connect_tcp( &self, _addr: SocketAddr, peer: SocketAddr, timeout: Option, ) -> Result> { let stream = if let Some(timeout) = timeout { - std::net::TcpStream::connect_timeout(&peer, timeout) + match tokio::time::timeout(timeout, tokio::net::TcpStream::connect(&peer)) + .await + { + Ok(a) => a, + Err(err) => { + Err(Into::::into(std::io::ErrorKind::TimedOut)) + } + } } else { - std::net::TcpStream::connect(peer) + tokio::net::TcpStream::connect(peer).await } .map_err(io_err_into_net_error)?; let peer = stream.peer_addr().map_err(io_err_into_net_error)?; Ok(Box::new(LocalTcpStream { - stream, + stream: stream, addr: peer, connect_timeout: None, + read_timeout: None, + write_timeout: None, + linger_timeout: None, + nonblocking: false, + shutdown: None })) } - fn resolve( + async fn resolve( &self, host: &str, port: Option, dns_server: Option, ) -> Result> { - use std::net::ToSocketAddrs; - Ok(if let Some(port) = port { - let host = format!("{}:{}", host, port); - host.to_socket_addrs() - .map(|a| a.map(|a| a.ip()).collect::>()) - .map_err(io_err_into_net_error)? - } else { - host.to_socket_addrs() - .map(|a| a.map(|a| a.ip()).collect::>()) - .map_err(io_err_into_net_error)? - }) + tokio::net::lookup_host(host) + .await + .map(|a| a.map(|a| a.ip()).collect::>()) + .map_err(io_err_into_net_error) } } #[derive(Debug)] pub struct LocalTcpListener { - stream: std::net::TcpListener, + stream: tokio::net::TcpListener, timeout: Option, + backlog: Mutex, SocketAddr)>>, + nonblocking: bool, } +#[async_trait::async_trait] impl VirtualTcpListener for LocalTcpListener { - fn accept(&self) -> Result<(Box, SocketAddr)> { - if let Some(timeout) = &self.timeout { - return self.accept_timeout(*timeout); + async fn accept(&mut self) -> Result<(Box, SocketAddr)> { + { + let mut backlog = self.backlog.lock().unwrap(); + if let Some((sock, addr)) = backlog.pop() { + return Ok((sock, addr)); + } } - let (sock, addr) = self - .stream - .accept() + + let nonblocking = self.nonblocking; + if nonblocking { + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + return match self.stream + .poll_accept(&mut cx) + .map_err(io_err_into_net_error) + { + Poll::Ready(Ok((sock, addr))) => { + Ok( + ( + Box::new(LocalTcpStream { + stream: sock, + addr, + connect_timeout: None, + read_timeout: None, + write_timeout: None, + linger_timeout: None, + nonblocking, + shutdown: None + }), + addr, + ) + ) + }, + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock) + }; + } + + let timeout = self.timeout.clone(); + let work = async move { + match timeout { + Some(timeout) => { + tokio::time::timeout(timeout, self.stream.accept()) + .await + .map_err(|_| Into::::into(std::io::ErrorKind::WouldBlock))? + }, + None => self.stream.accept().await + } + }; + + let (sock, addr) = work + .await .map(|(sock, addr)| { ( Box::new(LocalTcpStream { stream: sock, addr, connect_timeout: None, + read_timeout: None, + write_timeout: None, + linger_timeout: None, + nonblocking, + shutdown: None }), addr, ) @@ -193,34 +187,65 @@ impl VirtualTcpListener for LocalTcpListener { Ok((sock, addr)) } - #[cfg(feature = "wasix")] - fn accept_timeout( - &self, - timeout: Duration, - ) -> Result<(Box, SocketAddr)> { - let (sock, addr) = self - .stream - .accept_timeout(timeout) - .map(|(sock, addr)| { - ( + fn peek(&mut self) -> Result { + { + let backlog = self.backlog.lock().unwrap(); + if backlog.is_empty() == false { + return Ok(backlog.len()); + } + } + + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + match self.stream.poll_accept(&mut cx) { + Poll::Ready(Ok((sock, addr))) => { + let mut backlog = self.backlog.lock().unwrap(); + backlog.push(( Box::new(LocalTcpStream { stream: sock, - addr: addr.clone(), + addr, connect_timeout: None, + read_timeout: None, + write_timeout: None, + linger_timeout: None, + nonblocking: self.nonblocking, + shutdown: None }), addr, - ) - }) - .map_err(io_err_into_net_error)?; - Ok((sock, addr)) + )); + Ok(backlog.len()) + }, + Poll::Ready(Err(err)) => { + Err(io_err_into_net_error(err)) + } + Poll::Pending => { + let backlog = self.backlog.lock().unwrap(); + Ok(backlog.len()) + } + } } - #[cfg(not(feature = "wasix"))] - fn accept_timeout( - &self, - _timeout: Duration, - ) -> Result<(Box, SocketAddr)> { - self.accept() + fn poll_accept_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll> { + self.stream + .poll_accept(cx) + .map_err(io_err_into_net_error) + .map_ok(|(sock, addr)| { + let mut backlog = self.backlog.lock().unwrap(); + backlog.push(( + Box::new(LocalTcpStream { + stream: sock, + addr, + connect_timeout: None, + read_timeout: None, + write_timeout: None, + linger_timeout: None, + nonblocking: self.nonblocking, + shutdown: None + }), + addr, + )); + backlog.len() + }) } /// Sets the accept timeout @@ -239,57 +264,65 @@ impl VirtualTcpListener for LocalTcpListener { } fn set_ttl(&mut self, ttl: u8) -> Result<()> { - self.stream - .set_ttl(ttl as u32) - .map_err(io_err_into_net_error) + self.stream.set_ttl(ttl as u32).map_err(io_err_into_net_error) } fn ttl(&self) -> Result { - self.stream - .ttl() - .map(|ttl| ttl as u8) - .map_err(io_err_into_net_error) + self.stream.ttl().map(|ttl| ttl as u8).map_err(io_err_into_net_error) + } + + fn set_nonblocking(&mut self, nonblocking: bool) -> Result<()> { + self.nonblocking = nonblocking; + Ok(()) + } + + fn nonblocking(&self) -> Result { + Ok(self.nonblocking) } } #[derive(Debug)] pub struct LocalTcpStream { - stream: std::net::TcpStream, + stream: tokio::net::TcpStream, addr: SocketAddr, + read_timeout: Option, + write_timeout: Option, connect_timeout: Option, + linger_timeout: Option, + nonblocking: bool, + shutdown: Option, } +#[async_trait::async_trait] impl VirtualTcpSocket for LocalTcpStream { fn set_opt_time(&mut self, ty: TimeType, timeout: Option) -> Result<()> { match ty { - TimeType::ReadTimeout => self - .stream - .set_read_timeout(timeout) - .map_err(io_err_into_net_error), - TimeType::WriteTimeout => self - .stream - .set_write_timeout(timeout) - .map_err(io_err_into_net_error), + TimeType::ReadTimeout => { + self.read_timeout = timeout.clone(); + }, + TimeType::WriteTimeout => { + self.write_timeout = timeout.clone(); + }, TimeType::ConnectTimeout => { self.connect_timeout = timeout; - Ok(()) } #[cfg(feature = "wasix")] - TimeType::Linger => self - .stream - .set_linger(timeout) - .map_err(io_err_into_net_error), - _ => Err(NetworkError::InvalidInput), + TimeType::Linger => { + self.linger_timeout = timeout.clone(); + }, + _ => { + return Err(NetworkError::InvalidInput) + }, } + Ok(()) } fn opt_time(&self, ty: TimeType) -> Result> { match ty { - TimeType::ReadTimeout => self.stream.read_timeout().map_err(io_err_into_net_error), - TimeType::WriteTimeout => self.stream.write_timeout().map_err(io_err_into_net_error), + TimeType::ReadTimeout => Ok(self.read_timeout), + TimeType::WriteTimeout => Ok(self.write_timeout), TimeType::ConnectTimeout => Ok(self.connect_timeout), - #[cfg(feature = "wasix")] - TimeType::Linger => self.stream.linger().map_err(io_err_into_net_error), + TimeType::Linger => Ok(self.linger_timeout), _ => Err(NetworkError::InvalidInput), } } @@ -311,9 +344,7 @@ impl VirtualTcpSocket for LocalTcpStream { } fn set_nodelay(&mut self, nodelay: bool) -> Result<()> { - self.stream - .set_nodelay(nodelay) - .map_err(io_err_into_net_error) + self.stream.set_nodelay(nodelay).map_err(io_err_into_net_error) } fn nodelay(&self) -> Result { @@ -324,15 +355,18 @@ impl VirtualTcpSocket for LocalTcpStream { Ok(self.addr) } - fn flush(&mut self) -> Result<()> { + async fn flush(&mut self) -> Result<()> { Ok(()) } - fn shutdown(&mut self, how: Shutdown) -> Result<()> { - self.stream.shutdown(how).map_err(io_err_into_net_error) + async fn shutdown(&mut self, how: Shutdown) -> Result<()> { + self.stream.flush().await.map_err(io_err_into_net_error)?; + self.shutdown = Some(how); + Ok(()) } } +#[async_trait::async_trait] impl VirtualConnectedSocket for LocalTcpStream { fn set_linger(&mut self, linger: Option) -> Result<()> { #[cfg(feature = "wasix")] @@ -352,46 +386,227 @@ impl VirtualConnectedSocket for LocalTcpStream { Ok(None) } - fn send(&mut self, data: Bytes) -> Result { - self.stream - .write_all(&data[..]) + async fn send(&mut self, data: Bytes) -> Result { + let nonblocking = self.nonblocking; + if nonblocking { + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + if self.stream.poll_write_ready(&mut cx).is_pending() { + return Err(NetworkError::WouldBlock); + } + } + + use tokio::io::AsyncWriteExt; + let timeout = self.write_timeout.clone(); + let work = async move { + match timeout { + Some(timeout) => { + tokio::time::timeout(timeout, self.stream.write_all(&data[..])) + .await + .map_err(|_| Into::::into(std::io::ErrorKind::WouldBlock))? + }, + None => self.stream.write_all(&data[..]).await + } .map(|_| data.len()) - .map_err(io_err_into_net_error) + }; + + let amt = work + .await + .map_err(io_err_into_net_error)?; + if amt == 0 { + if nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + Ok(amt) } - fn flush(&mut self) -> Result<()> { - self.stream.flush().map_err(io_err_into_net_error) + async fn flush(&mut self) -> Result<()> { + if self.nonblocking { + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + if self.stream.poll_write_ready(&mut cx).is_pending() { + return Err(NetworkError::WouldBlock); + } + } + use tokio::io::AsyncWriteExt; + let timeout = self.write_timeout.clone(); + let work = async move { + match timeout { + Some(timeout) => { + tokio::time::timeout(timeout, self.stream.flush()) + .await + .map_err(|_| Into::::into(std::io::ErrorKind::WouldBlock))? + }, + None => self.stream.flush().await + } + }; + + work + .await + .map_err(io_err_into_net_error) } - fn recv(&mut self) -> Result { - let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); - let read = self - .stream - .read(&mut buf[..]) + async fn recv(&mut self) -> Result { + use tokio::io::AsyncReadExt; + let max_buf_size = 8192; + let mut buf = Vec::with_capacity(max_buf_size); + unsafe { buf.set_len(max_buf_size); } + + let nonblocking = self.nonblocking; + if nonblocking { + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let stream = Pin::new(&mut self.stream); + let mut read_buf = tokio::io::ReadBuf::new(&mut buf); + return match stream.poll_read(&mut cx, &mut read_buf) { + Poll::Ready(Ok(read)) => { + let read = read_buf.remaining(); + unsafe { buf.set_len(read); } + if read == 0 { + return Err(NetworkError::WouldBlock); + } + let buf = Bytes::from(buf); + Ok(SocketReceive { + data: buf, + truncated: read == max_buf_size, + }) + }, + Poll::Ready(Err(err)) => { + Err(io_err_into_net_error(err)) + }, + Poll::Pending => { + Err(NetworkError::WouldBlock) + } + }; + } + + let timeout = self.write_timeout.clone(); + let work = async move { + match timeout { + Some(timeout) => { + tokio::time::timeout(timeout, self.stream.read(&mut buf[..])) + .await + .map_err(|_| Into::::into(std::io::ErrorKind::WouldBlock))? + }, + None => self.stream.read(&mut buf[..]).await + } + .map(|read| { + unsafe { buf.set_len(read); } + Bytes::from(buf) + }) + }; + + let buf = work + .await .map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + if buf.is_empty() { + if nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } Ok(SocketReceive { + truncated: buf.len() == max_buf_size, data: buf, - truncated: read == buf_size, }) } - fn peek(&mut self) -> Result { - let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); - let read = self - .stream - .peek(&mut buf[..]) + fn try_recv(&mut self) -> Result> { + let max_buf_size = 8192; + let mut buf = Vec::with_capacity(max_buf_size); + unsafe { buf.set_len(max_buf_size); } + + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let stream = Pin::new(&mut self.stream); + let mut read_buf = tokio::io::ReadBuf::new(&mut buf); + match stream.poll_read(&mut cx, &mut read_buf) { + Poll::Ready(Ok(read)) => { + let read = read_buf.remaining(); + unsafe { buf.set_len(read); } + if read == 0 { + return Err(NetworkError::WouldBlock); + } + let buf = Bytes::from(buf); + Ok(Some(SocketReceive { + data: buf, + truncated: read == max_buf_size, + })) + }, + Poll::Ready(Err(err)) => { + Err(io_err_into_net_error(err)) + }, + Poll::Pending => { + Ok(None) + } + } + } + + async fn peek(&mut self) -> Result { + let max_buf_size = 8192; + let mut buf = Vec::with_capacity(max_buf_size); + unsafe { buf.set_len(max_buf_size); } + + if self.nonblocking { + let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let stream = Pin::new(&mut self.stream); + let mut read_buf = tokio::io::ReadBuf::new(&mut buf); + return match stream.poll_peek(&mut cx, &mut read_buf) { + Poll::Ready(Ok(read)) => { + unsafe { buf.set_len(read); } + if read == 0 { + return Err(NetworkError::WouldBlock); + } + let buf = Bytes::from(buf); + Ok(SocketReceive { + data: buf, + truncated: read == max_buf_size, + }) + }, + Poll::Ready(Err(err)) => { + Err(io_err_into_net_error(err)) + }, + Poll::Pending => { + Err(NetworkError::WouldBlock) + } + }; + } + + let timeout = self.write_timeout.clone(); + let work = async move { + match timeout { + Some(timeout) => { + tokio::time::timeout(timeout, self.stream.peek(&mut buf[..])) + .await + .map_err(|_| Into::::into(std::io::ErrorKind::WouldBlock))? + }, + None => self.stream.peek(&mut buf[..]).await + } + .map(|read| { + unsafe { buf.set_len(read); } + Bytes::from(buf) + }) + }; + + let buf = work + .await .map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + if buf.len() == 0 { + return Err(NetworkError::BrokenPipe); + } Ok(SocketReceive { - data: buf, - truncated: read == buf_size, + truncated: buf.len() == max_buf_size, + data: buf, }) } } +#[async_trait::async_trait] impl VirtualSocket for LocalTcpStream { fn set_ttl(&mut self, ttl: u32) -> Result<()> { self.stream.set_ttl(ttl).map_err(io_err_into_net_error) @@ -401,6 +616,15 @@ impl VirtualSocket for LocalTcpStream { self.stream.ttl().map_err(io_err_into_net_error) } + fn set_nonblocking(&mut self, nonblocking: bool) -> Result<()> { + self.nonblocking = nonblocking; + Ok(()) + } + + fn nonblocking(&self) -> Result { + Ok(self.nonblocking) + } + fn addr_local(&self) -> Result { self.stream.local_addr().map_err(io_err_into_net_error) } @@ -408,85 +632,231 @@ impl VirtualSocket for LocalTcpStream { fn status(&self) -> Result { Ok(SocketStatus::Opened) } + + fn poll_read_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll> + { + self.stream + .poll_read_ready(cx) + .map_ok(|a| 8192usize) + .map_err(io_err_into_net_error) + } + + fn poll_write_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll> + { + self.stream + .poll_write_ready(cx) + .map_ok(|a| 8192usize) + .map_err(io_err_into_net_error) + } +} + +struct LocalTcpStreamReadReady<'a> { + stream: &'a mut tokio::net::TcpStream, +} +impl<'a> Future +for LocalTcpStreamReadReady<'a> +{ + type Output = Result; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + self.stream + .poll_read_ready(cx) + .map_err(io_err_into_net_error) + .map_ok(|_| 1usize) + } +} + +struct LocalTcpStreamWriteReady<'a> { + stream: &'a mut tokio::net::TcpStream, +} +impl<'a> Future +for LocalTcpStreamWriteReady<'a> +{ + type Output = Result; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + self.stream + .poll_write_ready(cx) + .map_err(io_err_into_net_error) + .map_ok(|_| 1usize) + } } #[derive(Debug)] -pub struct LocalUdpSocket(std::net::UdpSocket, SocketAddr); +pub struct LocalUdpSocket { + socket: LocalUdpSocketMode, + #[allow(dead_code)] + addr: SocketAddr, + nonblocking: bool +} +#[derive(Debug)] +enum LocalUdpSocketMode { + Blocking(std::net::UdpSocket), + Async(tokio::net::UdpSocket), + Uninitialized +} + +impl LocalUdpSocketMode +{ + fn as_blocking_mut(&mut self) -> std::io::Result<&mut std::net::UdpSocket> { + match self { + Self::Blocking(a) => Ok(a), + Self::Async(_) => { + let mut listener = Self::Uninitialized; + std::mem::swap(self, &mut listener); + listener = match listener { + Self::Async(a) => Self::Blocking(a.into_std()?), + a => unreachable!(), + }; + std::mem::swap(self, &mut listener); + match self { + Self::Blocking(a) => Ok(a), + _ => unreachable!() + } + }, + Self::Uninitialized => unreachable!() + } + } + + fn as_async_mut(&mut self) -> std::io::Result<&mut tokio::net::UdpSocket> { + match self { + Self::Async(a) => Ok(a), + Self::Blocking(_) => { + let mut listener = Self::Uninitialized; + std::mem::swap(self, &mut listener); + listener = match listener { + Self::Blocking(a) => Self::Async(tokio::net::UdpSocket::from_std(a)?), + a => unreachable!(), + }; + std::mem::swap(self, &mut listener); + match self { + Self::Async(a) => Ok(a), + _ => unreachable!() + } + }, + Self::Uninitialized => unreachable!() + } + } +} + +#[async_trait::async_trait] impl VirtualUdpSocket for LocalUdpSocket { - fn connect(&mut self, addr: SocketAddr) -> Result<()> { - self.0.connect(addr).map_err(io_err_into_net_error) + async fn connect(&mut self, addr: SocketAddr) -> Result<()> { + self.socket + .as_async_mut() + .map_err(io_err_into_net_error)? + .connect(addr) + .await + .map_err(io_err_into_net_error) } fn set_broadcast(&mut self, broadcast: bool) -> Result<()> { - self.0 - .set_broadcast(broadcast) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.set_broadcast(broadcast).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.set_broadcast(broadcast).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn broadcast(&self) -> Result { - self.0.broadcast().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.broadcast().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.broadcast().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn set_multicast_loop_v4(&mut self, val: bool) -> Result<()> { - self.0 - .set_multicast_loop_v4(val) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.set_multicast_loop_v4(val).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.set_multicast_loop_v4(val).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn multicast_loop_v4(&self) -> Result { - self.0.multicast_loop_v4().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.multicast_loop_v4().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.multicast_loop_v4().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn set_multicast_loop_v6(&mut self, val: bool) -> Result<()> { - self.0 - .set_multicast_loop_v6(val) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.set_multicast_loop_v6(val).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.set_multicast_loop_v6(val).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn multicast_loop_v6(&self) -> Result { - self.0.multicast_loop_v6().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.multicast_loop_v6().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.multicast_loop_v6().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn set_multicast_ttl_v4(&mut self, ttl: u32) -> Result<()> { - self.0 - .set_multicast_ttl_v4(ttl) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.set_multicast_ttl_v4(ttl).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.set_multicast_ttl_v4(ttl).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn multicast_ttl_v4(&self) -> Result { - self.0.multicast_ttl_v4().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.multicast_ttl_v4().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.multicast_ttl_v4().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn join_multicast_v4(&mut self, multiaddr: Ipv4Addr, iface: Ipv4Addr) -> Result<()> { - self.0 - .join_multicast_v4(&multiaddr, &iface) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.join_multicast_v4(&multiaddr, &iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.join_multicast_v4(multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn leave_multicast_v4(&mut self, multiaddr: Ipv4Addr, iface: Ipv4Addr) -> Result<()> { - self.0 - .leave_multicast_v4(&multiaddr, &iface) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.leave_multicast_v4(&multiaddr, &iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.leave_multicast_v4(multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn join_multicast_v6(&mut self, multiaddr: Ipv6Addr, iface: u32) -> Result<()> { - self.0 - .join_multicast_v6(&multiaddr, iface) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.join_multicast_v6(&multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.join_multicast_v6(&multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn leave_multicast_v6(&mut self, multiaddr: Ipv6Addr, iface: u32) -> Result<()> { - self.0 - .leave_multicast_v6(&multiaddr, iface) - .map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.leave_multicast_v6(&multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.leave_multicast_v6(&multiaddr, iface).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn addr_peer(&self) -> Result> { - self.0.peer_addr().map(Some).map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.peer_addr().map(Some).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.peer_addr().map(Some).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } } +#[async_trait::async_trait] impl VirtualConnectedSocket for LocalUdpSocket { fn set_linger(&mut self, linger: Option) -> Result<()> { Err(NetworkError::Unsupported) @@ -496,30 +866,103 @@ impl VirtualConnectedSocket for LocalUdpSocket { Err(NetworkError::Unsupported) } - fn send(&mut self, data: Bytes) -> Result { - self.0.send(&data[..]).map_err(io_err_into_net_error) + async fn send(&mut self, data: Bytes) -> Result { + let amt = self.socket + .as_async_mut() + .map_err(io_err_into_net_error)? + .send(&data[..]) + .await + .map_err(io_err_into_net_error)?; + if amt == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + Ok(amt) } - fn flush(&mut self) -> Result<()> { + async fn flush(&mut self) -> Result<()> { Ok(()) } - fn recv(&mut self) -> Result { + async fn recv(&mut self) -> Result { let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); - let read = self.0.recv(&mut buf[..]).map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + + let read = self.socket + .as_async_mut() + .map_err(io_err_into_net_error)? + .recv(&mut buf[..]) + .await + .map_err(io_err_into_net_error)?; + unsafe { buf.set_len(read); } + if read == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + let buf = Bytes::from(buf); Ok(SocketReceive { data: buf, truncated: read == buf_size, }) } - fn peek(&mut self) -> Result { + fn try_recv(&mut self) -> Result> { let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); - let read = self.0.peek(&mut buf[..]).map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + + let socket = self.socket.as_blocking_mut().map_err(io_err_into_net_error)?; + socket.set_nonblocking(true).map_err(io_err_into_net_error)?; + let read = socket.recv(&mut buf[..]); + let _ = socket.set_nonblocking(self.nonblocking); + + let read = match read { + Ok(0) => { + return Ok(None); + } + Ok(a) => Ok(a), + Err(err) if err.kind() == std::io::ErrorKind::TimedOut || + err.kind() == std::io::ErrorKind::WouldBlock => { + return Ok(None); + }, + Err(err) => Err(io_err_into_net_error(err)) + }?; + unsafe { buf.set_len(read); } + + let buf = Bytes::from(buf); + Ok(Some(SocketReceive { + data: buf, + truncated: read == buf_size, + })) + } + + async fn peek(&mut self) -> Result { + let buf_size = 8192; + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + + let read = self.socket + .as_blocking_mut() + .map_err(io_err_into_net_error)? + .peek(&mut buf[..]) + .map_err(io_err_into_net_error)?; + unsafe { buf.set_len(read); } + if read == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + + let buf = Bytes::from(buf); Ok(SocketReceive { data: buf, truncated: read == buf_size, @@ -527,21 +970,77 @@ impl VirtualConnectedSocket for LocalUdpSocket { } } +#[async_trait::async_trait] impl VirtualConnectionlessSocket for LocalUdpSocket { - fn send_to(&mut self, data: Bytes, addr: SocketAddr) -> Result { - self.0 + async fn send_to(&mut self, data: Bytes, addr: SocketAddr) -> Result { + let amt = self.socket + .as_async_mut() + .map_err(io_err_into_net_error)? .send_to(&data[..], addr) - .map_err(io_err_into_net_error) + .await + .map_err(io_err_into_net_error)?; + if amt == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + Ok(amt) } - fn recv_from(&mut self) -> Result { + fn try_recv_from(&mut self) -> Result> { let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + + let socket = self.socket.as_blocking_mut().map_err(io_err_into_net_error)?; + socket.set_nonblocking(true).map_err(io_err_into_net_error)?; + let read = socket.recv_from(&mut buf[..]); + let _ = socket.set_nonblocking(self.nonblocking); + + let (read, peer) = match read { + Ok((0, _))=> { + return Ok(None); + } + Ok((a, b)) => Ok((a, b)), + Err(err) if err.kind() == std::io::ErrorKind::TimedOut || + err.kind() == std::io::ErrorKind::WouldBlock => { + return Ok(None); + }, + Err(err) => Err(io_err_into_net_error(err)) + }?; + unsafe { buf.set_len(read); } + + let buf = Bytes::from(buf); + Ok(Some(SocketReceiveFrom { + data: buf, + truncated: read == buf_size, + addr: peer, + })) + } + + async fn recv_from(&mut self) -> Result { + let buf_size = 8192; + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + let (read, peer) = self - .0 + .socket + .as_async_mut() + .map_err(io_err_into_net_error)? .recv_from(&mut buf[..]) + .await .map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + unsafe { buf.set_len(read); } + if read == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + let buf = Bytes::from(buf); Ok(SocketReceiveFrom { data: buf, truncated: read == buf_size, @@ -551,12 +1050,24 @@ impl VirtualConnectionlessSocket for LocalUdpSocket { fn peek_from(&mut self) -> Result { let buf_size = 8192; - let mut buf = BytesMut::with_capacity(buf_size); + let mut buf = Vec::with_capacity(buf_size); + unsafe { buf.set_len(buf_size); } + let (read, peer) = self - .0 + .socket + .as_blocking_mut() + .map_err(io_err_into_net_error)? .peek_from(&mut buf[..]) .map_err(io_err_into_net_error)?; - let buf = Bytes::from(buf).slice(..read); + unsafe { buf.set_len(read); } + if read == 0 { + if self.nonblocking { + return Err(NetworkError::WouldBlock); + } else { + return Err(NetworkError::BrokenPipe); + } + } + let buf = Bytes::from(buf); Ok(SocketReceiveFrom { data: buf, truncated: read == buf_size, @@ -565,20 +1076,108 @@ impl VirtualConnectionlessSocket for LocalUdpSocket { } } +#[async_trait::async_trait] impl VirtualSocket for LocalUdpSocket { fn set_ttl(&mut self, ttl: u32) -> Result<()> { - self.0.set_ttl(ttl).map_err(io_err_into_net_error) + match &mut self.socket { + LocalUdpSocketMode::Blocking(a) => a.set_ttl(ttl).map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.set_ttl(ttl).map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } + } + + fn set_nonblocking(&mut self, nonblocking: bool) -> Result<()> { + self.nonblocking = nonblocking; + self.socket + .as_blocking_mut() + .map_err(io_err_into_net_error)? + .set_nonblocking(nonblocking) + .map_err(io_err_into_net_error)?; + Ok(()) + } + + fn nonblocking(&self) -> Result { + Ok(self.nonblocking) } fn ttl(&self) -> Result { - self.0.ttl().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.ttl().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.ttl().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn addr_local(&self) -> Result { - self.0.local_addr().map_err(io_err_into_net_error) + match &self.socket { + LocalUdpSocketMode::Blocking(a) => a.local_addr().map_err(io_err_into_net_error), + LocalUdpSocketMode::Async(a) => a.local_addr().map_err(io_err_into_net_error), + LocalUdpSocketMode::Uninitialized => unreachable!() + } } fn status(&self) -> Result { Ok(SocketStatus::Opened) } + + fn poll_read_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll> + { + let socket = self.socket + .as_async_mut() + .map_err(io_err_into_net_error)?; + socket + .poll_recv_ready(cx) + .map_ok(|a| 8192usize) + .map_err(io_err_into_net_error) + + } + + fn poll_write_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll> + { + let socket = self.socket + .as_async_mut() + .map_err(io_err_into_net_error)?; + socket + .poll_send_ready(cx) + .map_ok(|a| 8192usize) + .map_err(io_err_into_net_error) + } +} + +struct LocalUdpSocketReadReady<'a> { + socket: &'a mut tokio::net::UdpSocket, +} +impl<'a> Future +for LocalUdpSocketReadReady<'a> +{ + type Output = Result; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + self.socket + .poll_recv_ready(cx) + .map_err(io_err_into_net_error) + .map_ok(|_| 1usize) + } +} + +struct LocalUdpSocketWriteReady<'a> { + socket: &'a mut tokio::net::UdpSocket, +} +impl<'a> Future +for LocalUdpSocketWriteReady<'a> +{ + type Output = Result; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + self.socket + .poll_send_ready(cx) + .map_err(io_err_into_net_error) + .map_ok(|_| 1usize) + } +} + +const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); +unsafe fn noop_clone(_data: *const ()) -> RawWaker { + RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE) } +unsafe fn noop(_data: *const ()) {} diff --git a/lib/wasi-types/src/asyncify.rs b/lib/wasi-types/src/asyncify.rs new file mode 100644 index 00000000000..6b0b63a5721 --- /dev/null +++ b/lib/wasi-types/src/asyncify.rs @@ -0,0 +1,9 @@ +use wasmer_derive::ValueType; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_asyncify_t +where O: wasmer_types::ValueType { + pub start: O, + pub end: O, +} diff --git a/lib/wasi-types/src/bus.rs b/lib/wasi-types/src/bus.rs new file mode 100644 index 00000000000..3cd543e5eb2 --- /dev/null +++ b/lib/wasi-types/src/bus.rs @@ -0,0 +1,127 @@ +use super::*; +use wasmer_derive::ValueType; + +pub type __wasi_small_hash_t = u64; + +pub type __wasi_hash_t = u128; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_option_hash_t { + pub tag: __wasi_option_t, + pub u: __wasi_hash_t, +} + +pub type __wasi_busdataformat_t = u8; +pub const __WASI_BUS_DATA_FORMAT_RAW: __wasi_busdataformat_t = 0; +pub const __WASI_BUS_DATA_FORMAT_BINCODE: __wasi_busdataformat_t = 1; +pub const __WASI_BUS_DATA_FORMAT_MESSAGE_PACK: __wasi_busdataformat_t = 2; +pub const __WASI_BUS_DATA_FORMAT_JSON: __wasi_busdataformat_t = 3; +pub const __WASI_BUS_DATA_FORMAT_YAML: __wasi_busdataformat_t = 4; +pub const __WASI_BUS_DATA_FORMAT_XML: __wasi_busdataformat_t = 5; +pub const __WASI_BUS_DATA_FORMAT_RKYV: __wasi_busdataformat_t = 6; + +pub type __wasi_buseventtype_t = u8; +pub const __WASI_BUS_EVENT_TYPE_NOOP: __wasi_buseventtype_t = 0; +pub const __WASI_BUS_EVENT_TYPE_EXIT: __wasi_buseventtype_t = 1; +pub const __WASI_BUS_EVENT_TYPE_CALL: __wasi_buseventtype_t = 2; +pub const __WASI_BUS_EVENT_TYPE_RESULT: __wasi_buseventtype_t = 3; +pub const __WASI_BUS_EVENT_TYPE_FAULT: __wasi_buseventtype_t = 4; +pub const __WASI_BUS_EVENT_TYPE_CLOSE: __wasi_buseventtype_t = 5; + +pub type __wasi_bid_t = u32; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_option_bid_t { + pub tag: __wasi_option_t, + pub bid: __wasi_bid_t, +} + +pub type __wasi_cid_t = u64; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_option_fd_t { + pub tag: __wasi_option_t, + pub fd: __wasi_fd_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_option_cid_t { + pub tag: __wasi_option_t, + pub cid: __wasi_cid_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_bus_handles_t { + pub bid: __wasi_bid_t, + pub stdin: __wasi_option_fd_t, + pub stdout: __wasi_option_fd_t, + pub stderr: __wasi_option_fd_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_exit_t { + pub bid: __wasi_bid_t, + pub rval: __wasi_exitcode_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_call_t { + pub parent: __wasi_option_cid_t, + pub cid: __wasi_cid_t, + pub format: __wasi_busdataformat_t, + pub topic_hash: __wasi_hash_t, + pub fd: __wasi_fd_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_result_t { + pub format: __wasi_busdataformat_t, + pub cid: __wasi_cid_t, + pub fd: __wasi_fd_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_fault_t { + pub cid: __wasi_cid_t, + pub err: __bus_errno_t, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_close_t { + pub cid: __wasi_cid_t, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union __wasi_busevent_u { + pub noop: u8, + pub exit: __wasi_busevent_exit_t, + pub call: __wasi_busevent_call_t, + pub result: __wasi_busevent_result_t, + pub fault: __wasi_busevent_fault_t, + pub close: __wasi_busevent_close_t, +} + +#[derive(Copy, Clone, ValueType)] +#[repr(C)] +pub struct __wasi_busevent_t { + pub tag: __wasi_buseventtype_t, + pub padding: [u8; 63], +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __wasi_busevent_t2 { + pub tag: __wasi_buseventtype_t, + pub u: __wasi_busevent_u, +} diff --git a/lib/wasi-types/src/file.rs b/lib/wasi-types/src/file.rs new file mode 100644 index 00000000000..92f0e74a6be --- /dev/null +++ b/lib/wasi-types/src/file.rs @@ -0,0 +1,340 @@ +use crate::*; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + mem::{self, MaybeUninit}, +}; +use wasmer_derive::ValueType; +use wasmer_types::ValueType; + +pub type __wasi_device_t = u64; + +pub type __wasi_fd_t = u32; +pub const __WASI_STDIN_FILENO: __wasi_fd_t = 0; +pub const __WASI_STDOUT_FILENO: __wasi_fd_t = 1; +pub const __WASI_STDERR_FILENO: __wasi_fd_t = 2; + +pub type __wasi_pid_t = u32; +pub type __wasi_tid_t = u32; + +pub type __wasi_tl_key_t = u32; +pub type __wasi_tl_val_t = u64; + +pub type __wasi_fdflags_t = u16; +pub const __WASI_FDFLAG_APPEND: __wasi_fdflags_t = 1 << 0; +pub const __WASI_FDFLAG_DSYNC: __wasi_fdflags_t = 1 << 1; +pub const __WASI_FDFLAG_NONBLOCK: __wasi_fdflags_t = 1 << 2; +pub const __WASI_FDFLAG_RSYNC: __wasi_fdflags_t = 1 << 3; +pub const __WASI_FDFLAG_SYNC: __wasi_fdflags_t = 1 << 4; + +pub type __wasi_eventfdflags = u16; +pub const __WASI_EVENTFDFLAGS_SEMAPHORE: __wasi_eventfdflags = 1 << 0; + +pub type __wasi_preopentype_t = u8; +pub const __WASI_PREOPENTYPE_DIR: __wasi_preopentype_t = 0; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_prestat_u_dir_t { + pub pr_name_len: u32, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union __wasi_prestat_u { + dir: __wasi_prestat_u_dir_t, +} + +impl fmt::Debug for __wasi_prestat_u { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "__wasi_prestat_u") + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct __wasi_prestat_t { + pub pr_type: __wasi_preopentype_t, + pub u: __wasi_prestat_u, +} + +#[derive(Copy, Clone)] +pub enum PrestatEnum { + Dir { pr_name_len: u32 }, +} + +impl PrestatEnum { + pub fn untagged(self) -> __wasi_prestat_u { + match self { + PrestatEnum::Dir { pr_name_len } => __wasi_prestat_u { + dir: __wasi_prestat_u_dir_t { pr_name_len }, + }, + } + } +} + +impl __wasi_prestat_t { + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn tagged(&self) -> Option { + match self.pr_type { + __WASI_PREOPENTYPE_DIR => Some(PrestatEnum::Dir { + pr_name_len: unsafe { self.u.dir.pr_name_len }, + }), + _ => None, + } + } +} + +unsafe impl ValueType for __wasi_prestat_t { + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]) { + macro_rules! field { + ($($f:tt)*) => { + &self.$($f)* as *const _ as usize - self as *const _ as usize + }; + } + macro_rules! field_end { + ($($f:tt)*) => { + field!($($f)*) + mem::size_of_val(&self.$($f)*) + }; + } + macro_rules! zero { + ($start:expr, $end:expr) => { + for i in $start..$end { + bytes[i] = MaybeUninit::new(0); + } + }; + } + self.pr_type + .zero_padding_bytes(&mut bytes[field!(pr_type)..field_end!(pr_type)]); + zero!(field_end!(pr_type), field!(u)); + match self.pr_type { + __WASI_PREOPENTYPE_DIR => unsafe { + self.u + .dir + .zero_padding_bytes(&mut bytes[field!(u.dir)..field_end!(u.dir)]); + zero!(field_end!(u.dir), field_end!(u)); + }, + _ => zero!(field!(u), field_end!(u)), + } + zero!(field_end!(u), mem::size_of_val(self)); + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] +#[repr(C)] +pub struct __wasi_fdstat_t { + pub fs_filetype: __wasi_filetype_t, + pub fs_flags: __wasi_fdflags_t, + pub fs_rights_base: __wasi_rights_t, + pub fs_rights_inheriting: __wasi_rights_t, +} + +pub type __wasi_filedelta_t = i64; + +pub type __wasi_filesize_t = u64; + +#[derive(Copy, Clone, PartialEq, Eq, ValueType)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +#[repr(C)] +pub struct __wasi_filestat_t { + pub st_dev: __wasi_device_t, + pub st_ino: __wasi_inode_t, + pub st_filetype: __wasi_filetype_t, + pub st_nlink: __wasi_linkcount_t, + pub st_size: __wasi_filesize_t, + pub st_atim: __wasi_timestamp_t, + pub st_mtim: __wasi_timestamp_t, + pub st_ctim: __wasi_timestamp_t, +} + +impl Default for __wasi_filestat_t { + fn default() -> Self { + __wasi_filestat_t { + st_dev: Default::default(), + st_ino: Default::default(), + st_filetype: __WASI_FILETYPE_UNKNOWN, + st_nlink: 1, + st_size: Default::default(), + st_atim: Default::default(), + st_mtim: Default::default(), + st_ctim: Default::default(), + } + } +} + +impl fmt::Debug for __wasi_filestat_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let convert_ts_into_time_string = |ts| { + let tspec = ::time::OffsetDateTime::from_unix_timestamp_nanos(ts); + format!("{} ({})", tspec.format("%a, %d %b %Y %T %z"), ts) + }; + f.debug_struct("__wasi_filestat_t") + .field("st_dev", &self.st_dev) + .field("st_ino", &self.st_ino) + .field( + "st_filetype", + &format!( + "{} ({})", + wasi_filetype_to_name(self.st_filetype), + self.st_filetype, + ), + ) + .field("st_nlink", &self.st_nlink) + .field("st_size", &self.st_size) + .field( + "st_atim", + &convert_ts_into_time_string(self.st_atim as i128), + ) + .field( + "st_mtim", + &convert_ts_into_time_string(self.st_mtim as i128), + ) + .field( + "st_ctim", + &convert_ts_into_time_string(self.st_ctim as i128), + ) + .finish() + } +} + +pub fn wasi_filetype_to_name(ft: __wasi_filetype_t) -> &'static str { + match ft { + __WASI_FILETYPE_UNKNOWN => "Unknown", + __WASI_FILETYPE_BLOCK_DEVICE => "Block device", + __WASI_FILETYPE_CHARACTER_DEVICE => "Character device", + __WASI_FILETYPE_DIRECTORY => "Directory", + __WASI_FILETYPE_REGULAR_FILE => "Regular file", + __WASI_FILETYPE_SOCKET_DGRAM => "Socket dgram", + __WASI_FILETYPE_SOCKET_STREAM => "Socket stream", + __WASI_FILETYPE_SYMBOLIC_LINK => "Symbolic link", + _ => "Invalid", + } +} + +pub type __wasi_filetype_t = u8; +pub const __WASI_FILETYPE_UNKNOWN: __wasi_filetype_t = 0; +pub const __WASI_FILETYPE_BLOCK_DEVICE: __wasi_filetype_t = 1; +pub const __WASI_FILETYPE_CHARACTER_DEVICE: __wasi_filetype_t = 2; +pub const __WASI_FILETYPE_DIRECTORY: __wasi_filetype_t = 3; +pub const __WASI_FILETYPE_REGULAR_FILE: __wasi_filetype_t = 4; +pub const __WASI_FILETYPE_SOCKET_DGRAM: __wasi_filetype_t = 5; +pub const __WASI_FILETYPE_SOCKET_STREAM: __wasi_filetype_t = 6; +pub const __WASI_FILETYPE_SYMBOLIC_LINK: __wasi_filetype_t = 7; +pub const __WASI_FILETYPE_SOCKET_RAW: __wasi_filetype_t = 8; +pub const __WASI_FILETYPE_SOCKET_SEQPACKET: __wasi_filetype_t = 9; + +pub type __wasi_fstflags_t = u16; +pub const __WASI_FILESTAT_SET_ATIM: __wasi_fstflags_t = 1 << 0; +pub const __WASI_FILESTAT_SET_ATIM_NOW: __wasi_fstflags_t = 1 << 1; +pub const __WASI_FILESTAT_SET_MTIM: __wasi_fstflags_t = 1 << 2; +pub const __WASI_FILESTAT_SET_MTIM_NOW: __wasi_fstflags_t = 1 << 3; + +pub type __wasi_inode_t = u64; + +pub type __wasi_linkcount_t = u64; + +pub type __wasi_lookupflags_t = u32; +pub const __WASI_LOOKUP_SYMLINK_FOLLOW: __wasi_lookupflags_t = 1 << 0; + +pub type __wasi_oflags_t = u16; +pub const __WASI_O_CREAT: __wasi_oflags_t = 1 << 0; +pub const __WASI_O_DIRECTORY: __wasi_oflags_t = 1 << 1; +pub const __WASI_O_EXCL: __wasi_oflags_t = 1 << 2; +pub const __WASI_O_TRUNC: __wasi_oflags_t = 1 << 3; + +pub type __wasi_rights_t = u64; +pub const __WASI_RIGHT_FD_DATASYNC: __wasi_rights_t = 1 << 0; +pub const __WASI_RIGHT_FD_READ: __wasi_rights_t = 1 << 1; +pub const __WASI_RIGHT_FD_SEEK: __wasi_rights_t = 1 << 2; +pub const __WASI_RIGHT_FD_FDSTAT_SET_FLAGS: __wasi_rights_t = 1 << 3; +pub const __WASI_RIGHT_FD_SYNC: __wasi_rights_t = 1 << 4; +pub const __WASI_RIGHT_FD_TELL: __wasi_rights_t = 1 << 5; +pub const __WASI_RIGHT_FD_WRITE: __wasi_rights_t = 1 << 6; +pub const __WASI_RIGHT_FD_ADVISE: __wasi_rights_t = 1 << 7; +pub const __WASI_RIGHT_FD_ALLOCATE: __wasi_rights_t = 1 << 8; +pub const __WASI_RIGHT_PATH_CREATE_DIRECTORY: __wasi_rights_t = 1 << 9; +pub const __WASI_RIGHT_PATH_CREATE_FILE: __wasi_rights_t = 1 << 10; +pub const __WASI_RIGHT_PATH_LINK_SOURCE: __wasi_rights_t = 1 << 11; +pub const __WASI_RIGHT_PATH_LINK_TARGET: __wasi_rights_t = 1 << 12; +pub const __WASI_RIGHT_PATH_OPEN: __wasi_rights_t = 1 << 13; +pub const __WASI_RIGHT_FD_READDIR: __wasi_rights_t = 1 << 14; +pub const __WASI_RIGHT_PATH_READLINK: __wasi_rights_t = 1 << 15; +pub const __WASI_RIGHT_PATH_RENAME_SOURCE: __wasi_rights_t = 1 << 16; +pub const __WASI_RIGHT_PATH_RENAME_TARGET: __wasi_rights_t = 1 << 17; +pub const __WASI_RIGHT_PATH_FILESTAT_GET: __wasi_rights_t = 1 << 18; +pub const __WASI_RIGHT_PATH_FILESTAT_SET_SIZE: __wasi_rights_t = 1 << 19; +pub const __WASI_RIGHT_PATH_FILESTAT_SET_TIMES: __wasi_rights_t = 1 << 20; +pub const __WASI_RIGHT_FD_FILESTAT_GET: __wasi_rights_t = 1 << 21; +pub const __WASI_RIGHT_FD_FILESTAT_SET_SIZE: __wasi_rights_t = 1 << 22; +pub const __WASI_RIGHT_FD_FILESTAT_SET_TIMES: __wasi_rights_t = 1 << 23; +pub const __WASI_RIGHT_PATH_SYMLINK: __wasi_rights_t = 1 << 24; +pub const __WASI_RIGHT_PATH_REMOVE_DIRECTORY: __wasi_rights_t = 1 << 25; +pub const __WASI_RIGHT_PATH_UNLINK_FILE: __wasi_rights_t = 1 << 26; +pub const __WASI_RIGHT_POLL_FD_READWRITE: __wasi_rights_t = 1 << 27; +pub const __WASI_RIGHT_SOCK_SHUTDOWN: __wasi_rights_t = 1 << 28; +pub const __WASI_RIGHT_SOCK_ACCEPT: __wasi_rights_t = 1 << 29; +pub const __WASI_RIGHT_SOCK_CONNECT: __wasi_rights_t = 1 << 30; +pub const __WASI_RIGHT_SOCK_LISTEN: __wasi_rights_t = 1 << 31; +pub const __WASI_RIGHT_SOCK_BIND: __wasi_rights_t = 1 << 32; +pub const __WASI_RIGHT_SOCK_RECV: __wasi_rights_t = 1 << 33; +pub const __WASI_RIGHT_SOCK_SEND: __wasi_rights_t = 1 << 34; +pub const __WASI_RIGHT_SOCK_ADDR_LOCAL: __wasi_rights_t = 1 << 35; +pub const __WASI_RIGHT_SOCK_ADDR_REMOTE: __wasi_rights_t = 1 << 36; +pub const __WASI_RIGHT_SOCK_RECV_FROM: __wasi_rights_t = 1 << 37; +pub const __WASI_RIGHT_SOCK_SEND_TO: __wasi_rights_t = 1 << 38; + +/// function for debugging rights issues +#[allow(dead_code)] +pub fn print_right_set(rights: __wasi_rights_t) { + // BTreeSet for consistent order + let mut right_set = std::collections::BTreeSet::new(); + for i in 0..28 { + let cur_right = rights & (1 << i); + if cur_right != 0 { + right_set.insert(right_to_string(cur_right).unwrap_or("INVALID RIGHT")); + } + } + println!("{:#?}", right_set); +} + +/// expects a single right, returns None if out of bounds or > 1 bit set +pub fn right_to_string(right: __wasi_rights_t) -> Option<&'static str> { + Some(match right { + __WASI_RIGHT_FD_DATASYNC => "__WASI_RIGHT_FD_DATASYNC", + __WASI_RIGHT_FD_READ => "__WASI_RIGHT_FD_READ", + __WASI_RIGHT_FD_SEEK => "__WASI_RIGHT_FD_SEEK", + __WASI_RIGHT_FD_FDSTAT_SET_FLAGS => "__WASI_RIGHT_FD_FDSTAT_SET_FLAGS", + __WASI_RIGHT_FD_SYNC => "__WASI_RIGHT_FD_SYNC", + __WASI_RIGHT_FD_TELL => "__WASI_RIGHT_FD_TELL", + __WASI_RIGHT_FD_WRITE => "__WASI_RIGHT_FD_WRITE", + __WASI_RIGHT_FD_ADVISE => "__WASI_RIGHT_FD_ADVISE", + __WASI_RIGHT_FD_ALLOCATE => "__WASI_RIGHT_FD_ALLOCATE", + __WASI_RIGHT_PATH_CREATE_DIRECTORY => "__WASI_RIGHT_PATH_CREATE_DIRECTORY", + __WASI_RIGHT_PATH_CREATE_FILE => "__WASI_RIGHT_PATH_CREATE_FILE", + __WASI_RIGHT_PATH_LINK_SOURCE => "__WASI_RIGHT_PATH_LINK_SOURCE", + __WASI_RIGHT_PATH_LINK_TARGET => "__WASI_RIGHT_PATH_LINK_TARGET", + __WASI_RIGHT_PATH_OPEN => "__WASI_RIGHT_PATH_OPEN", + __WASI_RIGHT_FD_READDIR => "__WASI_RIGHT_FD_READDIR", + __WASI_RIGHT_PATH_READLINK => "__WASI_RIGHT_PATH_READLINK", + __WASI_RIGHT_PATH_RENAME_SOURCE => "__WASI_RIGHT_PATH_RENAME_SOURCE", + __WASI_RIGHT_PATH_RENAME_TARGET => "__WASI_RIGHT_PATH_RENAME_TARGET", + __WASI_RIGHT_PATH_FILESTAT_GET => "__WASI_RIGHT_PATH_FILESTAT_GET", + __WASI_RIGHT_PATH_FILESTAT_SET_SIZE => "__WASI_RIGHT_PATH_FILESTAT_SET_SIZE", + __WASI_RIGHT_PATH_FILESTAT_SET_TIMES => "__WASI_RIGHT_PATH_FILESTAT_SET_TIMES", + __WASI_RIGHT_FD_FILESTAT_GET => "__WASI_RIGHT_FD_FILESTAT_GET", + __WASI_RIGHT_FD_FILESTAT_SET_SIZE => "__WASI_RIGHT_FD_FILESTAT_SET_SIZE", + __WASI_RIGHT_FD_FILESTAT_SET_TIMES => "__WASI_RIGHT_FD_FILESTAT_SET_TIMES", + __WASI_RIGHT_PATH_SYMLINK => "__WASI_RIGHT_PATH_SYMLINK", + __WASI_RIGHT_PATH_UNLINK_FILE => "__WASI_RIGHT_PATH_UNLINK_FILE", + __WASI_RIGHT_PATH_REMOVE_DIRECTORY => "__WASI_RIGHT_PATH_REMOVE_DIRECTORY", + __WASI_RIGHT_POLL_FD_READWRITE => "__WASI_RIGHT_POLL_FD_READWRITE", + __WASI_RIGHT_SOCK_SHUTDOWN => "__WASI_RIGHT_SOCK_SHUTDOWN", + _ => return None, + }) +} + +pub type __wasi_whence_t = u8; +pub const __WASI_WHENCE_SET: __wasi_whence_t = 0; +pub const __WASI_WHENCE_CUR: __wasi_whence_t = 1; +pub const __WASI_WHENCE_END: __wasi_whence_t = 2; diff --git a/lib/wasi-types/src/lib.rs b/lib/wasi-types/src/lib.rs index 2e54b95e5f9..89d95f5904b 100644 --- a/lib/wasi-types/src/lib.rs +++ b/lib/wasi-types/src/lib.rs @@ -8,36 +8,37 @@ pub mod wasi; fn fail_if_wit_files_arent_up_to_date() { use wit_bindgen_core::Generator; - let output_wit = concat!(env!("CARGO_MANIFEST_DIR"), "/wit-clean/output.wit"); - let bindings_target = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/wasi/bindings.rs")); - let mut generator = wit_bindgen_rust_wasm::Opts { - ..wit_bindgen_rust_wasm::Opts::default() - } - .build(); - let output_wit_parsed = wit_parser::Interface::parse_file(output_wit).unwrap(); - let imports = vec![output_wit_parsed]; - let exports = vec![]; - let mut files = Default::default(); - generator.generate_all( - &imports, &exports, &mut files, /* generate_structs */ true, - ); - let generated = files - .iter() - .filter_map(|(k, v)| if k == "bindings.rs" { Some(v) } else { None }) - .next() - .unwrap(); - let generated_str = String::from_utf8_lossy(generated); - let generated_str = generated_str - .lines() - .map(|s| s.to_string()) - .collect::>() - .join("\r\n"); - let generated_str = generated_str.replace("mod output {", "pub mod output {"); - let bindings_target = bindings_target - .lines() - .map(|s| s.to_string()) - .collect::>() - .join("\r\n"); - pretty_assertions::assert_eq!(generated_str, bindings_target); // output.wit out of date? regenerate bindings.rs -} +// Needed for #[derive(ValueType)] +extern crate wasmer_types as wasmer; + +mod advice; +mod bus; +mod directory; +mod error; +mod event; +mod file; +mod io; +mod net; +mod signal; +mod subscription; +mod time; +mod versions; +mod asyncify; + +pub use crate::time::*; +pub use advice::*; +pub use bus::*; +pub use directory::*; +pub use error::*; +pub use event::*; +pub use file::*; +pub use io::*; +pub use net::*; +pub use signal::*; +pub use subscription::*; +pub use versions::*; +pub use asyncify::*; + +pub type __wasi_exitcode_t = u32; + +pub type __wasi_userdata_t = u64; diff --git a/lib/wasi-types/src/signal.rs b/lib/wasi-types/src/signal.rs new file mode 100644 index 00000000000..7b23f05c416 --- /dev/null +++ b/lib/wasi-types/src/signal.rs @@ -0,0 +1,42 @@ +use wasmer_derive::ValueType; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, ValueType)] +#[repr(C)] +pub struct __wasi_stack_snaphost_t { + pub user: u64, + pub hash: u128, +} + +pub type __wasi_longsize_t = u64; + +pub type __wasi_signal_t = u8; +pub const __WASI_SIGHUP: u8 = 1; +pub const __WASI_SIGINT: u8 = 2; +pub const __WASI_SIGQUIT: u8 = 3; +pub const __WASI_SIGILL: u8 = 4; +pub const __WASI_SIGTRAP: u8 = 5; +pub const __WASI_SIGABRT: u8 = 6; +pub const __WASI_SIGBUS: u8 = 7; +pub const __WASI_SIGFPE: u8 = 8; +pub const __WASI_SIGKILL: u8 = 9; +pub const __WASI_SIGUSR1: u8 = 10; +pub const __WASI_SIGSEGV: u8 = 11; +pub const __WASI_SIGUSR2: u8 = 12; +pub const __WASI_SIGPIPE: u8 = 13; +pub const __WASI_SIGALRM: u8 = 14; +pub const __WASI_SIGTERM: u8 = 15; +pub const __WASI_SIGCHLD: u8 = 16; +pub const __WASI_SIGCONT: u8 = 17; +pub const __WASI_SIGSTOP: u8 = 18; +pub const __WASI_SIGTSTP: u8 = 19; +pub const __WASI_SIGTTIN: u8 = 20; +pub const __WASI_SIGTTOU: u8 = 21; +pub const __WASI_SIGURG: u8 = 22; +pub const __WASI_SIGXCPU: u8 = 23; +pub const __WASI_SIGXFSZ: u8 = 24; +pub const __WASI_SIGVTALRM: u8 = 25; +pub const __WASI_SIGPROF: u8 = 26; +pub const __WASI_SIGWINCH: u8 = 27; +pub const __WASI_SIGPOLL: u8 = 28; +pub const __WASI_SIGPWR: u8 = 29; +pub const __WASI_SIGSYS: u8 = 30; diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index ea29412b468..ba3570b308a 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -16,22 +16,48 @@ thiserror = "1" generational-arena = { version = "0.2" } tracing = "0.1" getrandom = "0.2" -wasmer-wasi-types = { path = "../wasi-types", version = "=3.0.0-rc.2" } -wasmer = { path = "../api", version = "=3.0.0-rc.2", default-features = false } -wasmer-vfs = { path = "../vfs", version = "=3.0.0-rc.2", default-features = false } -wasmer-vbus = { path = "../vbus", version = "=3.0.0-rc.2", default-features = false } -wasmer-vnet = { path = "../vnet", version = "=3.0.0-rc.2", default-features = false } -wasmer-wasi-local-networking = { path = "../wasi-local-networking", version = "=3.0.0-rc.2", default-features = false, optional = true } +wasmer-wasi-types = { path = "../wasi-types", version = "=3.0.0-beta" } +wasmer = { path = "../api", version = "=3.0.0-beta", default-features = false } +wasmer-types = { path = "../types", version = "=3.0.0-beta" } +wasmer-vfs = { path = "../vfs", version = "=3.0.0-beta", default-features = false, features = ["mem-fs"] } +wasmer-vbus = { path = "../vbus", version = "=3.0.0-beta", default-features = false } +wasmer-vnet = { path = "../vnet", version = "=3.0.0-beta", default-features = false } +wasmer-wasi-local-networking = { path = "../wasi-local-networking", version = "=3.0.0-beta", default-features = false, optional = true } typetag = { version = "0.1", optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } bincode = { version = "1.3", optional = true } chrono = { version = "^0.4", default-features = false, features = [ "wasmbind", "std", "clock" ], optional = true } derivative = { version = "^2" } bytes = "1" -webc = { version = "3.0.1", optional = true, default-features = false, features = ["std", "mmap"] } -serde_cbor = { version = "0.11.2", optional = true } -anyhow = { version = "1.0.66", optional = true } -wasmer-emscripten = { path = "../emscripten", version = "=3.0.0-rc.2", optional = true } +lazy_static = "1.4" +sha2 = { version = "0.10" } +waker-fn = { version = "1.1" } +cooked-waker = "^5" +rand = "0.8" +webc = { version = "0.1", path = "../../../pirita/crates/webc" } +# used by feature='os' +tokio = { version = "1", features = [ "sync", "macros" ], default_features = false, optional = true } +async-trait = { version = "^0.1", optional = true } +urlencoding = { version = "^2", optional = true } +webc-vfs = { version = "0.1", path = "../../../pirita/crates/webc-vfs", optional = true } +serde_derive = { version = "^1", optional = true } +serde_json = { version = "^1", optional = true } +serde_yaml = { version = "^0.8", optional = true } +shellexpand = { version = "^2", optional = true } +weezl = { version = "^0.1", optional = true } +hex = { version = "^0.4", optional = true } +term_size = { version = "0.3", optional = true } +linked_hash_set = { version = "0.1", optional = true } +futures = { version = "0.3", optional = true } +# used by feature='host-reqwest' +reqwest = { version = "0.11", features = ["json"], optional = true } +# used by feature='host-termios' +termios = { version = "0.3", optional = true } +# the various compilers +wasmer-compiler-cranelift = { version = "3.0.0-beta", path = "../compiler-cranelift", optional = true } +wasmer-compiler-llvm = { version = "3.0.0-beta", path = "../compiler-llvm", optional = true } +wasmer-compiler-singlepass = { version = "3.0.0-beta", path = "../compiler-singlepass", optional = true } +wasmer-compiler = { version = "3.0.0-beta", path = "../compiler", features = [ "translator" ], optional = true } [target.'cfg(unix)'.dependencies] libc = { version = "^0.2", default-features = false } @@ -46,25 +72,35 @@ wasm-bindgen = "0.2.74" wasm-bindgen-test = "0.3.0" tracing-wasm = "0.2" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +tracing-subscriber = { version = "^0.2" } + [features] default = ["sys-default"] wasix = [] -webc_runner = ["webc", "serde_cbor", "anyhow", "serde", "wasmer/compiler", "wasmer/cranelift"] -webc_runner_rt_emscripten = ["wasmer-emscripten"] -webc_runner_rt_wasi = [] - -sys = ["wasmer/sys", "wasix", "wasmer-wasi-types/sys"] -sys-default = ["wasmer/wat", "wasmer/compiler", "sys", "logging", "host-fs", "sys-poll", "host-vnet" ] +sys = ["wasmer/sys", "webc/mmap"] +sys-default = ["wasmer/wat", "wasmer/compiler", "sys", "os", "logging", "host-fs", "sys-poll", "sys-thread", "host-vnet", "host-threads", "host-reqwest" ] sys-poll = [] +sys-thread = ["tokio/rt", "tokio/rt-multi-thread"] + +compiler = [ "wasmer/compiler", "wasmer-compiler"] +compiler-cranelift = [ "wasmer-compiler-cranelift" ] +compiler-llvm = [ "wasmer-compiler-llvm" ] +compiler-singlepass = [ "wasmer-compiler-singlepass" ] js = ["wasmer/js", "mem-fs", "wasmer-vfs/no-time", "getrandom/js", "chrono", "wasmer-wasi-types/js"] js-default = ["js", "wasmer/js-default"] test-js = ["js", "wasmer/js-default", "wasmer/wat"] +os = [ "wasmer/wat", "wasmer/js-serializable-module", "tokio", "async-trait", "urlencoding", "webc-vfs", "serde", "serde_derive", "serde_json", "serde_yaml", "shellexpand", "weezl", "hex", "linked_hash_set", "futures" ] + host-vnet = [ "wasmer-wasi-local-networking" ] +host-threads = [] +host-reqwest = ["reqwest"] host-fs = ["wasmer-vfs/host-fs"] -mem-fs = ["wasmer-vfs/mem-fs"] +host-termios = ["termios", "term_size"] +mem-fs = [] logging = ["tracing/log"] disable-all-logging = [ diff --git a/lib/wasi/src/bin_factory/binary_package.rs b/lib/wasi/src/bin_factory/binary_package.rs new file mode 100644 index 00000000000..a03343ade5f --- /dev/null +++ b/lib/wasi/src/bin_factory/binary_package.rs @@ -0,0 +1,118 @@ +use std::{ + sync::{ + Arc, Mutex, RwLock + }, + any::Any, + borrow::Cow, + collections::HashMap +}; + +use derivative::*; +use wasmer_vfs::FileSystem; +use wasmer_wasi_types::__WASI_CLOCK_MONOTONIC; +use crate::{fs::TmpFileSystem, syscalls::platform_clock_time_get}; + +use super::hash_of_binary; + +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct BinaryPackageCommand { + pub name: String, + #[derivative(Debug = "ignore")] + pub atom: Cow<'static, [u8]>, + hash: Option, + pub ownership: Option>, +} + +impl BinaryPackageCommand { + pub fn new(name: String, atom: Cow<'static, [u8]>) -> Self { + Self { + name, + ownership: None, + hash: None, + atom + } + } + + pub unsafe fn new_with_ownership<'a, T>(name: String, atom: Cow<'a, [u8]>, ownership: Arc) -> Self + where T: 'static + { + let ownership: Arc = ownership; + let mut ret = Self::new(name, std::mem::transmute(atom)); + ret.ownership = Some(std::mem::transmute(ownership)); + ret + } + + pub fn hash(&mut self) -> &str { + if self.hash.is_none() { + self.hash = Some(hash_of_binary(self.atom.as_ref())); + } + let hash = self.hash.as_ref().unwrap(); + hash.as_str() + } +} + +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct BinaryPackage { + pub package_name: Cow<'static, str>, + pub when_cached: u128, + pub ownership: Option>, + #[derivative(Debug = "ignore")] + pub entry: Cow<'static, [u8]>, + pub hash: Arc>>, + pub wapm: Option, + pub base_dir: Option, + pub tmp_fs: TmpFileSystem, + pub webc_fs: Option>, + pub webc_top_level_dirs: Vec, + pub mappings: Vec, + pub envs: HashMap, + pub commands: Arc>>, + pub uses: Vec, + pub version: Cow<'static, str>, +} + +impl BinaryPackage { + pub fn new(package_name: &str, entry: Cow<'static, [u8]>) -> Self { + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + let (package_name, version) = match package_name.split_once("@") { + Some((a, b)) => (a.to_string(), b.to_string()), + None => (package_name.to_string(), "1.0.0".to_string()) + }; + Self { + package_name: package_name.into(), + when_cached: now, + ownership: None, + entry, + hash: Arc::new(Mutex::new(None)), + wapm: None, + base_dir: None, + tmp_fs: TmpFileSystem::new(), + webc_fs: None, + webc_top_level_dirs: Default::default(), + mappings: Vec::new(), + envs: HashMap::default(), + commands: Arc::new(RwLock::new(Vec::new())), + uses: Vec::new(), + version: version.into(), + } + } + + pub unsafe fn new_with_ownership<'a, T>(package_name: &str, entry: Cow<'a, [u8]>, ownership: Arc) -> Self + where T: 'static + { + let ownership: Arc = ownership; + let mut ret = Self::new(package_name, std::mem::transmute(entry)); + ret.ownership = Some(std::mem::transmute(ownership)); + ret + } + + pub fn hash(&self) -> String { + let mut hash = self.hash.lock().unwrap(); + if hash.is_none() { + hash.replace(hash_of_binary(self.entry.as_ref())); + } + hash.as_ref().unwrap().clone() + } +} diff --git a/lib/wasi/src/bin_factory/cached_modules.rs b/lib/wasi/src/bin_factory/cached_modules.rs new file mode 100644 index 00000000000..652b5d83639 --- /dev/null +++ b/lib/wasi/src/bin_factory/cached_modules.rs @@ -0,0 +1,261 @@ +use std::sync::RwLock; +use std::{ + collections::HashMap, + cell::RefCell, + ops::DerefMut, + path::PathBuf, +}; +use derivative::*; + +use bytes::Bytes; +use wasmer::{Module, AsStoreRef}; +#[cfg(feature = "sys")] +use wasmer::Engine; +use wasmer_wasi_types::__WASI_CLOCK_MONOTONIC; + +use crate::{WasiRuntimeImplementation, VirtualTaskManager}; +use crate::syscalls::platform_clock_time_get; + +use super::BinaryPackage; + +pub const DEFAULT_COMPILED_PATH: &'static str = "~/.wasmer/compiled"; +pub const DEFAULT_WEBC_PATH: &'static str = "~/.wasmer/webc"; +pub const DEFAULT_CACHE_TIME: u128 = std::time::Duration::from_secs(30).as_nanos(); + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct CachedCompiledModules { + #[cfg(feature = "sys")] + pub(crate) cached_modules: RwLock>, + pub(crate) cache_compile_dir: String, + pub(crate) cache_webc: RwLock>, + pub(crate) cache_webc_dir: String, + #[cfg(feature = "sys")] + #[derivative(Debug = "ignore")] + pub(crate) engine: Engine, +} + +impl Default +for CachedCompiledModules { + fn default() -> Self { + CachedCompiledModules::new(None, None) + } +} + +thread_local! { + static THREAD_LOCAL_CACHED_MODULES: std::cell::RefCell> + = RefCell::new(HashMap::new()); +} + +impl CachedCompiledModules +{ + #[cfg(feature = "sys")] + fn new_engine() -> wasmer::Engine + { + // Build the features list + let mut features = wasmer::Features::new(); + features.threads(true); + features.memory64(true); + features.bulk_memory(true); + #[cfg(feature = "singlepass")] + features.multi_value(false); + + // Choose the right compiler + #[cfg(feature = "compiler-cranelift")] + { + let compiler = wasmer_compiler_cranelift::Cranelift::default(); + return wasmer_compiler::EngineBuilder::new(compiler) + .set_features(Some(features)) + .engine(); + } + #[cfg(all(not(feature = "compiler-cranelift"), feature = "compiler-llvm"))] + { + let compiler = wasmer_compiler_llvm::LLVM::default(); + return wasmer_compiler::EngineBuilder::new(compiler) + .set_features(Some(features)) + .engine(); + } + #[cfg(all(not(feature = "compiler-cranelift"), not(feature = "compiler-singlepass"), feature = "compiler-llvm"))] + { + let compiler = wasmer_compiler_singlepass::Singlepass::default(); + return wasmer_compiler::EngineBuilder::new(compiler) + .set_features(Some(features)) + .engine(); + } + #[cfg(all(not(feature = "compiler-cranelift"), not(feature = "compiler-singlepass"), not(feature = "compiler-llvm")))] + panic!("wasmer not built with a compiler") + } + + pub fn new(cache_compile_dir: Option, cache_webc_dir: Option) -> CachedCompiledModules { + let cache_compile_dir = shellexpand::tilde(cache_compile_dir + .as_ref() + .map(|a| a.as_str()) + .unwrap_or_else(|| DEFAULT_COMPILED_PATH)) + .to_string(); + let _ = std::fs::create_dir_all(PathBuf::from(cache_compile_dir.clone())); + + let cache_webc_dir = shellexpand::tilde(cache_webc_dir + .as_ref() + .map(|a| a.as_str()) + .unwrap_or_else(|| DEFAULT_WEBC_PATH)) + .to_string(); + let _ = std::fs::create_dir_all(PathBuf::from(cache_webc_dir.clone())); + + #[cfg(feature = "sys")] + let engine = Self::new_engine(); + + CachedCompiledModules { + #[cfg(feature = "sys")] + cached_modules: RwLock::new(HashMap::default()), + cache_compile_dir, + cache_webc: RwLock::new(HashMap::default()), + cache_webc_dir, + #[cfg(feature = "sys")] + engine + } + } + + #[cfg(feature = "sys")] + pub fn new_store(&self) -> wasmer::Store + { + let engine = self.engine.clone(); + wasmer::Store::new(engine) + } + + #[cfg(not(feature = "sys"))] + pub fn new_store(&self) -> wasmer::Store + { + wasmer::Store::default() + } + + pub fn get_webc(&self, webc: &str, runtime: &dyn WasiRuntimeImplementation, tasks: &dyn VirtualTaskManager) -> Option { + let name = webc.to_string(); + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + + // Fast path + { + let cache = self.cache_webc.read().unwrap(); + if let Some(data) = cache.get(&name) { + let delta = now - data.when_cached; + if delta <= DEFAULT_CACHE_TIME { + return Some(data.clone()); + } + } + } + + // Slow path + let mut cache = self.cache_webc.write().unwrap(); + + // Check the cache + if let Some(data) = cache.get(&name) { + let delta = now - data.when_cached; + if delta <= DEFAULT_CACHE_TIME { + return Some(data.clone()); + } + } + + // Now try for the WebC + let wapm_name = name.split_once(":").map(|a| a.0).unwrap_or_else(|| name.as_str()); + let cache_webc_dir = self.cache_webc_dir.as_str(); + if let Some(data) = crate::wapm::fetch_webc(cache_webc_dir, wapm_name, runtime, tasks) + { + // If the package is the same then don't replace it + // as we don't want to duplicate the memory usage + if let Some(existing) = cache.get_mut(&name) { + if existing.hash() == data.hash() && existing.version == data.version { + existing.when_cached = now; + return Some(data.clone()); + } + } + cache.insert(name, data.clone()); + return Some(data); + } + + // Not found + None + } + + pub fn get_compiled_module(&self, store: &impl AsStoreRef, data_hash: &str, compiler: &str) -> Option { + let key = format!("{}-{}", data_hash, compiler); + + // fastest path + { + let module = THREAD_LOCAL_CACHED_MODULES.with(|cache| { + let cache = cache.borrow(); + cache.get(&key).map(|m| m.clone()) + }); + if let Some(module) = module { + return Some(module); + } + } + + // fast path + #[cfg(feature = "sys")] + { + let cache = self.cached_modules.read().unwrap(); + if let Some(module) = cache.get(&key) { + THREAD_LOCAL_CACHED_MODULES.with(|cache| { + let mut cache = cache.borrow_mut(); + cache.insert(key.clone(), module.clone()); + }); + return Some(module.clone()); + } + } + + // slow path + let path = std::path::Path::new(self.cache_compile_dir.as_str()).join(format!("{}.bin", key).as_str()); + if let Ok(data) = std::fs::read(path) { + let mut decoder = weezl::decode::Decoder::new(weezl::BitOrder::Msb, 8); + if let Ok(data) = decoder.decode(&data[..]) { + let module_bytes = Bytes::from(data); + + // Load the module + let module = unsafe { Module::deserialize(store, &module_bytes[..]) + .unwrap() + }; + + #[cfg(feature = "sys")] + { + let mut cache = self.cached_modules.write().unwrap(); + cache.insert(key.clone(), module.clone()); + } + THREAD_LOCAL_CACHED_MODULES.with(|cache| { + let mut cache = cache.borrow_mut(); + cache.insert(key.clone(), module.clone()); + }); + return Some(module); + } + } + + // Not found + None + } + + pub fn set_compiled_module(&self, data_hash: &str, compiler: &str, module: &Module) { + let key = format!("{}-{}", data_hash, compiler); + + // Add the module to the local thread cache + THREAD_LOCAL_CACHED_MODULES.with(|cache| { + let mut cache = cache.borrow_mut(); + let cache = cache.deref_mut(); + cache.insert(key.clone(), module.clone()); + }); + + // Serialize the compiled module into bytes and insert it into the cache + #[cfg(feature = "sys")] + { + let mut cache = self.cached_modules.write().unwrap(); + cache.insert(key.clone(), module.clone()); + } + + // We should also attempt to store it in the cache directory + let compiled_bytes = module.serialize().unwrap(); + + let path = std::path::Path::new(self.cache_compile_dir.as_str()).join(format!("{}.bin", key).as_str()); + let _ = std::fs::create_dir_all(path.parent().unwrap().clone()); + let mut encoder = weezl::encode::Encoder::new(weezl::BitOrder::Msb, 8); + if let Ok(compiled_bytes) = encoder.encode(&compiled_bytes[..]) { + let _ = std::fs::write(path, &compiled_bytes[..]); + } + } +} diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs new file mode 100644 index 00000000000..d14cb4c2323 --- /dev/null +++ b/lib/wasi/src/bin_factory/exec.rs @@ -0,0 +1,308 @@ +use wasmer::{Store, Module, Memory, Instance, FunctionEnvMut}; +use wasmer_vbus::{ + VirtualBusSpawner, + VirtualBusError, + SpawnOptionsConfig, + BusSpawnedProcess, VirtualBusProcess, VirtualBusScope, VirtualBusInvokable +}; +use wasmer_wasi_types::__WASI_ENOEXEC; +use std::{ + pin::Pin, + task::{ + Context, + Poll + }, sync::{Mutex, Arc}, ops::DerefMut +}; +use tokio::sync::mpsc; +use tracing::*; + +use crate::{WasiEnv, WasiFunctionEnv, import_object_for_all_wasi_versions, WasiError, WasiRuntimeImplementation, SpawnedMemory}; +use super::{BinFactory, BinaryPackage, CachedCompiledModules}; +use crate::runtime::SpawnType; + +pub fn spawn_exec( + binary: BinaryPackage, + name: &str, + store: Store, + config: SpawnOptionsConfig, + runtime: &Arc, + compiled_modules: &CachedCompiledModules +) -> wasmer_vbus::Result +{ + // Load the module + #[cfg(feature = "sys")] + let compiler = store.engine().name(); + #[cfg(not(feature = "sys"))] + let compiler = "generic"; + let module = compiled_modules.get_compiled_module( + &store, + binary.hash().as_str(), + compiler + ); + let module = match module { + Some(a) => a, + None => { + let module = Module::new(&store, &binary.entry[..]) + .map_err(|err| { + error!("failed to compile module [{}, len={}] - {}", name, binary.entry.len(), err); + VirtualBusError::CompileError + })?; + compiled_modules.set_compiled_module( + binary.hash().as_str(), + compiler, + &module + ); + module + } + }; + + // If the file system has not already been union'ed then do so + config.env().state.fs.conditional_union(&binary); + + // Now run the module + spawn_exec_module(module, store, config, runtime) +} + +pub fn spawn_exec_module( + module: Module, + store: Store, + config: SpawnOptionsConfig, + runtime: &Arc +) -> wasmer_vbus::Result +{ + // Create a new task manager + let tasks = runtime.new_task_manager(); + + // Create the signaler + let pid = config.env().pid(); + let signaler = Box::new(config.env().process.clone()); + + // Now run the binary + let (exit_code_tx, exit_code_rx) = mpsc::unbounded_channel(); + { + // Determine if shared memory needs to be created and imported + let shared_memory = module + .imports() + .memories() + .next() + .map(|a| *a.ty()); + + // Determine if we are going to create memory and import it or just rely on self creation of memory + let memory_spawn = match shared_memory { + Some(ty) => { + #[cfg(feature = "sys")] + let style = store + .tunables() + .memory_style(&ty); + SpawnType::CreateWithType(SpawnedMemory { + ty, + #[cfg(feature = "sys")] + style + }) + }, + None => SpawnType::Create, + }; + + // Create a thread that will run this process + let runtime = runtime.clone(); + let tasks_outer = tasks.clone(); + tasks_outer.task_wasm(Box::new(move |mut store, module, memory| + { + // Create the WasiFunctionEnv + let mut wasi_env = config.env().clone(); + wasi_env.runtime = runtime; + wasi_env.tasks = tasks; + let mut wasi_env = WasiFunctionEnv::new(&mut store, wasi_env); + + // Let's instantiate the module with the imports. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &wasi_env.env); + if let Some(memory) = memory { + import_object.define("env", "memory", Memory::new_from_existing(&mut store, memory)); + } + let instance = match Instance::new(&mut store, &module, &import_object) { + Ok(a) => a, + Err(err) => { + error!("wasi[{}]::wasm instantiate error ({})", pid, err); + return; + } + }; + + // Initialize the WASI environment + if let Err(err) = wasi_env.initialize(&mut store, &instance) { + error!("wasi[{}]::wasi initialize error ({})", pid, err); + return; + } + + // If this module exports an _initialize function, run that first. + if let Ok(initialize) = instance.exports.get_function("_initialize") { + if let Err(e) = initialize.call(&mut store, &[]) { + let code = match e.downcast::() { + Ok(WasiError::Exit(code)) => code, + Ok(WasiError::UnknownWasiVersion) => { + debug!("wasi[{}]::exec-failed: unknown wasi version", pid); + __WASI_ENOEXEC as u32 + } + Err(err) => { + debug!("wasi[{}]::exec-failed: runtime error - {}", pid, err); + 9999u32 + }, + }; + let _ = exit_code_tx.send(code); + return; + } + } + + // Let's call the `_start` function, which is our `main` function in Rust. + let start = instance + .exports + .get_function("_start") + .ok(); + + // If there is a start function + debug!("wasi[{}]::called main()", pid); + let ret = if let Some(start) = start { + match start.call(&mut store, &[]) { + Ok(_) => 0, + Err(e) => { + match e.downcast::() { + Ok(WasiError::Exit(code)) => code, + Ok(WasiError::UnknownWasiVersion) => { + debug!("wasi[{}]::exec-failed: unknown wasi version", pid); + __WASI_ENOEXEC as u32 + } + Err(err) => { + debug!("wasi[{}]::exec-failed: runtime error - {}", pid, err); + 9999u32 + }, + } + }, + } + } else { + debug!("wasi[{}]::exec-failed: missing _start function", pid); + __WASI_ENOEXEC as u32 + }; + debug!("wasi[{}]::main() has exited with {}", pid, ret); + + // Send the result + let _ = exit_code_tx.send(ret); + drop(exit_code_tx); + } + ), store, module, memory_spawn) + .map_err(|err| { + error!("wasi[{}]::failed to launch module - {}", pid, err); + VirtualBusError::UnknownError + })? + }; + + let inst = Box::new( + SpawnedProcess { + exit_code: Mutex::new(None), + exit_code_rx: Mutex::new(exit_code_rx), + } + ); + Ok( + BusSpawnedProcess { + inst, + stdin: None, + stdout: None, + stderr: None, + signaler: Some(signaler), + } + ) +} + +impl VirtualBusSpawner +for BinFactory +{ + fn spawn<'a>( + &self, + parent_ctx: Option<&FunctionEnvMut<'a, WasiEnv>>, + name: &str, + store: Store, + config: SpawnOptionsConfig, + _fallback: &dyn VirtualBusSpawner + ) -> wasmer_vbus::Result { + if config.remote_instance().is_some() { + return Err(VirtualBusError::Unsupported); + } + + // We check for built in commands + if let Some(parent_ctx) = parent_ctx { + if self.builtins.exists(name) { + return self.builtins.exec(parent_ctx, name, store, config); + } + } else { + if self.builtins.exists(name) { + tracing::warn!("builtin command without a parent ctx - {}", name); + } + } + + // Find the binary (or die trying) and make the spawn type + let binary = self + .get_binary(name) + .ok_or(VirtualBusError::NotFound)?; + spawn_exec(binary, name, store, config, &self.runtime, &self.cache) + } +} + +#[derive(Debug)] +pub(crate) struct SpawnedProcess { + pub exit_code: Mutex>, + pub exit_code_rx: Mutex>, +} + +impl VirtualBusProcess +for SpawnedProcess { + fn exit_code(&self) -> Option + { + let mut exit_code = self.exit_code.lock().unwrap(); + if let Some(exit_code) = exit_code.as_ref() { + return Some(exit_code.clone()); + } + let mut rx = self.exit_code_rx.lock().unwrap(); + match rx.try_recv() { + Ok(code) => { + exit_code.replace(code); + Some(code) + }, + Err(mpsc::error::TryRecvError::Disconnected) => { + let code = 9999; + exit_code.replace(code); + Some(code) + } + _ => None + } + } + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + { + let exit_code = self.exit_code.lock().unwrap(); + if exit_code.is_some() { + return Poll::Ready(()); + } + } + let mut rx = self.exit_code_rx.lock().unwrap(); + let mut rx = Pin::new(rx.deref_mut()); + match rx.poll_recv(cx) { + Poll::Ready(code) => { + let code = code.unwrap_or(9999); + { + let mut exit_code = self.exit_code.lock().unwrap(); + exit_code.replace(code); + } + Poll::Ready(()) + }, + Poll::Pending => Poll::Pending + } + } +} + +impl VirtualBusScope +for SpawnedProcess { + fn poll_finished(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + VirtualBusProcess::poll_ready(self, cx) + } +} + +impl VirtualBusInvokable +for SpawnedProcess { } diff --git a/lib/wasi/src/bin_factory/mod.rs b/lib/wasi/src/bin_factory/mod.rs new file mode 100644 index 00000000000..edc8f76b31e --- /dev/null +++ b/lib/wasi/src/bin_factory/mod.rs @@ -0,0 +1,111 @@ +use std::{ + sync::{ + Arc, RwLock, + }, + ops::{ + Deref + }, collections::HashMap, +}; +use derivative::Derivative; + +mod binary_package; +mod cached_modules; +mod exec; + +pub use binary_package::*; +pub use cached_modules::*; +pub use exec::spawn_exec; +pub use exec::spawn_exec_module; +pub(crate) use exec::SpawnedProcess; + +use sha2::*; + +use crate::{ + WasiState, + WasiRuntimeImplementation, + builtins::BuiltIns +}; + +#[derive(Derivative, Clone)] +pub struct BinFactory { + pub(crate) state: Arc, + pub(crate) builtins: BuiltIns, + runtime: Arc, + pub(crate) cache: Arc, + pub(crate) local: Arc>>>, +} + +impl BinFactory { + pub fn new( + state: Arc, + compiled_modules: Arc, + runtime: Arc, + ) -> BinFactory { + BinFactory { + state, + builtins: BuiltIns::new(runtime.clone(), compiled_modules.clone()), + runtime, + cache: compiled_modules, + local: Arc::new(RwLock::new(HashMap::new())) + } + } + + pub fn runtime(&self) -> &dyn WasiRuntimeImplementation { + self.runtime.deref() + } + + pub fn set_binary(&self, name: &str, binary: BinaryPackage) { + let mut cache = self.local.write().unwrap(); + cache.insert(name.to_string(), Some(binary)); + } + + pub fn get_binary(&self, name: &str) -> Option { + let name = name.to_string(); + + // Fast path + { + let cache = self.local.read().unwrap(); + if let Some(data) = cache.get(&name) { + return data.clone(); + } + } + + // Slow path + let mut cache = self.local.write().unwrap(); + + // Check the cache + if let Some(data) = cache.get(&name) { + return data.clone(); + } + + // Check the filesystem for the file + if name.starts_with("/") { + if let Ok(mut file) = self.state + .fs_new_open_options() + .read(true) + .open(name.clone()) + { + // Read the file + let mut data = Vec::with_capacity(file.size() as usize); + if let Ok(_) = file.read_to_end(&mut data) + { + let package_name = name.split("/").last().unwrap_or_else(|| name.as_str()); + let data = BinaryPackage::new(package_name, data.into()); + cache.insert(name, Some(data.clone())); + return Some(data); + } + } + } + + // NAK + cache.insert(name, None); + return None; + } +} + +pub fn hash_of_binary(data: impl AsRef<[u8]>) -> String { + let mut hasher = Sha256::default(); + hasher.update(data.as_ref()); + let hash = hasher.finalize(); + hex::encode(&hash[..]) +} diff --git a/lib/wasi/src/builtins/cmd_wasmer.rs b/lib/wasi/src/builtins/cmd_wasmer.rs new file mode 100644 index 00000000000..52695287783 --- /dev/null +++ b/lib/wasi/src/builtins/cmd_wasmer.rs @@ -0,0 +1,123 @@ +use wasmer::{FunctionEnvMut, Store}; +use wasmer_vbus::{ + SpawnOptionsConfig, + BusSpawnedProcess +}; +use wasmer_wasi_types::__WASI_ENOENT; +use std::{ + ops::Deref, + sync::{ + Arc, + }, +}; + +use crate::{ + WasiEnv, + syscalls::stderr_write, + WasiRuntimeImplementation, + bin_factory::{ + BinaryPackage, + CachedCompiledModules, + spawn_exec + }, VirtualTaskManager, +}; + +const HELP: &'static str = r#"USAGE: + wasmer + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + run Run a WebAssembly file. Formats accepted: wasm, wat +"#; + +const HELP_RUN: &'static str = r#"USAGE: + wasmer run [ARGS]... + +ARGS: + File to run + ... Application arguments +"#; + +use super::BuiltInCommand; + +#[derive(Debug, Clone)] +pub struct CmdWasmer { + runtime: Arc, + cache: Arc, +} + +impl CmdWasmer { + pub fn new(runtime: Arc, compiled_modules: Arc) -> Self { + Self { + runtime, + cache: compiled_modules + } + } +} + +impl CmdWasmer{ + fn run<'a>(&self, parent_ctx: &FunctionEnvMut<'a, WasiEnv>, name: &str, store: Store, mut config: SpawnOptionsConfig, what: Option, mut args: Vec) -> wasmer_vbus::Result { + if let Some(what) = what { + // Set the arguments of the environment by replacing the state + let mut state = config.env().state.fork(); + args.insert(0, what.clone()); + state.args = args; + config.env_mut().state = Arc::new(state); + + // Get the binary + let tasks = parent_ctx.data().tasks(); + if let Some(binary) = self.get(what.clone(), tasks) + { + // Now run the module + spawn_exec(binary, name, store, config, &self.runtime, &self.cache) + } else { + let _ = stderr_write(parent_ctx, format!("package not found - {}\r\n", what).as_bytes()); + Ok(BusSpawnedProcess::exited_process(__WASI_ENOENT as u32)) + } + } else { + let _ = stderr_write(parent_ctx, HELP_RUN.as_bytes()); + Ok(BusSpawnedProcess::exited_process(0)) + } + } + + pub fn get(&self, name: String, tasks: &dyn VirtualTaskManager) -> Option + { + self.cache.get_webc( + name.as_str(), + self.runtime.deref(), + tasks, + ) + } +} + +impl BuiltInCommand +for CmdWasmer { + fn exec<'a>(&self, parent_ctx: &FunctionEnvMut<'a, WasiEnv>, name: &str, store: Store, config: SpawnOptionsConfig) -> wasmer_vbus::Result + { + // Read the command we want to run + let mut args = config.env().state.args.iter().map(|a| a.as_str()); + let _alias = args.next(); + let cmd = args.next(); + + // Check the command + match cmd { + Some("run") => { + let what = args.next().map(|a| a.to_string()); + let args = args.map(|a| a.to_string()).collect(); + self.run(parent_ctx, name, store, config, what, args) + }, + Some("--help") | + None => { + let _ = stderr_write(parent_ctx, HELP.as_bytes()); + Ok(BusSpawnedProcess::exited_process(0)) + }, + Some(what) => { + let what = Some(what.to_string()); + let args = args.map(|a| a.to_string()).collect(); + self.run(parent_ctx, name, store, config, what, args) + } + } + } +} \ No newline at end of file diff --git a/lib/wasi/src/builtins/mod.rs b/lib/wasi/src/builtins/mod.rs new file mode 100644 index 00000000000..2265e15994a --- /dev/null +++ b/lib/wasi/src/builtins/mod.rs @@ -0,0 +1,58 @@ +use std::{ + collections::HashMap, + sync::Arc +}; + +use wasmer::{FunctionEnvMut, Store}; +use wasmer_vbus::{SpawnOptionsConfig, BusSpawnedProcess}; +use wasmer_wasi_types::__WASI_ENOENT; + +use crate::{WasiEnv, syscalls::stderr_write, bin_factory::CachedCompiledModules, WasiRuntimeImplementation}; +mod cmd_wasmer; + +pub trait BuiltInCommand +where Self: std::fmt::Debug { + fn exec<'a>(&self, parent_ctx: &FunctionEnvMut<'a, WasiEnv>, name: &str, store: Store, config: SpawnOptionsConfig) -> wasmer_vbus::Result; +} + +#[derive(Debug, Clone)] +pub struct BuiltIns { + commands: HashMap>, + pub(crate) cmd_wasmer: cmd_wasmer::CmdWasmer, +} + +impl BuiltIns { + pub(crate) fn new(runtime: Arc, compiled_modules: Arc) -> Self { + let cmd_wasmer = cmd_wasmer::CmdWasmer::new( + runtime.clone(), + compiled_modules.clone()); + let mut commands: HashMap> + = HashMap::new(); + commands.insert("/bin/wasmer".to_string(), Arc::new( + cmd_wasmer.clone()) + ); + + Self { + commands, + cmd_wasmer + } + } +} + +impl BuiltIns +{ + pub fn exists(&self, name: &str) -> bool { + let name = name.to_string(); + self.commands.contains_key(&name) + } + + pub fn exec<'a>(&self, parent_ctx: &FunctionEnvMut<'a, WasiEnv>, name: &str, store: Store, config: SpawnOptionsConfig) -> wasmer_vbus::Result { + let name = name.to_string(); + if let Some(cmd) = self.commands.get(&name) { + cmd.exec(parent_ctx, name.as_str(), store, config) + } else { + let _ = stderr_write(parent_ctx, format!("wasm command unknown - {}\r\n", name).as_bytes()); + Ok(BusSpawnedProcess::exited_process(__WASI_ENOENT as u32)) + } + } +} diff --git a/lib/wasi/src/fs/arc_file.rs b/lib/wasi/src/fs/arc_file.rs new file mode 100644 index 00000000000..fe571c95f57 --- /dev/null +++ b/lib/wasi/src/fs/arc_file.rs @@ -0,0 +1,106 @@ +use std::{ + io::{ + self, + * + }, + sync::{ + Arc, + Mutex + } +}; +use derivative::Derivative; +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::{VirtualFile, ClonableVirtualFile}; + +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct ArcFile { + #[derivative(Debug = "ignore")] + inner: Arc>> +} + +impl ArcFile +{ + pub fn new(inner: Box) -> Self { + Self { + inner: Arc::new(Mutex::new(inner)) + } + } +} + +impl Seek for ArcFile { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let mut inner = self.inner.lock().unwrap(); + inner.seek(pos) + } +} +impl Write for ArcFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut inner = self.inner.lock().unwrap(); + inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.flush() + } +} + +impl Read for ArcFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut inner = self.inner.lock().unwrap(); + inner.read(buf) + } +} + +impl VirtualFile for ArcFile { + fn last_accessed(&self) -> u64 { + let inner = self.inner.lock().unwrap(); + inner.last_accessed() + } + fn last_modified(&self) -> u64 { + let inner = self.inner.lock().unwrap(); + inner.last_modified() + } + fn created_time(&self) -> u64 { + let inner = self.inner.lock().unwrap(); + inner.created_time() + } + fn size(&self) -> u64 { + let inner = self.inner.lock().unwrap(); + inner.size() + } + fn set_len(&mut self, new_size: u64) -> wasmer_vfs::Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.set_len(new_size) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.unlink() + } + fn bytes_available(&self) -> wasmer_vfs::Result { + let inner = self.inner.lock().unwrap(); + inner.bytes_available() + } + fn bytes_available_read(&self) -> wasmer_vfs::Result> { + let inner = self.inner.lock().unwrap(); + inner.bytes_available_read() + } + fn bytes_available_write(&self) -> wasmer_vfs::Result> { + let inner = self.inner.lock().unwrap(); + inner.bytes_available_write() + } + fn get_fd(&self) -> Option { + let inner = self.inner.lock().unwrap(); + inner.get_fd() + } + fn is_open(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.is_open() + } + fn get_special_fd(&self) -> Option { + let inner = self.inner.lock().unwrap(); + inner.get_special_fd() + } +} + +impl ClonableVirtualFile for ArcFile {} diff --git a/lib/wasi/src/fs/arc_fs.rs b/lib/wasi/src/fs/arc_fs.rs new file mode 100644 index 00000000000..42e6b53fe61 --- /dev/null +++ b/lib/wasi/src/fs/arc_fs.rs @@ -0,0 +1,53 @@ +use std::path::Path; +use std::sync::Arc; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; + +use wasmer_vfs::*; + +#[derive(Debug)] +pub struct ArcFileSystem { + fs: Arc, +} + +impl ArcFileSystem { + pub fn new(inner: Arc) -> Self { + Self { + fs: inner, + } + } +} + +impl FileSystem for ArcFileSystem { + fn read_dir(&self, path: &Path) -> Result { + self.fs.read_dir(path) + } + + fn create_dir(&self, path: &Path) -> Result<()> { + self.fs.create_dir(path) + } + + fn remove_dir(&self, path: &Path) -> Result<()> { + self.fs.remove_dir(path) + } + + fn rename(&self, from: &Path, to: &Path) -> Result<()> { + self.fs.rename(from, to) + } + + fn metadata(&self, path: &Path) -> Result { + self.fs.metadata(path) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + self.fs.symlink_metadata(path) + } + + fn remove_file(&self, path: &Path) -> Result<()> { + self.fs.remove_file(path) + } + + fn new_open_options(&self) -> OpenOptions { + self.fs.new_open_options() + } +} diff --git a/lib/wasi/src/fs/builder.rs b/lib/wasi/src/fs/builder.rs new file mode 100644 index 00000000000..a35739b3973 --- /dev/null +++ b/lib/wasi/src/fs/builder.rs @@ -0,0 +1,98 @@ +use std::path::{Path, PathBuf}; +use tracing::*; +use wasmer_vfs::{FileSystem, VirtualFile}; +use wasmer_wasi_types::{__WASI_STDIN_FILENO, __WASI_STDOUT_FILENO, __WASI_STDERR_FILENO}; + +use super::{TmpFileSystem, ZeroFile}; +use super::{ + NullFile, + SpecialFile +}; + +pub struct RootFileSystemBuilder +{ + default_root_dirs: bool, + default_dev_files: bool, + add_wasmer_command: bool, + stdin: Option>, + stdout: Option>, + stderr: Option>, + tty: Option>, +} + +impl RootFileSystemBuilder +{ + pub fn new() -> Self { + Self { + default_root_dirs: true, + default_dev_files: true, + add_wasmer_command: true, + stdin: None, + stdout: None, + stderr: None, + tty: None, + } + } + + pub fn with_stdin(mut self, file: Box) -> Self { + self.stdin.replace(file); + self + } + + pub fn with_stdout(mut self, file: Box) -> Self { + self.stdout.replace(file); + self + } + + pub fn with_stderr(mut self, file: Box) -> Self { + self.stderr.replace(file); + self + } + + pub fn with_tty(mut self, file: Box) -> Self { + self.tty.replace(file); + self + } + + pub fn default_root_dirs(mut self, val: bool) -> Self { + self.default_root_dirs = val; + self + } + + pub fn build(self) -> TmpFileSystem { + let tmp = TmpFileSystem::new(); + if self.default_root_dirs { + for root_dir in vec![ + "/.app", + "/.private", + "/bin", + "/dev", + "/etc", + "/tmp" + ] { + if let Err(err) = tmp.create_dir(&Path::new(root_dir)) { + debug!("failed to create dir [{}] - {}", root_dir, err); + } + } + } + if self.add_wasmer_command { + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/bin/wasmer"), Box::new(NullFile::default())); + } + if self.default_dev_files { + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/null"), Box::new(NullFile::default())); + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/zero"), Box::new(ZeroFile::default())); + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/stdin"), self.stdin.unwrap_or_else(|| Box::new(SpecialFile::new(__WASI_STDIN_FILENO)))); + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/stdout"), self.stdout.unwrap_or_else(|| Box::new(SpecialFile::new(__WASI_STDOUT_FILENO)))); + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/stderr"), self.stderr.unwrap_or_else(|| Box::new(SpecialFile::new(__WASI_STDERR_FILENO)))); + let _ = tmp.new_open_options_ext() + .insert_custom_file(PathBuf::from("/dev/tty"), self.tty.unwrap_or_else(|| Box::new(NullFile::default()))); + } + tmp + } +} \ No newline at end of file diff --git a/lib/wasi/src/fs/delegate_file.rs b/lib/wasi/src/fs/delegate_file.rs new file mode 100644 index 00000000000..4be41f9dc6f --- /dev/null +++ b/lib/wasi/src/fs/delegate_file.rs @@ -0,0 +1,157 @@ +use std::{io::{self, *}, sync::{Arc, RwLock}}; +use derivative::Derivative; +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::VirtualFile; + +#[derive(Default)] +pub struct DelegateFileInner { + seek: Option io::Result + Send + Sync>>, + write: Option io::Result + Send + Sync>>, + flush: Option io::Result<()> + Send + Sync>>, + read: Option io::Result + Send + Sync>>, + size: Option u64 + Send + Sync>>, + set_len: Option wasmer_vfs::Result<()> + Send + Sync>>, + unlink: Option wasmer_vfs::Result<()> + Send + Sync>>, + bytes_available: Option wasmer_vfs::Result + Send + Sync>>, +} + +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct DelegateFile { + #[derivative(Debug = "ignore")] + inner: Arc>, +} + +impl DelegateFile +{ + pub fn with_seek(&self, func: impl Fn(SeekFrom) -> io::Result + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.seek.replace(Box::new(func)); + self + } + + pub fn with_write(&self, func: impl Fn(&[u8]) -> io::Result + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.write.replace(Box::new(func)); + self + } + + pub fn with_flush(&self, func: impl Fn() -> io::Result<()> + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.flush.replace(Box::new(func)); + self + } + + pub fn with_read(&self, func: impl Fn(&mut [u8]) -> io::Result + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.read.replace(Box::new(func)); + self + } + + pub fn with_size(&self, func: impl Fn() -> u64 + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.size.replace(Box::new(func)); + self + } + + pub fn with_set_len(&self, func: impl Fn(u64) -> wasmer_vfs::Result<()> + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.set_len.replace(Box::new(func)); + self + } + + pub fn with_unlink(&self, func: impl Fn() -> wasmer_vfs::Result<()> + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.unlink.replace(Box::new(func)); + self + } + + pub fn with_bytes_available(&self, func: impl Fn() -> wasmer_vfs::Result + Send + Sync + 'static) -> &Self { + let mut inner = self.inner.write().unwrap(); + inner.bytes_available.replace(Box::new(func)); + self + } +} + +impl Default +for DelegateFile +{ + fn default() -> Self { + Self { + inner: Arc::new(RwLock::new( + DelegateFileInner::default() + )) + } + } +} + +impl Seek for DelegateFile { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let inner = self.inner.read().unwrap(); + inner.seek.as_ref() + .map(|seek| seek(pos)) + .unwrap_or(Ok(0)) + } +} +impl Write for DelegateFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + let inner = self.inner.read().unwrap(); + inner.write.as_ref() + .map(|write| write(buf)) + .unwrap_or(Ok(buf.len())) + } + fn flush(&mut self) -> io::Result<()> { + let inner = self.inner.read().unwrap(); + inner.flush.as_ref() + .map(|flush| flush()) + .unwrap_or(Ok(())) + } +} + +impl Read for DelegateFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let inner = self.inner.read().unwrap(); + inner.read.as_ref() + .map(|read| read(buf)) + .unwrap_or(Ok(0)) + } +} + +impl VirtualFile for DelegateFile { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + let inner = self.inner.read().unwrap(); + inner.size.as_ref() + .map(|size| size()) + .unwrap_or(0) + } + fn set_len(&mut self, new_size: u64) -> wasmer_vfs::Result<()> { + let inner = self.inner.read().unwrap(); + inner.set_len.as_ref() + .map(|set_len| set_len(new_size)) + .unwrap_or(Ok(())) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + let inner = self.inner.read().unwrap(); + inner.unlink.as_ref() + .map(|unlink| unlink()) + .unwrap_or(Ok(())) + } + fn bytes_available(&self) -> wasmer_vfs::Result { + let inner = self.inner.read().unwrap(); + inner.bytes_available.as_ref() + .map(|bytes_available| bytes_available()) + .unwrap_or(Ok(0)) + } + fn get_fd(&self) -> Option { + None + } +} \ No newline at end of file diff --git a/lib/wasi/src/fs/empty_fs.rs b/lib/wasi/src/fs/empty_fs.rs new file mode 100644 index 00000000000..b0ec57d57de --- /dev/null +++ b/lib/wasi/src/fs/empty_fs.rs @@ -0,0 +1,57 @@ +use std::path::Path; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; + +use wasmer_vfs::*; + +#[derive(Debug, Default)] +pub struct EmptyFileSystem { +} + +#[allow(unused_variables)] +impl FileSystem for EmptyFileSystem { + fn read_dir(&self, path: &Path) -> Result { + Err(FsError::EntryNotFound) + } + + fn create_dir(&self, path: &Path) -> Result<()> { + Err(FsError::EntryNotFound) + } + + fn remove_dir(&self, path: &Path) -> Result<()> { + Err(FsError::EntryNotFound) + } + + fn rename(&self, from: &Path, to: &Path) -> Result<()> { + Err(FsError::EntryNotFound) + } + + fn metadata(&self, path: &Path) -> Result { + Err(FsError::EntryNotFound) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + Err(FsError::EntryNotFound) + } + + fn remove_file(&self, path: &Path) -> Result<()> { + Err(FsError::EntryNotFound) + } + + fn new_open_options(&self) -> OpenOptions { + OpenOptions::new(Box::new(EmptyFileSystem::default())) + } +} + +impl FileOpener +for EmptyFileSystem +{ + #[allow(unused_variables)] + fn open( + &mut self, + path: &Path, + conf: &OpenOptionsConfig, + ) -> Result> { + Err(FsError::EntryNotFound) + } +} \ No newline at end of file diff --git a/lib/wasi/src/fs/mod.rs b/lib/wasi/src/fs/mod.rs new file mode 100644 index 00000000000..0feb48756b3 --- /dev/null +++ b/lib/wasi/src/fs/mod.rs @@ -0,0 +1,25 @@ +mod builder; +mod tmp_fs; +mod union_fs; +mod passthru_fs; +mod arc_fs; +mod arc_file; +mod null_file; +mod delegate_file; +mod special_file; +mod empty_fs; +mod tty_file; +mod zero_file; + +pub use builder::*; +pub use tmp_fs::*; +pub use union_fs::*; +pub use passthru_fs::*; +pub use arc_fs::*; +pub use arc_file::*; +pub use null_file::*; +pub use delegate_file::*; +pub use special_file::*; +pub use empty_fs::*; +pub use tty_file::*; +pub use zero_file::*; \ No newline at end of file diff --git a/lib/wasi/src/fs/null_file.rs b/lib/wasi/src/fs/null_file.rs new file mode 100644 index 00000000000..dda151fabef --- /dev/null +++ b/lib/wasi/src/fs/null_file.rs @@ -0,0 +1,56 @@ +use std::io::{self, *}; + +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::{VirtualFile, ClonableVirtualFile}; + +#[derive(Debug, Clone, Default)] +pub struct NullFile {} + +impl Seek for NullFile { + fn seek(&mut self, _pos: SeekFrom) -> io::Result { + Ok(0) + } +} +impl Write for NullFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Read for NullFile { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl VirtualFile for NullFile { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn bytes_available(&self) -> wasmer_vfs::Result { + Ok(0) + } + fn get_fd(&self) -> Option { + None + } +} + +impl ClonableVirtualFile for NullFile {} \ No newline at end of file diff --git a/lib/wasi/src/fs/passthru_fs.rs b/lib/wasi/src/fs/passthru_fs.rs new file mode 100644 index 00000000000..78522970892 --- /dev/null +++ b/lib/wasi/src/fs/passthru_fs.rs @@ -0,0 +1,52 @@ +use std::path::Path; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; + +use wasmer_vfs::*; + +#[derive(Debug)] +pub struct PassthruFileSystem { + fs: Box, +} + +impl PassthruFileSystem { + pub fn new(inner: Box) -> Self { + Self { + fs: inner, + } + } +} + +impl FileSystem for PassthruFileSystem { + fn read_dir(&self, path: &Path) -> Result { + self.fs.read_dir(path) + } + + fn create_dir(&self, path: &Path) -> Result<()> { + self.fs.create_dir(path) + } + + fn remove_dir(&self, path: &Path) -> Result<()> { + self.fs.remove_dir(path) + } + + fn rename(&self, from: &Path, to: &Path) -> Result<()> { + self.fs.rename(from, to) + } + + fn metadata(&self, path: &Path) -> Result { + self.fs.metadata(path) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + self.fs.symlink_metadata(path) + } + + fn remove_file(&self, path: &Path) -> Result<()> { + self.fs.remove_file(path) + } + + fn new_open_options(&self) -> OpenOptions { + self.fs.new_open_options() + } +} diff --git a/lib/wasi/src/fs/special_file.rs b/lib/wasi/src/fs/special_file.rs new file mode 100644 index 00000000000..4cd5c2720ff --- /dev/null +++ b/lib/wasi/src/fs/special_file.rs @@ -0,0 +1,68 @@ +use std::io::{self, *}; + +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::VirtualFile; +use wasmer_wasi_types::__wasi_fd_t; + +#[derive(Debug)] +pub struct SpecialFile { + fd: __wasi_fd_t +} + +impl SpecialFile { + pub fn new(fd: __wasi_fd_t) -> Self { + Self { + fd + } + } +} + +impl Seek for SpecialFile { + fn seek(&mut self, _pos: SeekFrom) -> io::Result { + Ok(0) + } +} +impl Write for SpecialFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Read for SpecialFile { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl VirtualFile for SpecialFile { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn bytes_available(&self) -> wasmer_vfs::Result { + Ok(0) + } + fn get_special_fd(&self) -> Option { + Some(self.fd) + } + fn get_fd(&self) -> Option { + None + } +} \ No newline at end of file diff --git a/lib/wasi/src/fs/tmp_fs.rs b/lib/wasi/src/fs/tmp_fs.rs new file mode 100644 index 00000000000..03d0e2546c7 --- /dev/null +++ b/lib/wasi/src/fs/tmp_fs.rs @@ -0,0 +1,77 @@ +#![allow(dead_code)] +#![allow(unused)] +use std::collections::HashMap; +use std::io::prelude::*; +use std::io::SeekFrom; +use std::io::{self}; +use std::path::{Path, PathBuf}; +use std::result::Result as StdResult; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; +use std::sync::Mutex; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; + +use wasmer_vfs::mem_fs; +use wasmer_vfs::Result as FsResult; +use wasmer_vfs::*; +use crate::{types as wasi_types, WasiFile, WasiFsError}; + +#[derive(Debug, Clone)] +pub struct TmpFileSystem { + fs: mem_fs::FileSystem, +} + +impl TmpFileSystem { + pub fn new() -> Self { + Self { + fs: mem_fs::FileSystem::default(), + } + } + + pub fn new_open_options_ext(&self) -> mem_fs::FileOpener { + self.fs.new_open_options_ext() + } + + pub fn union(&self, other: &Arc) { + self.fs.union(other) + } + + pub fn mount(&self, src_path: PathBuf, other: &Arc, dst_path: PathBuf) -> Result<()> { + self.fs.mount(src_path, other, dst_path) + } +} + +impl FileSystem for TmpFileSystem { + fn read_dir(&self, path: &Path) -> Result { + self.fs.read_dir(path) + } + + fn create_dir(&self, path: &Path) -> Result<()> { + self.fs.create_dir(path) + } + + fn remove_dir(&self, path: &Path) -> Result<()> { + self.fs.remove_dir(path) + } + + fn rename(&self, from: &Path, to: &Path) -> Result<()> { + self.fs.rename(from, to) + } + + fn metadata(&self, path: &Path) -> Result { + self.fs.metadata(path) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + self.fs.symlink_metadata(path) + } + + fn remove_file(&self, path: &Path) -> Result<()> { + self.fs.remove_file(path) + } + + fn new_open_options(&self) -> OpenOptions { + self.fs.new_open_options() + } +} diff --git a/lib/wasi/src/fs/tty_file.rs b/lib/wasi/src/fs/tty_file.rs new file mode 100644 index 00000000000..9236ed2f2bd --- /dev/null +++ b/lib/wasi/src/fs/tty_file.rs @@ -0,0 +1,85 @@ +use std::{ + io::{ + self, + * + }, + sync::Arc +}; +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::VirtualFile; + +#[derive(Debug)] +pub struct TtyFile { + runtime: Arc, + stdin: Box +} + +impl TtyFile +{ + pub fn new(runtime: Arc, stdin: Box) -> Self { + Self { + runtime, + stdin + } + } +} + +impl Seek for TtyFile { + fn seek(&mut self, _pos: SeekFrom) -> io::Result { + Ok(0) + } +} +impl Write for TtyFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.runtime.stdout(buf)?; + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Read for TtyFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.stdin.read(buf) + } +} + +impl VirtualFile for TtyFile { + fn last_accessed(&self) -> u64 { + self.stdin.last_accessed() + } + fn last_modified(&self) -> u64 { + self.stdin.last_modified() + } + fn created_time(&self) -> u64 { + self.stdin.created_time() + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn bytes_available(&self) -> wasmer_vfs::Result { + self.stdin.bytes_available() + } + fn bytes_available_read(&self) -> wasmer_vfs::Result> { + self.stdin.bytes_available_read() + } + fn bytes_available_write(&self) -> wasmer_vfs::Result> { + self.stdin.bytes_available_write() + } + fn get_fd(&self) -> Option { + None + } + fn is_open(&self) -> bool { + true + } + fn get_special_fd(&self) -> Option { + None + } +} diff --git a/lib/wasi/src/fs/union_fs.rs b/lib/wasi/src/fs/union_fs.rs new file mode 100644 index 00000000000..202e102063c --- /dev/null +++ b/lib/wasi/src/fs/union_fs.rs @@ -0,0 +1,435 @@ +#![allow(dead_code)] +#![allow(unused)] +use wasmer_vfs::*; +use std::borrow::Cow; +use std::ops::Add; +use std::path::{Path, PathBuf}; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::Weak; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; + +#[derive(Debug)] +pub struct MountPoint { + pub path: String, + pub name: String, + pub fs: Option>>, + pub weak_fs: Weak>, + pub temp_holding: Arc>>>>, + pub should_sanitize: bool, + pub new_path: Option, +} + +impl Clone for MountPoint { + fn clone(&self) -> Self { + Self { + path: self.path.clone(), + name: self.name.clone(), + fs: None, + weak_fs: self.weak_fs.clone(), + temp_holding: self.temp_holding.clone(), + should_sanitize: self.should_sanitize, + new_path: self.new_path.clone(), + } + } +} + +impl MountPoint { + pub fn fs(&self) -> Option>> { + match &self.fs { + Some(a) => Some(a.clone()), + None => self.weak_fs.upgrade(), + } + } + + fn solidify(&mut self) { + if self.fs.is_none() { + self.fs = self.weak_fs.upgrade(); + } + { + let mut guard = self.temp_holding.lock().unwrap(); + let fs = guard.take(); + if self.fs.is_none() { + self.fs = fs; + } + } + } + + fn strong(&self) -> Option { + match self.fs() { + Some(fs) => Some(StrongMountPoint { + path: self.path.clone(), + name: self.name.clone(), + fs, + should_sanitize: self.should_sanitize, + new_path: self.new_path.clone(), + }), + None => None, + } + } +} + +#[derive(Debug)] +pub struct StrongMountPoint { + pub path: String, + pub name: String, + pub fs: Arc>, + pub should_sanitize: bool, + pub new_path: Option, +} + +#[derive(Debug, Clone)] +pub struct UnionFileSystem { + pub mounts: Vec, +} + +impl UnionFileSystem { + pub fn new() -> UnionFileSystem { + UnionFileSystem { + mounts: Vec::new(), + } + } + + pub fn clear(&mut self) { + self.mounts.clear(); + } +} + +impl UnionFileSystem { + pub fn mount( + &mut self, + name: &str, + path: &str, + should_sanitize: bool, + fs: Box, + new_path: Option<&str>, + ) { + self.unmount(path); + let mut path = path.to_string(); + if path.starts_with("/") == false { + path.insert(0, '/'); + } + if path.ends_with("/") == false { + path += "/"; + } + let new_path = new_path.map(|new_path| { + let mut new_path = new_path.to_string(); + if new_path.ends_with("/") == false { + new_path += "/"; + } + new_path + }); + let fs = Arc::new(fs); + + let mount = MountPoint { + path, + name: name.to_string(), + fs: None, + weak_fs: Arc::downgrade(&fs), + temp_holding: Arc::new(Mutex::new(Some(fs.clone()))), + should_sanitize, + new_path, + }; + + self.mounts.push(mount); + } + + pub fn unmount(&mut self, path: &str) { + let path1 = path.to_string(); + let mut path2 = path1.clone(); + if path2.starts_with("/") == false { + path2.insert(0, '/'); + } + let mut path3 = path2.clone(); + if path3.ends_with("/") == false { + path3.push_str("/") + } + if path2.ends_with("/") { + path2 = (&path2[..(path2.len() - 1)]).to_string(); + } + + self.mounts + .retain(|mount| mount.path != path2 && mount.path != path3); + } + + fn read_dir_internal(&self, path: &Path) -> Result { + let path = path.to_string_lossy(); + + let mut ret = None; + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.read_dir(Path::new(path.as_str())) { + Ok(dir) => { + if ret.is_none() { + ret = Some(Vec::new()); + } + let ret = ret.as_mut().unwrap(); + for sub in dir { + if let Ok(sub) = sub { + ret.push(sub); + } + } + } + Err(err) => { + debug!("failed to read dir - {}", err); + } + } + } + + match ret { + Some(ret) => Ok(ReadDir::new(ret)), + None => Err(FsError::EntryNotFound), + } + } + + pub fn sanitize(mut self) -> Self { + self.solidify(); + self.mounts.retain(|mount| mount.should_sanitize == false); + self + } + + pub fn solidify(&mut self) { + for mount in self.mounts.iter_mut() { + mount.solidify(); + } + } +} + +impl FileSystem for UnionFileSystem { + fn read_dir(&self, path: &Path) -> Result { + debug!("read_dir: path={}", path.display()); + self.read_dir_internal(path) + } + fn create_dir(&self, path: &Path) -> Result<()> { + debug!("create_dir: path={}", path.display()); + + if self.read_dir_internal(path).is_ok() { + //return Err(FsError::AlreadyExists); + return Ok(()); + } + + let path = path.to_string_lossy(); + let mut ret_error = FsError::EntryNotFound; + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.create_dir(Path::new(path.as_str())) { + Ok(ret) => { + return Ok(ret); + } + Err(err) => { + ret_error = err; + } + } + } + Err(ret_error) + } + fn remove_dir(&self, path: &Path) -> Result<()> { + debug!("remove_dir: path={}", path.display()); + let mut ret_error = FsError::EntryNotFound; + let path = path.to_string_lossy(); + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.remove_dir(Path::new(path.as_str())) { + Ok(ret) => { + return Ok(ret); + } + Err(err) => { + ret_error = err; + } + } + } + Err(ret_error) + } + fn rename(&self, from: &Path, to: &Path) -> Result<()> { + debug!("rename: from={} to={}", from.display(), to.display()); + let mut ret_error = FsError::EntryNotFound; + let from = from.to_string_lossy(); + let to = to.to_string_lossy(); + for (path, mount) in filter_mounts(&self.mounts, from.as_ref()) { + let mut to = if to.starts_with(mount.path.as_str()) { + (&to[mount.path.len()..]).to_string() + } else { + ret_error = FsError::UnknownError; + continue; + }; + if to.starts_with("/") == false { + to = format!("/{}", to); + } + match mount.fs.rename(Path::new(from.as_ref()), Path::new(to.as_str())) { + Ok(ret) => { + trace!("rename ok"); + return Ok(ret); + } + Err(err) => { + trace!("rename error (from={}, to={}) - {}", from, to, err); + ret_error = err; + } + } + } + trace!("rename failed - {}", ret_error); + Err(ret_error) + } + fn metadata(&self, path: &Path) -> Result { + debug!("metadata: path={}", path.display()); + let mut ret_error = FsError::EntryNotFound; + let path = path.to_string_lossy(); + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.metadata(Path::new(path.as_str())) { + Ok(ret) => { + return Ok(ret); + } + Err(err) => { + // This fixes a bug when attempting to create the directory /usr when it does not exist + // on the x86 version of memfs + // TODO: patch wasmer_vfs and remove + if let FsError::NotAFile = &err { + ret_error = FsError::EntryNotFound; + } else { + debug!("metadata failed: (path={}) - {}", path, err); + ret_error = err; + } + } + } + } + Err(ret_error) + } + fn symlink_metadata(&self, path: &Path) -> Result { + debug!("symlink_metadata: path={}", path.display()); + let mut ret_error = FsError::EntryNotFound; + let path = path.to_string_lossy(); + for (path_inner, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.symlink_metadata(Path::new(path_inner.as_str())) { + Ok(ret) => { + return Ok(ret); + } + Err(err) => { + // This fixes a bug when attempting to create the directory /usr when it does not exist + // on the x86 version of memfs + // TODO: patch wasmer_vfs and remove + if let FsError::NotAFile = &err { + ret_error = FsError::EntryNotFound; + } else { + debug!("metadata failed: (path={}) - {}", path, err); + ret_error = err; + } + } + } + } + debug!("symlink_metadata: failed={}", ret_error); + Err(ret_error) + } + fn remove_file(&self, path: &Path) -> Result<()> { + debug!("remove_file: path={}", path.display()); + let mut ret_error = FsError::EntryNotFound; + let path = path.to_string_lossy(); + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount.fs.remove_file(Path::new(path.as_str())) { + Ok(ret) => { + return Ok(ret); + } + Err(err) => { + ret_error = err; + } + } + } + Err(ret_error) + } + fn new_open_options(&self) -> OpenOptions { + let opener = Box::new(UnionFileOpener { + mounts: self.mounts.clone(), + }); + OpenOptions::new(opener) + } +} + +fn filter_mounts( + mounts: &Vec, + mut target: &str, +) -> impl Iterator { + let mut biggest_path = 0usize; + let mut ret = Vec::new(); + for mount in mounts.iter().rev() { + let mut test_mount_path1 = mount.path.clone(); + if test_mount_path1.ends_with("/") == false { + test_mount_path1.push_str("/"); + } + + let mut test_mount_path2 = mount.path.clone(); + if test_mount_path2.ends_with("/") == true { + test_mount_path2 = test_mount_path2[..(test_mount_path2.len() - 1)].to_string(); + } + + if target == test_mount_path1 || target == test_mount_path2 { + if let Some(mount) = mount.strong() { + biggest_path = biggest_path.max(mount.path.len()); + let mut path = "/".to_string(); + if let Some(ref np) = mount.new_path { + path = np.to_string(); + } + ret.push((path, mount)); + } + } else if target.starts_with(test_mount_path1.as_str()) { + if let Some(mount) = mount.strong() { + biggest_path = biggest_path.max(mount.path.len()); + let path = &target[test_mount_path2.len()..]; + let mut path = path.to_string(); + if let Some(ref np) = mount.new_path { + path = format!("{}{}", np, &path[1..]); + } + ret.push((path, mount)); + } + } + } + ret.retain(|(a, b)| b.path.len() >= biggest_path); + ret.into_iter() +} + +#[derive(Debug)] +pub struct UnionFileOpener { + mounts: Vec, +} + +impl FileOpener for UnionFileOpener { + fn open( + &mut self, + path: &Path, + conf: &OpenOptionsConfig, + ) -> Result> { + debug!("open: path={}", path.display()); + let mut ret_err = FsError::EntryNotFound; + let path = path.to_string_lossy(); + if conf.create() || conf.create_new() { + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + if let Ok(mut ret) = mount + .fs + .new_open_options() + .truncate(conf.truncate()) + .append(conf.append()) + .read(conf.read()) + .write(conf.write()) + .open(path) + { + if conf.create_new() { + ret.unlink(); + continue; + } + return Ok(ret); + } + } + } + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + match mount + .fs + .new_open_options() + .options(conf.clone()) + .open(path) + { + Ok(ret) => return Ok(ret), + Err(err) if ret_err == FsError::EntryNotFound => { + ret_err = err; + } + _ => {} + } + } + Err(ret_err) + } +} diff --git a/lib/wasi/src/fs/zero_file.rs b/lib/wasi/src/fs/zero_file.rs new file mode 100644 index 00000000000..83801f1d0d1 --- /dev/null +++ b/lib/wasi/src/fs/zero_file.rs @@ -0,0 +1,57 @@ +use std::io::{self, *}; + +use wasmer_vbus::FileDescriptor; +use wasmer_vfs::VirtualFile; + +#[derive(Debug, Default)] +pub struct ZeroFile {} + +impl Seek for ZeroFile { + fn seek(&mut self, _pos: SeekFrom) -> io::Result { + Ok(0) + } +} +impl Write for ZeroFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Read for ZeroFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + for b in buf.iter_mut() { + *b = 0; + } + Ok(buf.len()) + } +} + +impl VirtualFile for ZeroFile { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } + fn bytes_available(&self) -> wasmer_vfs::Result { + Ok(0) + } + fn get_fd(&self) -> Option { + None + } +} diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 81a77fc7fcd..46bdd9bd991 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -15,6 +15,10 @@ #[cfg(all(not(feature = "sys"), not(feature = "js")))] compile_error!("At least the `sys` or the `js` feature must be enabled. Please, pick one."); +#[cfg(feature="compiler")] +#[cfg(not(any(feature = "compiler-cranelift", feature = "compiler-llvm", feature = "compiler-singlepass")))] +compile_error!("Either feature \"compiler_cranelift\", \"compiler_singlepass\" or \"compiler_llvm\" must be enabled when using \"compiler\"."); + #[cfg(all(feature = "sys", feature = "js"))] compile_error!( "Cannot have both `sys` and `js` features enabled at the same time. Please, pick one." @@ -28,56 +32,83 @@ compile_error!( "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." ); -#[cfg(all(feature = "host-fs", feature = "mem-fs"))] -compile_error!( - "Cannot have both `host-fs` and `mem-fs` features enabled at the same time. Please, pick one." -); - #[macro_use] mod macros; -mod runtime; +pub mod runtime; mod state; mod syscalls; mod utils; +pub mod fs; +#[cfg(feature = "os")] +pub mod wapm; +#[cfg(feature = "os")] +pub mod bin_factory; +#[cfg(feature = "os")] +pub mod builtins; +#[cfg(feature = "os")] +pub mod os; -/// Runners for WASI / Emscripten -#[cfg(feature = "webc_runner")] -pub mod runners; - -use crate::syscalls::*; +#[cfg(feature = "compiler")] +pub use wasmer_compiler; +#[cfg(feature = "compiler-cranelift")] +pub use wasmer_compiler_cranelift; +#[cfg(feature = "compiler-llvm")] +pub use wasmer_compiler_llvm; +#[cfg(feature = "compiler-singlepass")] +pub use wasmer_compiler_singlepass; pub use crate::state::{ - Fd, Pipe, Stderr, Stdin, Stdout, WasiBidirectionalPipePair, WasiBidirectionalSharedPipePair, - WasiFs, WasiInodes, WasiPipe, WasiState, WasiStateBuilder, WasiStateCreationError, ALL_RIGHTS, - VIRTUAL_ROOT_FD, + Fd, Pipe, WasiFs, WasiInodes, WasiState, WasiStateBuilder, + WasiThreadId, WasiThreadHandle, WasiProcessId, WasiControlPlane, WasiThread, WasiProcess, WasiPipe, + WasiStateCreationError, ALL_RIGHTS, VIRTUAL_ROOT_FD, default_fs_backing }; pub use crate::syscalls::types; -#[cfg(feature = "wasix")] -pub use crate::utils::is_wasix_module; -pub use crate::utils::{get_wasi_version, get_wasi_versions, is_wasi_module, WasiVersion}; -pub use wasmer_vbus::{UnsupportedVirtualBus, VirtualBus}; +pub use crate::utils::{ + get_wasi_version, get_wasi_versions, is_wasi_module, is_wasix_module, WasiVersion, +}; +#[cfg(feature = "os")] +use bin_factory::BinFactory; +#[allow(unused_imports)] +use bytes::{BytesMut, Bytes}; +use derivative::Derivative; +use syscalls::platform_clock_time_get; +use tracing::{trace, warn, error}; +use wasmer_vbus::SpawnEnvironmentIntrinsics; +pub use wasmer_vbus::{DefaultVirtualBus, VirtualBus, BusSpawnedProcessJoin}; #[deprecated(since = "2.1.0", note = "Please use `wasmer_vfs::FsError`")] pub use wasmer_vfs::FsError as WasiFsError; #[deprecated(since = "2.1.0", note = "Please use `wasmer_vfs::VirtualFile`")] pub use wasmer_vfs::VirtualFile as WasiFile; pub use wasmer_vfs::{FsError, VirtualFile}; pub use wasmer_vnet::{UnsupportedVirtualNetworking, VirtualNetworking}; +use wasmer_wasi_types::{__WASI_CLOCK_MONOTONIC, __WASI_SIGKILL, __WASI_SIGQUIT, __WASI_SIGINT, __WASI_EINTR}; -use derivative::*; +// re-exports needed for OS +#[cfg(feature = "os")] +pub use wasmer_vfs; +#[cfg(feature = "os")] +pub use wasmer_vnet; +#[cfg(feature = "os")] +pub use wasmer_vbus; +#[cfg(feature = "os")] +pub use wasmer; + +use std::cell::RefCell; use std::ops::Deref; +use std::sync::atomic::{AtomicU32, Ordering}; use thiserror::Error; use tracing::trace; use wasmer::{ - imports, namespace, AsStoreMut, AsStoreRef, ExportError, Exports, Function, FunctionEnv, - Imports, Instance, Memory, Memory32, MemoryAccessError, MemorySize, MemoryView, Module, - TypedFunction, + imports, namespace, AsStoreMut, Exports, Function, FunctionEnv, Imports, Memory, Memory32, + MemoryAccessError, MemorySize, Module, TypedFunction, Memory64, MemoryView, AsStoreRef, Instance, ExportError, Global, Value, Store, }; use wasmer_wasi_types::wasi::{BusErrno, Errno, Snapshot0Clockid}; pub use runtime::{ PluggableRuntimeImplementation, WasiRuntimeImplementation, WasiThreadError, WasiTtyState, + WebSocketAbi, VirtualTaskManager, SpawnedMemory }; -use std::sync::{mpsc, Arc, Mutex, RwLockReadGuard, RwLockWriteGuard}; +use std::sync::{Arc, RwLockReadGuard, RwLockWriteGuard}; use std::time::Duration; /// This is returned in `RuntimeError`. @@ -90,308 +121,479 @@ pub enum WasiError { UnknownWasiVersion, } -/// Represents the ID of a WASI thread -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WasiThreadId(u32); +/// Represents the ID of a WASI calling thread +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WasiCallingId(u32); -impl From for WasiThreadId { - fn from(id: u32) -> Self { - Self(id) +impl WasiCallingId { + pub fn raw(&self) -> u32 { + self.0 } -} -impl From for u32 { - fn from(t: WasiThreadId) -> u32 { - t.0 as u32 + + pub fn inc(&mut self) -> WasiCallingId { + self.0 += 1; + self.clone() } } -/// Represents the ID of a sub-process -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WasiBusProcessId(u32); - -impl From for WasiBusProcessId { +impl From for WasiCallingId { fn from(id: u32) -> Self { Self(id) } } -impl From for u32 { - fn from(id: WasiBusProcessId) -> u32 { - id.0 as u32 +impl From for u32 { + fn from(t: WasiCallingId) -> u32 { + t.0 as u32 } } -#[derive(Debug, Clone)] -pub struct WasiThread { - /// ID of this thread +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct WasiEnvInner +{ + /// Represents a reference to the memory + memory: Memory, + /// Represents the module that is being used (this is NOT send/sync) + /// however the code itself makes sure that it is used in a safe way + module: Module, + /// All the exports for the module + exports: Exports, + //// Points to the current location of the memory stack pointer + stack_pointer: Option, + /// Main function that will be invoked (name = "_start") + #[derivative(Debug = "ignore")] + start: Option>, + /// Function thats invoked to initialize the WASM module (nane = "_initialize") + #[allow(dead_code)] + #[derivative(Debug = "ignore")] + initialize: Option>, + /// Represents the callback for spawning a thread (name = "_start_thread") + /// (due to limitations with i64 in browsers the parameters are broken into i32 pairs) + /// [this takes a user_data field] + #[derivative(Debug = "ignore")] + thread_spawn: Option>, + /// Represents the callback for spawning a reactor (name = "_react") + /// (due to limitations with i64 in browsers the parameters are broken into i32 pairs) + /// [this takes a user_data field] + #[derivative(Debug = "ignore")] + react: Option>, + /// Represents the callback for signals (name = "__wasm_signal") + /// Signals are triggered asynchronously at idle times of the process + #[derivative(Debug = "ignore")] + signal: Option>, + /// Flag that indicates if the signal callback has been set by the WASM + /// process - if it has not been set then the runtime behaves differently + /// when a CTRL-C is pressed. + signal_set: bool, + /// Represents the callback for destroying a local thread variable (name = "_thread_local_destroy") + /// [this takes a pointer to the destructor and the data to be destroyed] + #[derivative(Debug = "ignore")] + thread_local_destroy: Option>, + /// asyncify_start_unwind(data : i32): call this to start unwinding the + /// stack from the current location. "data" must point to a data + /// structure as described above (with fields containing valid data). + #[derivative(Debug = "ignore")] + asyncify_start_unwind: Option>, + /// asyncify_stop_unwind(): call this to note that unwinding has + /// concluded. If no other code will run before you start to rewind, + /// this is not strictly necessary, however, if you swap between + /// coroutines, or even just want to run some normal code during a + /// "sleep", then you must call this at the proper time. Otherwise, + /// the code will think it is still unwinding when it should not be, + /// which means it will keep unwinding in a meaningless way. + #[derivative(Debug = "ignore")] + asyncify_stop_unwind: Option>, + /// asyncify_start_rewind(data : i32): call this to start rewinding the + /// stack vack up to the location stored in the provided data. This prepares + /// for the rewind; to start it, you must call the first function in the + /// call stack to be unwound. + #[derivative(Debug = "ignore")] + asyncify_start_rewind: Option>, + /// asyncify_stop_rewind(): call this to note that rewinding has + /// concluded, and normal execution can resume. + #[derivative(Debug = "ignore")] + asyncify_stop_rewind: Option>, + /// asyncify_get_state(): call this to get the current value of the + /// internal "__asyncify_state" variable as described above. + /// It can be used to distinguish between unwinding/rewinding and normal + /// calls, so that you know when to start an asynchronous operation and + /// when to propagate results back. #[allow(dead_code)] - id: WasiThreadId, - /// Signalers used to tell joiners that the thread has exited - exit: Arc>>>, - /// Event to wait on for the thread to join - join: Arc>>, + #[derivative(Debug = "ignore")] + asyncify_get_state: Option>, } -impl WasiThread { - /// Waits for the thread to exit (false = timeout) - pub fn join(&self, timeout: Duration) -> bool { - let guard = self.join.lock().unwrap(); - let timeout = guard.recv_timeout(timeout); - match timeout { - Ok(_) => true, - Err(mpsc::RecvTimeoutError::Disconnected) => true, - Err(mpsc::RecvTimeoutError::Timeout) => false, +impl WasiEnvInner +{ + pub fn new(module: Module, memory: Memory, store: &impl AsStoreRef, instance: &Instance) -> Self + { + WasiEnvInner { + module, + memory, + exports: instance.exports.clone(), + stack_pointer: instance.exports.get_global("__stack_pointer").map(|a| a.clone()).ok(), + start: instance.exports.get_typed_function(store, "_start").ok(), + initialize: instance.exports.get_typed_function(store, "_initialize").ok(), + thread_spawn: instance.exports.get_typed_function(store, "_start_thread").ok(), + react: instance.exports.get_typed_function(store, "_react").ok(), + signal: instance.exports.get_typed_function(store, "__wasm_signal").ok(), + signal_set: false, + asyncify_start_unwind: instance.exports.get_typed_function(store, "asyncify_start_unwind").ok(), + asyncify_stop_unwind: instance.exports.get_typed_function(store, "asyncify_stop_unwind").ok(), + asyncify_start_rewind: instance.exports.get_typed_function(store, "asyncify_start_rewind").ok(), + asyncify_stop_rewind: instance.exports.get_typed_function(store, "asyncify_stop_rewind").ok(), + asyncify_get_state: instance.exports.get_typed_function(store, "asyncify_get_state").ok(), + thread_local_destroy: instance.exports.get_typed_function(store, "_thread_local_destroy").ok(), } } } -pub struct WasiFunctionEnv { - pub env: FunctionEnv, -} +/// The code itself makes safe use of the struct so multiple threads don't access +/// it (without this the JS code prevents the reference to the module from being stored +/// which is needed for the multithreading mode) +unsafe impl Send for WasiEnvInner { } +unsafe impl Sync for WasiEnvInner { } -impl WasiFunctionEnv { - pub fn new(store: &mut impl AsStoreMut, env: WasiEnv) -> Self { - Self { - env: FunctionEnv::new(store, env), - } - } +/// The default stack size for WASIX +pub const DEFAULT_STACK_SIZE: u64 = 1_048_576u64; +pub const DEFAULT_STACK_BASE: u64 = DEFAULT_STACK_SIZE; - /// Get an `Imports` for a specific version of WASI detected in the module. - pub fn import_object( - &self, - store: &mut impl AsStoreMut, - module: &Module, - ) -> Result { - let wasi_version = get_wasi_version(module, false).ok_or(WasiError::UnknownWasiVersion)?; - Ok(generate_import_object_from_env( - store, - &self.env, - wasi_version, - )) - } - - pub fn data_mut<'a>(&'a self, store: &'a mut impl AsStoreMut) -> &'a mut WasiEnv { - self.env.as_mut(store) - } - - /// Initializes the WasiEnv using the instance exports - /// (this must be executed before attempting to use it) - /// (as the stores can not by themselves be passed between threads we can store the module - /// in a thread-local variables and use it later - for multithreading) - pub fn initialize( - &mut self, - store: &mut impl AsStoreMut, - instance: &Instance, - ) -> Result<(), ExportError> { - // List all the exports and imports - for ns in instance.module().exports() { - //trace!("module::export - {} ({:?})", ns.name(), ns.ty()); - trace!("module::export - {}", ns.name()); - } - for ns in instance.module().imports() { - trace!("module::import - {}::{}", ns.module(), ns.name()); - } - - // First we get the malloc function which if it exists will be used to - // create the pthread_self structure - let memory = instance.exports.get_memory("memory")?.clone(); - let env = self.data_mut(store); - env.set_memory(memory); - - Ok(()) - } - - /// Like `import_object` but containing all the WASI versions detected in - /// the module. - pub fn import_object_for_all_wasi_versions( - &self, - store: &mut impl AsStoreMut, - module: &Module, - ) -> Result { - let wasi_versions = - get_wasi_versions(module, false).ok_or(WasiError::UnknownWasiVersion)?; - - let mut resolver = Imports::new(); - for version in wasi_versions.iter() { - let new_import_object = generate_import_object_from_env(store, &self.env, *version); - for ((n, m), e) in new_import_object.into_iter() { - resolver.define(&n, &m, e); - } - } - - #[cfg(feature = "wasix")] - if is_wasix_module(module) { - self.data_mut(store) - .state - .fs - .is_wasix - .store(true, std::sync::atomic::Ordering::Release); - } - - Ok(resolver) - } +#[derive(Debug, Clone)] +pub struct WasiVFork { + /// The unwound stack before the vfork occured + pub rewind_stack: BytesMut, + /// The memory stack before the vfork occured + pub memory_stack: BytesMut, + /// The mutable parts of the store + pub store_data: Bytes, + /// The environment before the vfork occured + pub env: Box, + /// Handle of the thread we have forked (dropping this handle + /// will signal that the thread is dead) + pub handle: WasiThreadHandle, + /// Offset into the memory where the PID will be + /// written when the real fork takes places + pub pid_offset: u64, } /// The environment provided to the WASI imports. #[derive(Derivative, Clone)] #[derivative(Debug)] -#[allow(dead_code)] -pub struct WasiEnv { - /// ID of this thread (zero is the main thread) - id: WasiThreadId, - /// Represents a reference to the memory - memory: Option, - /// If the module has it then map the thread start - #[derivative(Debug = "ignore")] - thread_start: Option>, - #[derivative(Debug = "ignore")] - reactor_work: Option>, - #[derivative(Debug = "ignore")] - reactor_finish: Option>, - #[derivative(Debug = "ignore")] - malloc: Option>, - #[derivative(Debug = "ignore")] - free: Option>, +pub struct WasiEnv +where +{ + /// Represents the process this environment is attached to + pub process: WasiProcess, + /// Represents the thread this environment is attached to + pub thread: WasiThread, + /// Represents a fork of the process that is currently in play + pub vfork: Option, + /// Base stack pointer for the memory stack + pub stack_base: u64, + /// Start of the stack memory that is allocated for this thread + pub stack_start: u64, /// Shared state of the WASI system. Manages all the data that the /// executing WASI program can see. pub state: Arc, + /// Binary factory attached to this environment + #[cfg(feature = "os")] + #[derivative(Debug = "ignore")] + pub bin_factory: BinFactory, + /// Inner functions and references that are loaded before the environment starts + pub inner: Option, + /// List of the handles that are owned by this context + /// (this can be used to ensure that threads own themselves or others) + pub owned_handles: Vec, /// Implementation of the WASI runtime. - pub(crate) runtime: Arc, + pub runtime: Arc, + /// Task manager used to spawn threads and manage the ASYNC runtime + pub tasks: Arc } impl WasiEnv { - /// Create a new WasiEnv from a WasiState (memory will be set to None) - pub fn new(state: WasiState) -> Self { - Self { - id: 0u32.into(), - state: Arc::new(state), - memory: None, - thread_start: None, - reactor_work: None, - reactor_finish: None, - malloc: None, - free: None, - runtime: Arc::new(PluggableRuntimeImplementation::default()), + /// Forking the WasiState is used when either fork or vfork is called + pub fn fork(&self) -> (Self, WasiThreadHandle) + { + let process = self.process.compute.new_process(); + let handle = process.new_thread(); + + let thread = handle.as_thread(); + thread.copy_stack_from(&self.thread); + + let state = Arc::new(self.state.fork()); + + #[cfg(feature = "os")] + let bin_factory = { + let mut bin_factory = self.bin_factory.clone(); + bin_factory.state = state.clone(); + bin_factory + }; + + ( + Self { + process: process, + thread, + vfork: None, + stack_base: self.stack_base, + stack_start: self.stack_start, + #[cfg(feature = "os")] + bin_factory, + state, + inner: None, + owned_handles: Vec::new(), + runtime: self.runtime.clone(), + tasks: self.tasks.clone(), + }, + handle + ) + } + + pub fn pid(&self) -> WasiProcessId { + self.process.pid() + } + + pub fn tid(&self) -> WasiThreadId { + self.thread.tid() + } +} + +// Represents the current thread ID for the executing method +thread_local!(pub(crate) static CALLER_ID: RefCell = RefCell::new(0)); +thread_local!(pub(crate) static REWIND: RefCell> = RefCell::new(None)); +lazy_static::lazy_static! { + static ref CALLER_ID_SEED: Arc = Arc::new(AtomicU32::new(1)); +} + +/// Returns the current thread ID +pub fn current_caller_id() -> WasiCallingId { + CALLER_ID.with(|f| { + let mut caller_id = f.borrow_mut(); + if *caller_id == 0 { + *caller_id = CALLER_ID_SEED.fetch_add(1, Ordering::AcqRel); } + *caller_id + }).into() +} + +impl WasiEnv { + pub fn new(state: WasiState, #[cfg(feature = "os")] compiled_modules: Arc, process: WasiProcess, thread: WasiThreadHandle) -> Self { + let state = Arc::new(state); + let runtime = Arc::new(PluggableRuntimeImplementation::default()); + Self::new_ext(state, #[cfg(feature = "os")] compiled_modules, process, thread, runtime) } + pub fn new_ext(state: Arc, #[cfg(feature = "os")] compiled_modules: Arc, process: WasiProcess, thread: WasiThreadHandle, runtime: Arc) -> Self { + #[cfg(feature = "os")] + let bin_factory = BinFactory::new( + state.clone(), + compiled_modules, + runtime.clone() + ); + let tasks = runtime.new_task_manager(); + let mut ret = Self { + process, + thread: thread.as_thread(), + vfork: None, + stack_base: DEFAULT_STACK_SIZE, + stack_start: 0, + state, + inner: None, + owned_handles: Vec::new(), + runtime, + tasks, + #[cfg(feature = "os")] + bin_factory + }; + ret.owned_handles.push(thread); + ret + } + /// Returns a copy of the current runtime implementation for this environment - pub fn runtime(&self) -> &(dyn WasiRuntimeImplementation) { + pub fn runtime<'a>(&'a self) -> &'a (dyn WasiRuntimeImplementation) { self.runtime.deref() } + /// Returns a copy of the current tasks implementation for this environment + pub fn tasks<'a>(&'a self) -> &'a (dyn VirtualTaskManager) { + self.tasks.deref() + } + /// Overrides the runtime implementation for this environment - pub fn set_runtime(&mut self, runtime: R) + pub fn set_runtime(&mut self, runtime: R) where R: WasiRuntimeImplementation + Send + Sync + 'static, { self.runtime = Arc::new(runtime); } - /// Returns the current thread ID - pub fn current_thread_id(&self) -> WasiThreadId { - self.id + /// Returns the number of active threads + pub fn active_threads(&self) -> u32 { + self.process.active_threads() } - /// Creates a new thread only this wasi environment - pub fn new_thread(&self) -> WasiThread { - let (tx, rx) = mpsc::channel(); - - let mut guard = self.state.threading.lock().unwrap(); + /// Porcesses any signals that are batched up + pub fn process_signals(&self, store: &mut impl AsStoreMut) -> Result<(), WasiError> + { + // If a signal handler has never been set then we need to handle signals + // differently + if self.inner().signal_set == false { + let signals = self.thread.pop_signals(); + for sig in signals { + if sig == __WASI_SIGINT || + sig == __WASI_SIGQUIT || + sig == __WASI_SIGKILL + { + return Err(WasiError::Exit(__WASI_EINTR as u32)); + } else { + trace!("wasi[{}]::signal-ignored: {}", self.pid(), sig); + } + } + } - guard.thread_seed += 1; - let next_id: WasiThreadId = guard.thread_seed.into(); + // Check for any signals that we need to trigger + // (but only if a signal handler is registered) + if let Some(handler) = self.inner().signal.clone() { + let mut signals = self.thread.pop_signals(); - let thread = WasiThread { - id: next_id, - exit: Arc::new(Mutex::new(Some(tx))), - join: Arc::new(Mutex::new(rx)), - }; + // We might also have signals that trigger on timers + let mut now = 0; + let has_signal_interval = { + let mut any = false; + let inner = self.process.inner.read().unwrap(); + if inner.signal_intervals.is_empty() == false { + now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + for signal in inner.signal_intervals.values() { + let elapsed = now - signal.last_signal; + if elapsed >= signal.interval.as_nanos() { + any = true; + break; + } + } + } + any + }; + if has_signal_interval { + let mut inner = self.process.inner.write().unwrap(); + for signal in inner.signal_intervals.values_mut() { + let elapsed = now - signal.last_signal; + if elapsed >= signal.interval.as_nanos() { + signal.last_signal = now; + signals.push(signal.signal); + } + } + } - guard.threads.insert(thread.id, thread.clone()); - thread + for signal in signals { + tracing::trace!("wasi[{}]::processing-signal: {}", self.pid(), signal); + if let Err(err) = handler.call(store, signal as i32) { + match err.downcast::() { + Ok(err) => { + return Err(err); + } + Err(err) => { + warn!("wasi[{}]::signal handler runtime error - {}", self.pid(), err); + return Err(WasiError::Exit(1)); + } + } + } + } + } + self.yield_now() } - /// Copy the lazy reference so that when it's initialized during the - /// export phase, all the other references get a copy of it - pub fn memory_clone(&self) -> Option { - self.memory.clone() + // Yields execution + pub fn yield_now_with_signals(&self, store: &mut impl AsStoreMut) -> Result<(), WasiError> + { + self.process_signals(store)?; + self.yield_now() } // Yields execution pub fn yield_now(&self) -> Result<(), WasiError> { - self.runtime.yield_now(self.id)?; + if let Some(forced_exit) = self.thread.try_join() { + return Err(WasiError::Exit(forced_exit)); + } + if let Some(forced_exit) = self.process.try_join() { + return Err(WasiError::Exit(forced_exit)); + } + let tasks = self.tasks.clone(); + self.tasks.block_on(Box::pin(async move { + tasks.sleep_now(current_caller_id(), 0); + })); Ok(()) } - + // Sleeps for a period of time - pub fn sleep(&self, duration: Duration) -> Result<(), WasiError> { - let duration = duration.as_nanos(); - let start = - platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; - self.yield_now()?; - loop { - let now = - platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; - let delta = match now.checked_sub(start) { - Some(a) => a, - None => { - break; + pub fn sleep(&self, store: &mut impl AsStoreMut, duration: Duration) -> Result<(), WasiError> { + let mut signaler = self.thread.signals.1.subscribe(); + + let tasks = self.tasks.clone(); + let (tx_signaller, mut rx_signaller) = tokio::sync::mpsc::unbounded_channel(); + self.tasks.block_on(Box::pin(async move { + loop { + tokio::select! { + _ = tasks.sleep_now(current_caller_id(), duration.as_millis()) => { }, + _ = signaler.recv() => { + let _ = tx_signaller.send(true); + break; + } } - }; - if delta >= duration { - break; } - let remaining = match duration.checked_sub(delta) { - Some(a) => Duration::from_nanos(a as u64), - None => { - break; - } - }; - std::thread::sleep(remaining.min(Duration::from_millis(10))); - self.yield_now()?; + })); + if let Ok(true) = rx_signaller.try_recv() { + self.process_signals(store)?; } Ok(()) } /// Accesses the virtual networking implementation - pub fn net(&self) -> &(dyn VirtualNetworking) { + pub fn net<'a>(&'a self) -> Arc { self.runtime.networking() } /// Accesses the virtual bus implementation - pub fn bus(&self) -> &(dyn VirtualBus) { + pub fn bus<'a>(&'a self) -> Arc + Send + Sync + 'static> { self.runtime.bus() } - /// Set the memory of the WasiEnv (can only be done once) - pub fn set_memory(&mut self, memory: Memory) { - if self.memory.is_some() { - panic!("Memory of a WasiEnv can only be set once!"); - } - self.memory = Some(memory); + /// Providers safe access to the initialized part of WasiEnv + /// (it must be initialized before it can be used) + pub fn inner(&self) -> &WasiEnvInner { + self.inner.as_ref() + .expect("You must initialize the WasiEnv before using it") } + /// Providers safe access to the initialized part of WasiEnv + /// (it must be initialized before it can be used) + pub fn inner_mut(&mut self) -> &mut WasiEnvInner { + self.inner.as_mut() + .expect("You must initialize the WasiEnv before using it") + } + /// Providers safe access to the memory /// (it must be initialized before it can be used) pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> { self.memory().view(store) } - /// Get memory, that needs to have been set fist + /// Providers safe access to the memory + /// (it must be initialized before it can be used) pub fn memory(&self) -> &Memory { - self.memory.as_ref().unwrap() + &self.inner().memory + } + + /// Copy the lazy reference so that when it's initialized during the + /// export phase, all the other references get a copy of it + pub fn memory_clone(&self) -> Memory { + self.memory().clone() } /// Get the WASI state pub fn state(&self) -> &WasiState { &self.state } - - pub(crate) fn get_memory_and_wasi_state<'a>( - &'a self, - store: &'a impl AsStoreRef, - _mem_index: u32, - ) -> (MemoryView<'a>, &WasiState) { + + pub(crate) fn get_memory_and_wasi_state<'a>(&'a self, store: &'a impl AsStoreRef, _mem_index: u32) -> (MemoryView<'a>, &WasiState) { let memory = self.memory_view(store); let state = self.state.deref(); (memory, state) @@ -418,29 +620,364 @@ impl WasiEnv { let inodes = state.inodes.write().unwrap(); (memory, state, inodes) } + + #[cfg(feature = "os")] + pub fn uses<'a, I>(&self, uses: I) -> Result<(), WasiStateCreationError> + where I: IntoIterator + { + use std::{collections::{VecDeque, HashMap}, borrow::Cow}; + // Load all the containers that we inherit from + #[allow(unused_imports)] + use std::path::Path; + #[allow(unused_imports)] + use wasmer_vfs::FileSystem; + + use crate::state::WasiFsRoot; + + let mut already: HashMap> = HashMap::new(); + + let mut use_packages = uses.into_iter().collect::>(); + while let Some(use_package) = use_packages.pop_back() { + if let Some(package) = self.bin_factory.builtins.cmd_wasmer.get(use_package.clone(), self.tasks.deref()) + { + // If its already been added make sure the version is correct + let package_name = package.package_name.to_string(); + if let Some(version) = already.get(&package_name) { + if version.as_ref() != package.version.as_ref() { + return Err(WasiStateCreationError::WasiInheritError(format!("webc package version conflict for {} - {} vs {}", use_package, version, package.version))); + } + continue; + } + already.insert(package_name, package.version.clone()); + + // Add the additional dependencies + for dependency in package.uses.clone() { + use_packages.push_back(dependency); + } + + if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs { + // We first need to copy any files in the package over to the temporary file system + if let Some(fs) = package.webc_fs.as_ref() { + root_fs.union(fs); + } + + // Add all the commands as binaries in the bin folder + let commands = package.commands.read().unwrap(); + if commands.is_empty() == false { + let _ = root_fs.create_dir(Path::new("/bin")); + for command in commands.iter() { + let path = format!("/bin/{}", command.name); + let path = Path::new(path.as_str()); + if let Err(err) = root_fs + .new_open_options_ext() + .insert_ro_file(path, command.atom.clone()) + { + tracing::debug!("failed to add package [{}] command [{}] - {}", use_package, command.name, err); + continue; + } + + // Add the binary package to the bin factory (zero copy the atom) + let mut package = package.clone(); + package.entry = command.atom.clone(); + self.bin_factory.set_binary(path.as_os_str().to_string_lossy().as_ref(), package); + } + } + } else { + return Err(WasiStateCreationError::WasiInheritError(format!("failed to add package as the file system is not sandboxed"))); + } + } else { + return Err(WasiStateCreationError::WasiInheritError(format!("failed to fetch webc package for {}", use_package))); + } + } + Ok(()) + } + + #[cfg(feature = "os")] + #[cfg(feature = "sys")] + pub fn map_commands(&self, map_commands: std::collections::HashMap) -> Result<(), WasiStateCreationError> + { + // Load all the mapped atoms + #[allow(unused_imports)] + use std::path::Path; + #[allow(unused_imports)] + use wasmer_vfs::FileSystem; + + use crate::state::WasiFsRoot; + + #[cfg(feature = "sys")] + for (command, target) in map_commands.iter() { + // Read the file + let file = std::fs::read(target) + .map_err(|err| { + WasiStateCreationError::WasiInheritError(format!("failed to read local binary [{}] - {}", target.as_os_str().to_string_lossy(), err)) + })?; + let file: std::borrow::Cow<'static, [u8]> = file.into(); + + if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs { + let _ = root_fs.create_dir(Path::new("/bin")); + + let path = format!("/bin/{}", command); + let path = Path::new(path.as_str()); + if let Err(err) = root_fs + .new_open_options_ext() + .insert_ro_file(path, file) + { + tracing::debug!("failed to add atom command [{}] - {}", command, err); + continue; + } + } else { + tracing::debug!("failed to add atom command [{}] to the root file system as it is not sandboxed", command); + continue; + } + } + Ok(()) + } } -/// Create an [`Imports`] from a [`Context`] +impl SpawnEnvironmentIntrinsics +for WasiEnv +{ + fn args(&self) -> &Vec { + &self.state.args + } + + fn preopen(&self) -> &Vec { + &self.state.preopen + } + + fn stdin_mode(&self) -> wasmer_vbus::StdioMode { + match self.state.stdin() { + Ok(Some(_)) => wasmer_vbus::StdioMode::Inherit, + _ => wasmer_vbus::StdioMode::Null, + } + } + + fn stdout_mode(&self) -> wasmer_vbus::StdioMode { + match self.state.stdout() { + Ok(Some(_)) => wasmer_vbus::StdioMode::Inherit, + _ => wasmer_vbus::StdioMode::Null, + } + } + + fn stderr_mode(&self) -> wasmer_vbus::StdioMode { + match self.state.stderr() { + Ok(Some(_)) => wasmer_vbus::StdioMode::Inherit, + _ => wasmer_vbus::StdioMode::Null, + } + } + + fn working_dir(&self) -> String { + let guard = self.state.fs.current_dir.lock().unwrap(); + guard.clone() + } +} + +pub struct WasiFunctionEnv { + pub env: FunctionEnv, +} + +impl WasiFunctionEnv { + pub fn new(store: &mut impl AsStoreMut, env: WasiEnv) -> Self { + Self { + env: FunctionEnv::new(store, env), + } + } + + /// Get an `Imports` for a specific version of WASI detected in the module. + pub fn import_object( + &self, + store: &mut impl AsStoreMut, + module: &Module, + ) -> Result { + let wasi_version = get_wasi_version(module, false).ok_or(WasiError::UnknownWasiVersion)?; + Ok(generate_import_object_from_env( + store, + &self.env, + wasi_version, + )) + } + + /// Gets a reference to the WasiEnvironment + pub fn data<'a>(&'a self, store: &'a impl AsStoreRef) -> &'a WasiEnv { + self.env.as_ref(store) + } + + /// Gets a mutable- reference to the host state in this context. + pub fn data_mut<'a>(&'a mut self, store: &'a mut impl AsStoreMut) -> &'a mut WasiEnv { + self.env + .as_mut(store) + } + + /// Initializes the WasiEnv using the instance exports + /// (this must be executed before attempting to use it) + /// (as the stores can not by themselves be passed between threads we can store the module + /// in a thread-local variables and use it later - for multithreading) + pub fn initialize(&mut self, store: &mut impl AsStoreMut, instance: &Instance) -> Result<(), ExportError> + { + // List all the exports and imports + for ns in instance.module().exports() { + //trace!("module::export - {} ({:?})", ns.name(), ns.ty()); + trace!("module::export - {}", ns.name()); + } + for ns in instance.module().imports() { + trace!("module::import - {}::{}", ns.module(), ns.name()); + } + + // First we get the malloc function which if it exists will be used to + // create the pthread_self structure + let memory = instance.exports.get_memory("memory")?.clone(); + let new_inner = WasiEnvInner { + memory, + module: instance.module().clone(), + exports: instance.exports.clone(), + stack_pointer: instance.exports.get_global("__stack_pointer").map(|a| a.clone()).ok(), + start: instance.exports.get_typed_function(store, "_start").ok(), + initialize: instance.exports.get_typed_function(store, "_initialize").ok(), + thread_spawn: instance.exports.get_typed_function(store, "_start_thread").ok(), + react: instance.exports.get_typed_function(store, "_react").ok(), + signal: instance.exports.get_typed_function(&store, "__wasm_signal").ok(), + signal_set: false, + asyncify_start_unwind: instance.exports.get_typed_function(store, "asyncify_start_unwind").ok(), + asyncify_stop_unwind: instance.exports.get_typed_function(store, "asyncify_stop_unwind").ok(), + asyncify_start_rewind: instance.exports.get_typed_function(store, "asyncify_start_rewind").ok(), + asyncify_stop_rewind: instance.exports.get_typed_function(store, "asyncify_stop_rewind").ok(), + asyncify_get_state: instance.exports.get_typed_function(store, "asyncify_get_state").ok(), + thread_local_destroy: instance.exports.get_typed_function(store, "_thread_local_destroy").ok(), + }; + + let env = self.data_mut(store); + env.inner.replace(new_inner); + + env.state.fs.is_wasix.store( + is_wasix_module(instance.module()), + std::sync::atomic::Ordering::Release, + ); + + // Set the base stack + let stack_base = if let Some(stack_pointer) = env.inner().stack_pointer.clone() { + match stack_pointer.get(store) { + Value::I32(a) => a as u64, + Value::I64(a) => a as u64, + _ => DEFAULT_STACK_SIZE + } + } else { + DEFAULT_STACK_SIZE + }; + self.data_mut(store).stack_base = stack_base; + + Ok(()) + } + + /// Initializes the WasiEnv using the instance exports + /// (this must be executed before attempting to use it) + /// (as the stores can not by themselves be passed between threads we can store the module + /// in a thread-local variables and use it later - for multithreading) + pub fn initialize( + &mut self, + store: &mut impl AsStoreMut, + instance: &Instance, + ) -> Result<(), ExportError> { + // List all the exports and imports + for ns in instance.module().exports() { + //trace!("module::export - {} ({:?})", ns.name(), ns.ty()); + trace!("module::export - {}", ns.name()); + } + for ns in instance.module().imports() { + trace!("module::import - {}::{}", ns.module(), ns.name()); + } + + // First we get the malloc function which if it exists will be used to + // create the pthread_self structure + let memory = instance.exports.get_memory("memory")?.clone(); + let env = self.data_mut(store); + env.set_memory(memory); + + Ok(()) + } + + /// Like `import_object` but containing all the WASI versions detected in + /// the module. + pub fn import_object_for_all_wasi_versions( + &self, + store: &mut impl AsStoreMut, + module: &Module, + ) -> Result { + let wasi_versions = + get_wasi_versions(module, false).ok_or(WasiError::UnknownWasiVersion)?; + + let mut resolver = Imports::new(); + for version in wasi_versions.iter() { + let new_import_object = generate_import_object_from_env(store, &self.env, *version); + for ((n, m), e) in new_import_object.into_iter() { + resolver.define(&n, &m, e); + } + } + + Ok(resolver) + } + + pub fn cleanup(&self, store: &mut Store) { + trace!("wasi[{}]:: cleaning up local thread variables", self.data(store).pid()); + + // Destroy all the local thread variables that were allocated for this thread + let to_local_destroy = { + let thread_id = self.data(store).thread.tid(); + let mut to_local_destroy = Vec::new(); + let mut inner = self.data(store).process.write(); + for ((thread, key), val) in inner.thread_local.iter() { + if *thread == thread_id { + if let Some(user_data) = inner.thread_local_user_data.get(key) { + to_local_destroy.push((*user_data, *val)) + } + } + } + inner.thread_local.retain(|(t, _), _| *t != thread_id); + to_local_destroy + }; + if to_local_destroy.len() > 0 { + if let Some(thread_local_destroy) = self.data(store).inner().thread_local_destroy.as_ref().map(|a| a.clone()) { + for (user_data, val) in to_local_destroy { + let user_data_low: u32 = (user_data & 0xFFFFFFFF) as u32; + let user_data_high: u32 = (user_data >> 32) as u32; + + let val_low: u32 = (val & 0xFFFFFFFF) as u32; + let val_high: u32 = (val >> 32) as u32; + + let _ = thread_local_destroy.call(store, user_data_low as i32, user_data_high as i32, val_low as i32, val_high as i32); + } + } + } + + // If this is the main thread then also close all the files + if self.data(store).thread.is_main() { + trace!("wasi[{}]:: cleaning up open file handles", self.data(store).pid()); + + let inodes = self.data(store).state.inodes.read().unwrap(); + self.data(store).state.fs.close_all(inodes.deref()); + } + } +} + +/// Create an [`Imports`] with an existing [`WasiEnv`]. `WasiEnv` +/// needs a [`WasiState`], that can be constructed from a +/// [`WasiStateBuilder`](state::WasiStateBuilder). pub fn generate_import_object_from_env( store: &mut impl AsStoreMut, - env: &FunctionEnv, + ctx: &FunctionEnv, version: WasiVersion, ) -> Imports { match version { - WasiVersion::Snapshot0 => generate_import_object_snapshot0(store, env), + WasiVersion::Snapshot0 => generate_import_object_snapshot0(store, ctx), WasiVersion::Snapshot1 | WasiVersion::Latest => { - generate_import_object_snapshot1(store, env) + generate_import_object_snapshot1(store, ctx) } - #[cfg(feature = "wasix")] - WasiVersion::Wasix32v1 => generate_import_object_wasix32_v1(store, env), - #[cfg(feature = "wasix")] - WasiVersion::Wasix64v1 => generate_import_object_wasix64_v1(store, env), - #[cfg(not(feature = "wasix"))] - _ => unimplemented!(), + WasiVersion::Wasix32v1 => generate_import_object_wasix32_v1(store, ctx), + WasiVersion::Wasix64v1 => generate_import_object_wasix64_v1(store, ctx), } } fn wasi_unstable_exports(mut store: &mut impl AsStoreMut, env: &FunctionEnv) -> Exports { + use syscalls::*; let namespace = namespace! { "args_get" => Function::new_typed_with_env(&mut store, env, args_get::), "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get::), @@ -480,7 +1017,7 @@ fn wasi_unstable_exports(mut store: &mut impl AsStoreMut, env: &FunctionEnv Function::new_typed_with_env(&mut store, env, path_symlink::), "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file::), "poll_oneoff" => Function::new_typed_with_env(&mut store, env, legacy::snapshot0::poll_oneoff), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit), + "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), @@ -495,11 +1032,69 @@ fn wasi_snapshot_preview1_exports( mut store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Exports { + use syscalls::*; + let namespace = namespace! { + "args_get" => Function::new_typed_with_env(&mut store, env, args_get::), + "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get::), + "clock_res_get" => Function::new_typed_with_env(&mut store, env, clock_res_get::), + "clock_time_get" => Function::new_typed_with_env(&mut store, env, clock_time_get::), + "environ_get" => Function::new_typed_with_env(&mut store, env, environ_get::), + "environ_sizes_get" => Function::new_typed_with_env(&mut store, env, environ_sizes_get::), + "fd_advise" => Function::new_typed_with_env(&mut store, env, fd_advise), + "fd_allocate" => Function::new_typed_with_env(&mut store, env, fd_allocate), + "fd_close" => Function::new_typed_with_env(&mut store, env, fd_close), + "fd_datasync" => Function::new_typed_with_env(&mut store, env, fd_datasync), + "fd_fdstat_get" => Function::new_typed_with_env(&mut store, env, fd_fdstat_get::), + "fd_fdstat_set_flags" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_flags), + "fd_fdstat_set_rights" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_rights), + "fd_filestat_get" => Function::new_typed_with_env(&mut store, env, fd_filestat_get::), + "fd_filestat_set_size" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_size), + "fd_filestat_set_times" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_times), + "fd_pread" => Function::new_typed_with_env(&mut store, env, fd_pread::), + "fd_prestat_get" => Function::new_typed_with_env(&mut store, env, fd_prestat_get::), + "fd_prestat_dir_name" => Function::new_typed_with_env(&mut store, env, fd_prestat_dir_name::), + "fd_pwrite" => Function::new_typed_with_env(&mut store, env, fd_pwrite::), + "fd_read" => Function::new_typed_with_env(&mut store, env, fd_read::), + "fd_readdir" => Function::new_typed_with_env(&mut store, env, fd_readdir::), + "fd_renumber" => Function::new_typed_with_env(&mut store, env, fd_renumber), + "fd_seek" => Function::new_typed_with_env(&mut store, env, fd_seek::), + "fd_sync" => Function::new_typed_with_env(&mut store, env, fd_sync), + "fd_tell" => Function::new_typed_with_env(&mut store, env, fd_tell::), + "fd_write" => Function::new_typed_with_env(&mut store, env, fd_write::), + "path_create_directory" => Function::new_typed_with_env(&mut store, env, path_create_directory::), + "path_filestat_get" => Function::new_typed_with_env(&mut store, env, path_filestat_get::), + "path_filestat_set_times" => Function::new_typed_with_env(&mut store, env, path_filestat_set_times::), + "path_link" => Function::new_typed_with_env(&mut store, env, path_link::), + "path_open" => Function::new_typed_with_env(&mut store, env, path_open::), + "path_readlink" => Function::new_typed_with_env(&mut store, env, path_readlink::), + "path_remove_directory" => Function::new_typed_with_env(&mut store, env, path_remove_directory::), + "path_rename" => Function::new_typed_with_env(&mut store, env, path_rename::), + "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink::), + "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file::), + "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff::), + "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), + "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), + "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv::), + "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send::), + "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), + }; + namespace +} + +fn wasix_exports_32( + mut store: &mut impl AsStoreMut, + env: &FunctionEnv, +) -> Exports +{ + use syscalls::*; let namespace = namespace! { "args_get" => Function::new_typed_with_env(&mut store, env, args_get::), "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get::), "clock_res_get" => Function::new_typed_with_env(&mut store, env, clock_res_get::), "clock_time_get" => Function::new_typed_with_env(&mut store, env, clock_time_get::), + "clock_time_set" => Function::new_typed_with_env(&mut store, env, clock_time_set::), "environ_get" => Function::new_typed_with_env(&mut store, env, environ_get::), "environ_sizes_get" => Function::new_typed_with_env(&mut store, env, environ_sizes_get::), "fd_advise" => Function::new_typed_with_env(&mut store, env, fd_advise), @@ -519,10 +1114,13 @@ fn wasi_snapshot_preview1_exports( "fd_read" => Function::new_typed_with_env(&mut store, env, fd_read::), "fd_readdir" => Function::new_typed_with_env(&mut store, env, fd_readdir::), "fd_renumber" => Function::new_typed_with_env(&mut store, env, fd_renumber), + "fd_dup" => Function::new_typed_with_env(&mut store, env, fd_dup::), + "fd_event" => Function::new_typed_with_env(&mut store, env, fd_event::), "fd_seek" => Function::new_typed_with_env(&mut store, env, fd_seek::), "fd_sync" => Function::new_typed_with_env(&mut store, env, fd_sync), "fd_tell" => Function::new_typed_with_env(&mut store, env, fd_tell::), "fd_write" => Function::new_typed_with_env(&mut store, env, fd_write::), + "fd_pipe" => Function::new_typed_with_env(&mut store, env, fd_pipe::), "path_create_directory" => Function::new_typed_with_env(&mut store, env, path_create_directory::), "path_filestat_get" => Function::new_typed_with_env(&mut store, env, path_filestat_get::), "path_filestat_set_times" => Function::new_typed_with_env(&mut store, env, path_filestat_set_times::), @@ -534,25 +1132,248 @@ fn wasi_snapshot_preview1_exports( "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink::), "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file::), "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff::), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit), + "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), + "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), + "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), + "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), + "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), + "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), + "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), + "proc_id" => Function::new_typed_with_env(&mut store, env, proc_id::), + "proc_parent" => Function::new_typed_with_env(&mut store, env, proc_parent::), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), + "tty_get" => Function::new_typed_with_env(&mut store, env, tty_get::), + "tty_set" => Function::new_typed_with_env(&mut store, env, tty_set::), + "getcwd" => Function::new_typed_with_env(&mut store, env, getcwd::), + "chdir" => Function::new_typed_with_env(&mut store, env, chdir::), + "callback_signal" => Function::new_typed_with_env(&mut store, env, callback_signal::), + "callback_thread" => Function::new_typed_with_env(&mut store, env, callback_thread::), + "callback_reactor" => Function::new_typed_with_env(&mut store, env, callback_reactor::), + "callback_thread_local_destroy" => Function::new_typed_with_env(&mut store, env, callback_thread_local_destroy::), + "thread_spawn" => Function::new_typed_with_env(&mut store, env, thread_spawn::), + "thread_local_create" => Function::new_typed_with_env(&mut store, env, thread_local_create::), + "thread_local_destroy" => Function::new_typed_with_env(&mut store, env, thread_local_destroy), + "thread_local_set" => Function::new_typed_with_env(&mut store, env, thread_local_set), + "thread_local_get" => Function::new_typed_with_env(&mut store, env, thread_local_get::), + "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), + "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id::), + "thread_signal" => Function::new_typed_with_env(&mut store, env, thread_signal), + "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), + "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism::), + "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), + "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), + "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), + "futex_wake" => Function::new_typed_with_env(&mut store, env, futex_wake::), + "futex_wake_all" => Function::new_typed_with_env(&mut store, env, futex_wake_all::), + "bus_open_local" => Function::new_typed_with_env(&mut store, env, bus_open_local::), + "bus_open_remote" => Function::new_typed_with_env(&mut store, env, bus_open_remote::), + "bus_close" => Function::new_typed_with_env(&mut store, env, bus_close), + "bus_call" => Function::new_typed_with_env(&mut store, env, bus_call::), + "bus_subcall" => Function::new_typed_with_env(&mut store, env, bus_subcall::), + "bus_poll" => Function::new_typed_with_env(&mut store, env, bus_poll::), + "call_reply" => Function::new_typed_with_env(&mut store, env, call_reply::), + "call_fault" => Function::new_typed_with_env(&mut store, env, call_fault), + "call_close" => Function::new_typed_with_env(&mut store, env, call_close), + "ws_connect" => Function::new_typed_with_env(&mut store, env, ws_connect::), + "http_request" => Function::new_typed_with_env(&mut store, env, http_request::), + "http_status" => Function::new_typed_with_env(&mut store, env, http_status::), + "port_bridge" => Function::new_typed_with_env(&mut store, env, port_bridge::), + "port_unbridge" => Function::new_typed_with_env(&mut store, env, port_unbridge), + "port_dhcp_acquire" => Function::new_typed_with_env(&mut store, env, port_dhcp_acquire), + "port_addr_add" => Function::new_typed_with_env(&mut store, env, port_addr_add::), + "port_addr_remove" => Function::new_typed_with_env(&mut store, env, port_addr_remove::), + "port_addr_clear" => Function::new_typed_with_env(&mut store, env, port_addr_clear), + "port_addr_list" => Function::new_typed_with_env(&mut store, env, port_addr_list::), + "port_mac" => Function::new_typed_with_env(&mut store, env, port_mac::), + "port_gateway_set" => Function::new_typed_with_env(&mut store, env, port_gateway_set::), + "port_route_add" => Function::new_typed_with_env(&mut store, env, port_route_add::), + "port_route_remove" => Function::new_typed_with_env(&mut store, env, port_route_remove::), + "port_route_clear" => Function::new_typed_with_env(&mut store, env, port_route_clear), + "port_route_list" => Function::new_typed_with_env(&mut store, env, port_route_list::), + "sock_status" => Function::new_typed_with_env(&mut store, env, sock_status::), + "sock_addr_local" => Function::new_typed_with_env(&mut store, env, sock_addr_local::), + "sock_addr_peer" => Function::new_typed_with_env(&mut store, env, sock_addr_peer::), + "sock_open" => Function::new_typed_with_env(&mut store, env, sock_open::), + "sock_set_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_set_opt_flag), + "sock_get_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_get_opt_flag::), + "sock_set_opt_time" => Function::new_typed_with_env(&mut store, env, sock_set_opt_time::), + "sock_get_opt_time" => Function::new_typed_with_env(&mut store, env, sock_get_opt_time::), + "sock_set_opt_size" => Function::new_typed_with_env(&mut store, env, sock_set_opt_size), + "sock_get_opt_size" => Function::new_typed_with_env(&mut store, env, sock_get_opt_size::), + "sock_join_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v4::), + "sock_leave_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v4::), + "sock_join_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v6::), + "sock_leave_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v6::), + "sock_bind" => Function::new_typed_with_env(&mut store, env, sock_bind::), + "sock_listen" => Function::new_typed_with_env(&mut store, env, sock_listen::), + "sock_accept" => Function::new_typed_with_env(&mut store, env, sock_accept::), + "sock_connect" => Function::new_typed_with_env(&mut store, env, sock_connect::), "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv::), + "sock_recv_from" => Function::new_typed_with_env(&mut store, env, sock_recv_from::), "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send::), + "sock_send_to" => Function::new_typed_with_env(&mut store, env, sock_send_to::), + "sock_send_file" => Function::new_typed_with_env(&mut store, env, sock_send_file::), "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), + "resolve" => Function::new_typed_with_env(&mut store, env, resolve::), }; namespace } + +fn wasix_exports_64( + mut store: &mut impl AsStoreMut, + env: &FunctionEnv, +) -> Exports +{ + use syscalls::*; + let namespace = namespace! { + "args_get" => Function::new_typed_with_env(&mut store, env, args_get::), + "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get::), + "clock_res_get" => Function::new_typed_with_env(&mut store, env, clock_res_get::), + "clock_time_get" => Function::new_typed_with_env(&mut store, env, clock_time_get::), + "clock_time_set" => Function::new_typed_with_env(&mut store, env, clock_time_set::), + "environ_get" => Function::new_typed_with_env(&mut store, env, environ_get::), + "environ_sizes_get" => Function::new_typed_with_env(&mut store, env, environ_sizes_get::), + "fd_advise" => Function::new_typed_with_env(&mut store, env, fd_advise), + "fd_allocate" => Function::new_typed_with_env(&mut store, env, fd_allocate), + "fd_close" => Function::new_typed_with_env(&mut store, env, fd_close), + "fd_datasync" => Function::new_typed_with_env(&mut store, env, fd_datasync), + "fd_fdstat_get" => Function::new_typed_with_env(&mut store, env, fd_fdstat_get::), + "fd_fdstat_set_flags" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_flags), + "fd_fdstat_set_rights" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_rights), + "fd_filestat_get" => Function::new_typed_with_env(&mut store, env, fd_filestat_get::), + "fd_filestat_set_size" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_size), + "fd_filestat_set_times" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_times), + "fd_pread" => Function::new_typed_with_env(&mut store, env, fd_pread::), + "fd_prestat_get" => Function::new_typed_with_env(&mut store, env, fd_prestat_get::), + "fd_prestat_dir_name" => Function::new_typed_with_env(&mut store, env, fd_prestat_dir_name::), + "fd_pwrite" => Function::new_typed_with_env(&mut store, env, fd_pwrite::), + "fd_read" => Function::new_typed_with_env(&mut store, env, fd_read::), + "fd_readdir" => Function::new_typed_with_env(&mut store, env, fd_readdir::), + "fd_renumber" => Function::new_typed_with_env(&mut store, env, fd_renumber), + "fd_dup" => Function::new_typed_with_env(&mut store, env, fd_dup::), + "fd_event" => Function::new_typed_with_env(&mut store, env, fd_event::), + "fd_seek" => Function::new_typed_with_env(&mut store, env, fd_seek::), + "fd_sync" => Function::new_typed_with_env(&mut store, env, fd_sync), + "fd_tell" => Function::new_typed_with_env(&mut store, env, fd_tell::), + "fd_write" => Function::new_typed_with_env(&mut store, env, fd_write::), + "fd_pipe" => Function::new_typed_with_env(&mut store, env, fd_pipe::), + "path_create_directory" => Function::new_typed_with_env(&mut store, env, path_create_directory::), + "path_filestat_get" => Function::new_typed_with_env(&mut store, env, path_filestat_get::), + "path_filestat_set_times" => Function::new_typed_with_env(&mut store, env, path_filestat_set_times::), + "path_link" => Function::new_typed_with_env(&mut store, env, path_link::), + "path_open" => Function::new_typed_with_env(&mut store, env, path_open::), + "path_readlink" => Function::new_typed_with_env(&mut store, env, path_readlink::), + "path_remove_directory" => Function::new_typed_with_env(&mut store, env, path_remove_directory::), + "path_rename" => Function::new_typed_with_env(&mut store, env, path_rename::), + "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink::), + "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file::), + "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff::), + "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), + "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), + "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), + "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), + "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), + "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), + "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), + "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), + "proc_id" => Function::new_typed_with_env(&mut store, env, proc_id::), + "proc_parent" => Function::new_typed_with_env(&mut store, env, proc_parent::), + "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), + "tty_get" => Function::new_typed_with_env(&mut store, env, tty_get::), + "tty_set" => Function::new_typed_with_env(&mut store, env, tty_set::), + "getcwd" => Function::new_typed_with_env(&mut store, env, getcwd::), + "chdir" => Function::new_typed_with_env(&mut store, env, chdir::), + "callback_signal" => Function::new_typed_with_env(&mut store, env, callback_signal::), + "callback_thread" => Function::new_typed_with_env(&mut store, env, callback_thread::), + "callback_reactor" => Function::new_typed_with_env(&mut store, env, callback_reactor::), + "callback_thread_local_destroy" => Function::new_typed_with_env(&mut store, env, callback_thread_local_destroy::), + "thread_spawn" => Function::new_typed_with_env(&mut store, env, thread_spawn::), + "thread_local_create" => Function::new_typed_with_env(&mut store, env, thread_local_create::), + "thread_local_destroy" => Function::new_typed_with_env(&mut store, env, thread_local_destroy), + "thread_local_set" => Function::new_typed_with_env(&mut store, env, thread_local_set), + "thread_local_get" => Function::new_typed_with_env(&mut store, env, thread_local_get::), + "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), + "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id::), + "thread_signal" => Function::new_typed_with_env(&mut store, env, thread_signal), + "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), + "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism::), + "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), + "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), + "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), + "futex_wake" => Function::new_typed_with_env(&mut store, env, futex_wake::), + "futex_wake_all" => Function::new_typed_with_env(&mut store, env, futex_wake_all::), + "bus_open_local" => Function::new_typed_with_env(&mut store, env, bus_open_local::), + "bus_open_remote" => Function::new_typed_with_env(&mut store, env, bus_open_remote::), + "bus_close" => Function::new_typed_with_env(&mut store, env, bus_close), + "bus_call" => Function::new_typed_with_env(&mut store, env, bus_call::), + "bus_subcall" => Function::new_typed_with_env(&mut store, env, bus_subcall::), + "bus_poll" => Function::new_typed_with_env(&mut store, env, bus_poll::), + "call_reply" => Function::new_typed_with_env(&mut store, env, call_reply::), + "call_fault" => Function::new_typed_with_env(&mut store, env, call_fault), + "call_close" => Function::new_typed_with_env(&mut store, env, call_close), + "ws_connect" => Function::new_typed_with_env(&mut store, env, ws_connect::), + "http_request" => Function::new_typed_with_env(&mut store, env, http_request::), + "http_status" => Function::new_typed_with_env(&mut store, env, http_status::), + "port_bridge" => Function::new_typed_with_env(&mut store, env, port_bridge::), + "port_unbridge" => Function::new_typed_with_env(&mut store, env, port_unbridge), + "port_dhcp_acquire" => Function::new_typed_with_env(&mut store, env, port_dhcp_acquire), + "port_addr_add" => Function::new_typed_with_env(&mut store, env, port_addr_add::), + "port_addr_remove" => Function::new_typed_with_env(&mut store, env, port_addr_remove::), + "port_addr_clear" => Function::new_typed_with_env(&mut store, env, port_addr_clear), + "port_addr_list" => Function::new_typed_with_env(&mut store, env, port_addr_list::), + "port_mac" => Function::new_typed_with_env(&mut store, env, port_mac::), + "port_gateway_set" => Function::new_typed_with_env(&mut store, env, port_gateway_set::), + "port_route_add" => Function::new_typed_with_env(&mut store, env, port_route_add::), + "port_route_remove" => Function::new_typed_with_env(&mut store, env, port_route_remove::), + "port_route_clear" => Function::new_typed_with_env(&mut store, env, port_route_clear), + "port_route_list" => Function::new_typed_with_env(&mut store, env, port_route_list::), + "sock_status" => Function::new_typed_with_env(&mut store, env, sock_status::), + "sock_addr_local" => Function::new_typed_with_env(&mut store, env, sock_addr_local::), + "sock_addr_peer" => Function::new_typed_with_env(&mut store, env, sock_addr_peer::), + "sock_open" => Function::new_typed_with_env(&mut store, env, sock_open::), + "sock_set_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_set_opt_flag), + "sock_get_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_get_opt_flag::), + "sock_set_opt_time" => Function::new_typed_with_env(&mut store, env, sock_set_opt_time::), + "sock_get_opt_time" => Function::new_typed_with_env(&mut store, env, sock_get_opt_time::), + "sock_set_opt_size" => Function::new_typed_with_env(&mut store, env, sock_set_opt_size), + "sock_get_opt_size" => Function::new_typed_with_env(&mut store, env, sock_get_opt_size::), + "sock_join_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v4::), + "sock_leave_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v4::), + "sock_join_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v6::), + "sock_leave_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v6::), + "sock_bind" => Function::new_typed_with_env(&mut store, env, sock_bind::), + "sock_listen" => Function::new_typed_with_env(&mut store, env, sock_listen::), + "sock_accept" => Function::new_typed_with_env(&mut store, env, sock_accept::), + "sock_connect" => Function::new_typed_with_env(&mut store, env, sock_connect::), + "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv::), + "sock_recv_from" => Function::new_typed_with_env(&mut store, env, sock_recv_from::), + "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send::), + "sock_send_to" => Function::new_typed_with_env(&mut store, env, sock_send_to::), + "sock_send_file" => Function::new_typed_with_env(&mut store, env, sock_send_file::), + "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), + "resolve" => Function::new_typed_with_env(&mut store, env, resolve::), + }; + namespace +} + pub fn import_object_for_all_wasi_versions( store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Imports { - let wasi_unstable_exports = wasi_unstable_exports(store, env); - let wasi_snapshot_preview1_exports = wasi_snapshot_preview1_exports(store, env); + let exports_wasi_unstable = wasi_unstable_exports(store, env); + let exports_wasi_snapshot_preview1 = wasi_snapshot_preview1_exports(store, env); + let exports_wasix_32v1 = wasix_exports_32(store, env); + let exports_wasix_64v1 = wasix_exports_64(store, env); imports! { - "wasi_unstable" => wasi_unstable_exports, - "wasi_snapshot_preview1" => wasi_snapshot_preview1_exports, + "wasi_unstable" => exports_wasi_unstable, + "wasi_snapshot_preview1" => exports_wasi_snapshot_preview1, + "wasix_32v1" => exports_wasix_32v1, + "wasix_64v1" => exports_wasix_64v1, } } @@ -561,9 +1382,9 @@ fn generate_import_object_snapshot0( store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Imports { - let wasi_unstable_exports = wasi_unstable_exports(store, env); + let exports_unstable = wasi_unstable_exports(store, env); imports! { - "wasi_unstable" => wasi_unstable_exports + "wasi_unstable" => exports_unstable } } @@ -571,248 +1392,32 @@ fn generate_import_object_snapshot1( store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Imports { - let wasi_snapshot_preview1_exports = wasi_snapshot_preview1_exports(store, env); + let exports_wasi_snapshot_preview1 = wasi_snapshot_preview1_exports(store, env); imports! { - "wasi_snapshot_preview1" => wasi_snapshot_preview1_exports + "wasi_snapshot_preview1" => exports_wasi_snapshot_preview1 } } /// Combines a state generating function with the import list for snapshot 1 #[cfg(feature = "wasix")] fn generate_import_object_wasix32_v1( - mut store: &mut impl AsStoreMut, + store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Imports { - use self::wasix32::*; + let exports_wasix_32v1 = wasix_exports_32(store, env); imports! { - "wasix_32v1" => { - "args_get" => Function::new_typed_with_env(&mut store, env, args_get), - "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get), - "clock_res_get" => Function::new_typed_with_env(&mut store, env, clock_res_get), - "clock_time_get" => Function::new_typed_with_env(&mut store, env, clock_time_get), - "environ_get" => Function::new_typed_with_env(&mut store, env, environ_get), - "environ_sizes_get" => Function::new_typed_with_env(&mut store, env, environ_sizes_get), - "fd_advise" => Function::new_typed_with_env(&mut store, env, fd_advise), - "fd_allocate" => Function::new_typed_with_env(&mut store, env, fd_allocate), - "fd_close" => Function::new_typed_with_env(&mut store, env, fd_close), - "fd_datasync" => Function::new_typed_with_env(&mut store, env, fd_datasync), - "fd_fdstat_get" => Function::new_typed_with_env(&mut store, env, fd_fdstat_get), - "fd_fdstat_set_flags" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_flags), - "fd_fdstat_set_rights" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_rights), - "fd_filestat_get" => Function::new_typed_with_env(&mut store, env, fd_filestat_get), - "fd_filestat_set_size" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_size), - "fd_filestat_set_times" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_times), - "fd_pread" => Function::new_typed_with_env(&mut store, env, fd_pread), - "fd_prestat_get" => Function::new_typed_with_env(&mut store, env, fd_prestat_get), - "fd_prestat_dir_name" => Function::new_typed_with_env(&mut store, env, fd_prestat_dir_name), - "fd_pwrite" => Function::new_typed_with_env(&mut store, env, fd_pwrite), - "fd_read" => Function::new_typed_with_env(&mut store, env, fd_read), - "fd_readdir" => Function::new_typed_with_env(&mut store, env, fd_readdir), - "fd_renumber" => Function::new_typed_with_env(&mut store, env, fd_renumber), - "fd_dup" => Function::new_typed_with_env(&mut store, env, fd_dup), - "fd_event" => Function::new_typed_with_env(&mut store, env, fd_event), - "fd_seek" => Function::new_typed_with_env(&mut store, env, fd_seek), - "fd_sync" => Function::new_typed_with_env(&mut store, env, fd_sync), - "fd_tell" => Function::new_typed_with_env(&mut store, env, fd_tell), - "fd_write" => Function::new_typed_with_env(&mut store, env, fd_write), - "fd_pipe" => Function::new_typed_with_env(&mut store, env, fd_pipe), - "path_create_directory" => Function::new_typed_with_env(&mut store, env, path_create_directory), - "path_filestat_get" => Function::new_typed_with_env(&mut store, env, path_filestat_get), - "path_filestat_set_times" => Function::new_typed_with_env(&mut store, env, path_filestat_set_times), - "path_link" => Function::new_typed_with_env(&mut store, env, path_link), - "path_open" => Function::new_typed_with_env(&mut store, env, path_open), - "path_readlink" => Function::new_typed_with_env(&mut store, env, path_readlink), - "path_remove_directory" => Function::new_typed_with_env(&mut store, env, path_remove_directory), - "path_rename" => Function::new_typed_with_env(&mut store, env, path_rename), - "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink), - "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file), - "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit), - "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), - "random_get" => Function::new_typed_with_env(&mut store, env, random_get), - "tty_get" => Function::new_typed_with_env(&mut store, env, tty_get), - "tty_set" => Function::new_typed_with_env(&mut store, env, tty_set), - "getcwd" => Function::new_typed_with_env(&mut store, env, getcwd), - "chdir" => Function::new_typed_with_env(&mut store, env, chdir), - "thread_spawn" => Function::new_typed_with_env(&mut store, env, thread_spawn), - "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), - "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id), - "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), - "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism), - "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), - "getpid" => Function::new_typed_with_env(&mut store, env, getpid), - "process_spawn" => Function::new_typed_with_env(&mut store, env, process_spawn), - "bus_open_local" => Function::new_typed_with_env(&mut store, env, bus_open_local), - "bus_open_remote" => Function::new_typed_with_env(&mut store, env, bus_open_remote), - "bus_close" => Function::new_typed_with_env(&mut store, env, bus_close), - "bus_call" => Function::new_typed_with_env(&mut store, env, bus_call), - "bus_subcall" => Function::new_typed_with_env(&mut store, env, bus_subcall), - "bus_poll" => Function::new_typed_with_env(&mut store, env, bus_poll), - "call_reply" => Function::new_typed_with_env(&mut store, env, call_reply), - "call_fault" => Function::new_typed_with_env(&mut store, env, call_fault), - "call_close" => Function::new_typed_with_env(&mut store, env, call_close), - "ws_connect" => Function::new_typed_with_env(&mut store, env, ws_connect), - "http_request" => Function::new_typed_with_env(&mut store, env, http_request), - "http_status" => Function::new_typed_with_env(&mut store, env, http_status), - "port_bridge" => Function::new_typed_with_env(&mut store, env, port_bridge), - "port_unbridge" => Function::new_typed_with_env(&mut store, env, port_unbridge), - "port_dhcp_acquire" => Function::new_typed_with_env(&mut store, env, port_dhcp_acquire), - "port_addr_add" => Function::new_typed_with_env(&mut store, env, port_addr_add), - "port_addr_remove" => Function::new_typed_with_env(&mut store, env, port_addr_remove), - "port_addr_clear" => Function::new_typed_with_env(&mut store, env, port_addr_clear), - "port_addr_list" => Function::new_typed_with_env(&mut store, env, port_addr_list), - "port_mac" => Function::new_typed_with_env(&mut store, env, port_mac), - "port_gateway_set" => Function::new_typed_with_env(&mut store, env, port_gateway_set), - "port_route_add" => Function::new_typed_with_env(&mut store, env, port_route_add), - "port_route_remove" => Function::new_typed_with_env(&mut store, env, port_route_remove), - "port_route_clear" => Function::new_typed_with_env(&mut store, env, port_route_clear), - "port_route_list" => Function::new_typed_with_env(&mut store, env, port_route_list), - "sock_status" => Function::new_typed_with_env(&mut store, env, sock_status), - "sock_addr_local" => Function::new_typed_with_env(&mut store, env, sock_addr_local), - "sock_addr_peer" => Function::new_typed_with_env(&mut store, env, sock_addr_peer), - "sock_open" => Function::new_typed_with_env(&mut store, env, sock_open), - "sock_set_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_set_opt_flag), - "sock_get_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_get_opt_flag), - "sock_set_opt_time" => Function::new_typed_with_env(&mut store, env, sock_set_opt_time), - "sock_get_opt_time" => Function::new_typed_with_env(&mut store, env, sock_get_opt_time), - "sock_set_opt_size" => Function::new_typed_with_env(&mut store, env, sock_set_opt_size), - "sock_get_opt_size" => Function::new_typed_with_env(&mut store, env, sock_get_opt_size), - "sock_join_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v4), - "sock_leave_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v4), - "sock_join_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v6), - "sock_leave_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v6), - "sock_bind" => Function::new_typed_with_env(&mut store, env, sock_bind), - "sock_listen" => Function::new_typed_with_env(&mut store, env, sock_listen), - "sock_accept" => Function::new_typed_with_env(&mut store, env, sock_accept), - "sock_connect" => Function::new_typed_with_env(&mut store, env, sock_connect), - "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv), - "sock_recv_from" => Function::new_typed_with_env(&mut store, env, sock_recv_from), - "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send), - "sock_send_to" => Function::new_typed_with_env(&mut store, env, sock_send_to), - "sock_send_file" => Function::new_typed_with_env(&mut store, env, sock_send_file), - "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), - "resolve" => Function::new_typed_with_env(&mut store, env, resolve), - } + "wasix_32v1" => exports_wasix_32v1 } } #[cfg(feature = "wasix")] fn generate_import_object_wasix64_v1( - mut store: &mut impl AsStoreMut, + store: &mut impl AsStoreMut, env: &FunctionEnv, ) -> Imports { - use self::wasix64::*; + let exports_wasix_64v1 = wasix_exports_64(store, env); imports! { - "wasix_64v1" => { - "args_get" => Function::new_typed_with_env(&mut store, env, args_get), - "args_sizes_get" => Function::new_typed_with_env(&mut store, env, args_sizes_get), - "clock_res_get" => Function::new_typed_with_env(&mut store, env, clock_res_get), - "clock_time_get" => Function::new_typed_with_env(&mut store, env, clock_time_get), - "environ_get" => Function::new_typed_with_env(&mut store, env, environ_get), - "environ_sizes_get" => Function::new_typed_with_env(&mut store, env, environ_sizes_get), - "fd_advise" => Function::new_typed_with_env(&mut store, env, fd_advise), - "fd_allocate" => Function::new_typed_with_env(&mut store, env, fd_allocate), - "fd_close" => Function::new_typed_with_env(&mut store, env, fd_close), - "fd_datasync" => Function::new_typed_with_env(&mut store, env, fd_datasync), - "fd_fdstat_get" => Function::new_typed_with_env(&mut store, env, fd_fdstat_get), - "fd_fdstat_set_flags" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_flags), - "fd_fdstat_set_rights" => Function::new_typed_with_env(&mut store, env, fd_fdstat_set_rights), - "fd_filestat_get" => Function::new_typed_with_env(&mut store, env, fd_filestat_get), - "fd_filestat_set_size" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_size), - "fd_filestat_set_times" => Function::new_typed_with_env(&mut store, env, fd_filestat_set_times), - "fd_pread" => Function::new_typed_with_env(&mut store, env, fd_pread), - "fd_prestat_get" => Function::new_typed_with_env(&mut store, env, fd_prestat_get), - "fd_prestat_dir_name" => Function::new_typed_with_env(&mut store, env, fd_prestat_dir_name), - "fd_pwrite" => Function::new_typed_with_env(&mut store, env, fd_pwrite), - "fd_read" => Function::new_typed_with_env(&mut store, env, fd_read), - "fd_readdir" => Function::new_typed_with_env(&mut store, env, fd_readdir), - "fd_renumber" => Function::new_typed_with_env(&mut store, env, fd_renumber), - "fd_dup" => Function::new_typed_with_env(&mut store, env, fd_dup), - "fd_event" => Function::new_typed_with_env(&mut store, env, fd_event), - "fd_seek" => Function::new_typed_with_env(&mut store, env, fd_seek), - "fd_sync" => Function::new_typed_with_env(&mut store, env, fd_sync), - "fd_tell" => Function::new_typed_with_env(&mut store, env, fd_tell), - "fd_write" => Function::new_typed_with_env(&mut store, env, fd_write), - "fd_pipe" => Function::new_typed_with_env(&mut store, env, fd_pipe), - "path_create_directory" => Function::new_typed_with_env(&mut store, env, path_create_directory), - "path_filestat_get" => Function::new_typed_with_env(&mut store, env, path_filestat_get), - "path_filestat_set_times" => Function::new_typed_with_env(&mut store, env, path_filestat_set_times), - "path_link" => Function::new_typed_with_env(&mut store, env, path_link), - "path_open" => Function::new_typed_with_env(&mut store, env, path_open), - "path_readlink" => Function::new_typed_with_env(&mut store, env, path_readlink), - "path_remove_directory" => Function::new_typed_with_env(&mut store, env, path_remove_directory), - "path_rename" => Function::new_typed_with_env(&mut store, env, path_rename), - "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink), - "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file), - "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit), - "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), - "random_get" => Function::new_typed_with_env(&mut store, env, random_get), - "tty_get" => Function::new_typed_with_env(&mut store, env, tty_get), - "tty_set" => Function::new_typed_with_env(&mut store, env, tty_set), - "getcwd" => Function::new_typed_with_env(&mut store, env, getcwd), - "chdir" => Function::new_typed_with_env(&mut store, env, chdir), - "thread_spawn" => Function::new_typed_with_env(&mut store, env, thread_spawn), - "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), - "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id), - "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), - "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism), - "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), - "getpid" => Function::new_typed_with_env(&mut store, env, getpid), - "process_spawn" => Function::new_typed_with_env(&mut store, env, process_spawn), - "bus_open_local" => Function::new_typed_with_env(&mut store, env, bus_open_local), - "bus_open_remote" => Function::new_typed_with_env(&mut store, env, bus_open_remote), - "bus_close" => Function::new_typed_with_env(&mut store, env, bus_close), - "bus_call" => Function::new_typed_with_env(&mut store, env, bus_call), - "bus_subcall" => Function::new_typed_with_env(&mut store, env, bus_subcall), - "bus_poll" => Function::new_typed_with_env(&mut store, env, bus_poll), - "call_reply" => Function::new_typed_with_env(&mut store, env, call_reply), - "call_fault" => Function::new_typed_with_env(&mut store, env, call_fault), - "call_close" => Function::new_typed_with_env(&mut store, env, call_close), - "ws_connect" => Function::new_typed_with_env(&mut store, env, ws_connect), - "http_request" => Function::new_typed_with_env(&mut store, env, http_request), - "http_status" => Function::new_typed_with_env(&mut store, env, http_status), - "port_bridge" => Function::new_typed_with_env(&mut store, env, port_bridge), - "port_unbridge" => Function::new_typed_with_env(&mut store, env, port_unbridge), - "port_dhcp_acquire" => Function::new_typed_with_env(&mut store, env, port_dhcp_acquire), - "port_addr_add" => Function::new_typed_with_env(&mut store, env, port_addr_add), - "port_addr_remove" => Function::new_typed_with_env(&mut store, env, port_addr_remove), - "port_addr_clear" => Function::new_typed_with_env(&mut store, env, port_addr_clear), - "port_addr_list" => Function::new_typed_with_env(&mut store, env, port_addr_list), - "port_mac" => Function::new_typed_with_env(&mut store, env, port_mac), - "port_gateway_set" => Function::new_typed_with_env(&mut store, env, port_gateway_set), - "port_route_add" => Function::new_typed_with_env(&mut store, env, port_route_add), - "port_route_remove" => Function::new_typed_with_env(&mut store, env, port_route_remove), - "port_route_clear" => Function::new_typed_with_env(&mut store, env, port_route_clear), - "port_route_list" => Function::new_typed_with_env(&mut store, env, port_route_list), - "sock_status" => Function::new_typed_with_env(&mut store, env, sock_status), - "sock_addr_local" => Function::new_typed_with_env(&mut store, env, sock_addr_local), - "sock_addr_peer" => Function::new_typed_with_env(&mut store, env, sock_addr_peer), - "sock_open" => Function::new_typed_with_env(&mut store, env, sock_open), - "sock_set_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_set_opt_flag), - "sock_get_opt_flag" => Function::new_typed_with_env(&mut store, env, sock_get_opt_flag), - "sock_set_opt_time" => Function::new_typed_with_env(&mut store, env, sock_set_opt_time), - "sock_get_opt_time" => Function::new_typed_with_env(&mut store, env, sock_get_opt_time), - "sock_set_opt_size" => Function::new_typed_with_env(&mut store, env, sock_set_opt_size), - "sock_get_opt_size" => Function::new_typed_with_env(&mut store, env, sock_get_opt_size), - "sock_join_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v4), - "sock_leave_multicast_v4" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v4), - "sock_join_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_join_multicast_v6), - "sock_leave_multicast_v6" => Function::new_typed_with_env(&mut store, env, sock_leave_multicast_v6), - "sock_bind" => Function::new_typed_with_env(&mut store, env, sock_bind), - "sock_listen" => Function::new_typed_with_env(&mut store, env, sock_listen), - "sock_accept" => Function::new_typed_with_env(&mut store, env, sock_accept), - "sock_connect" => Function::new_typed_with_env(&mut store, env, sock_connect), - "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv), - "sock_recv_from" => Function::new_typed_with_env(&mut store, env, sock_recv_from), - "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send), - "sock_send_to" => Function::new_typed_with_env(&mut store, env, sock_send_to), - "sock_send_file" => Function::new_typed_with_env(&mut store, env, sock_send_file), - "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), - "resolve" => Function::new_typed_with_env(&mut store, env, resolve), - } + "wasix_64v1" => exports_wasix_64v1 } } diff --git a/lib/wasi/src/macros.rs b/lib/wasi/src/macros.rs index 450e88cd637..0d819b3b5e4 100644 --- a/lib/wasi/src/macros.rs +++ b/lib/wasi/src/macros.rs @@ -7,7 +7,6 @@ macro_rules! wasi_try { let res: Result<_, crate::syscalls::types::wasi::Errno> = $expr; match res { Ok(val) => { - tracing::trace!("wasi::wasi_try::val: {:?}", val); val } Err(err) => { @@ -25,7 +24,6 @@ macro_rules! wasi_try_ok { let res: Result<_, crate::syscalls::types::wasi::Errno> = $expr; match res { Ok(val) => { - tracing::trace!("wasi::wasi_try_ok::val: {:?}", val); val } Err(err) => { @@ -39,7 +37,6 @@ macro_rules! wasi_try_ok { let res: Result<_, crate::syscalls::types::wasi::Errno> = $expr; match res { Ok(val) => { - tracing::trace!("wasi::wasi_try_ok::val: {:?}", val); val } Err(err) => { @@ -60,7 +57,6 @@ macro_rules! wasi_try_bus { let res: Result<_, crate::syscalls::types::wasi::BusErrno> = $expr; match res { Ok(val) => { - tracing::trace!("wasi::wasi_try_bus::val: {:?}", val); val } Err(err) => { @@ -71,7 +67,25 @@ macro_rules! wasi_try_bus { }}; } -/// Like `wasi_try` but converts a `MemoryAccessError` to a `wasi::Errno`. +/// Like the `try!` macro or `?` syntax: returns the value if the computation +/// succeeded or returns the error value. +macro_rules! wasi_try_bus_ok { + ($expr:expr) => {{ + let res: Result<_, crate::syscalls::types::__bus_errno_t> = $expr; + match res { + Ok(val) => { + //tracing::trace!("wasi::wasi_try_bus::val: {:?}", val); + val + } + Err(err) => { + tracing::debug!("wasi::wasi_try_bus::err: {:?}", err); + return Ok(err); + } + } + }}; +} + +/// Like `wasi_try` but converts a `MemoryAccessError` to a __wasi_errno_t`. macro_rules! wasi_try_mem { ($expr:expr) => {{ wasi_try!($expr.map_err($crate::mem_error_to_wasi)) @@ -85,7 +99,14 @@ macro_rules! wasi_try_mem_bus { }}; } -/// Like `wasi_try` but converts a `MemoryAccessError` to a `wasi::Errno`. +/// Like `wasi_try` but converts a `MemoryAccessError` to a __bus_errno_t`. +macro_rules! wasi_try_mem_bus_ok { + ($expr:expr) => {{ + wasi_try_bus_ok!($expr.map_err($crate::mem_error_to_bus)) + }}; +} + +/// Like `wasi_try` but converts a `MemoryAccessError` to a __wasi_errno_t`. macro_rules! wasi_try_mem_ok { ($expr:expr) => {{ wasi_try_ok!($expr.map_err($crate::mem_error_to_wasi)) @@ -108,3 +129,9 @@ macro_rules! get_input_str_bus { wasi_try_mem_bus!($data.read_utf8_string($memory, $len)) }}; } + +macro_rules! get_input_str_bus_ok { + ($memory:expr, $data:expr, $len:expr) => {{ + wasi_try_mem_bus_ok!($data.read_utf8_string($memory, $len)) + }}; +} diff --git a/lib/wasi/src/os/cconst.rs b/lib/wasi/src/os/cconst.rs new file mode 100644 index 00000000000..9bec0625d34 --- /dev/null +++ b/lib/wasi/src/os/cconst.rs @@ -0,0 +1,82 @@ +#![allow(dead_code)] +pub struct ConsoleConst {} + +impl ConsoleConst { + pub const TERM_KEY_ENTER: u32 = 13; + pub const TERM_KEY_BACKSPACE: u32 = 8; + pub const TERM_KEY_INSERT: u32 = 45; + pub const TERM_KEY_DEL: u32 = 46; + pub const TERM_KEY_TAB: u32 = 9; + pub const TERM_KEY_HOME: u32 = 36; + pub const TERM_KEY_END: u32 = 35; + pub const TERM_KEY_PAGE_UP: u32 = 33; + pub const TERM_KEY_PAGE_DOWN: u32 = 34; + pub const TERM_KEY_LEFT_ARROW: u32 = 37; + pub const TERM_KEY_UP_ARROW: u32 = 38; + pub const TERM_KEY_RIGHT_ARROW: u32 = 39; + pub const TERM_KEY_DOWN_ARROW: u32 = 40; + pub const TERM_KEY_C: u32 = 67; + pub const TERM_KEY_L: u32 = 76; + pub const TERM_KEY_F1: u32 = 112; + pub const TERM_KEY_F2: u32 = 113; + pub const TERM_KEY_F3: u32 = 114; + pub const TERM_KEY_F4: u32 = 115; + pub const TERM_KEY_F5: u32 = 116; + pub const TERM_KEY_F6: u32 = 117; + pub const TERM_KEY_F7: u32 = 118; + pub const TERM_KEY_F8: u32 = 119; + pub const TERM_KEY_F9: u32 = 120; + pub const TERM_KEY_F10: u32 = 121; + pub const TERM_KEY_F11: u32 = 122; + pub const TERM_KEY_F12: u32 = 123; + + pub const TERM_CURSOR_UP: &'static str = "\x1b[A"; + pub const TERM_CURSOR_DOWN: &'static str = "\x1b[B"; + pub const TERM_CURSOR_RIGHT: &'static str = "\x1b[C"; + pub const TERM_CURSOR_LEFT: &'static str = "\x1b[D"; + + pub const TERM_DELETE_LINE: &'static str = "\x1b[2K\r"; + pub const TERM_DELETE_RIGHT: &'static str = "\x1b[0K\r"; + pub const TERM_DELETE_LEFT: &'static str = "\x1b[1K\r"; + pub const TERM_DELETE_BELOW: &'static str = "\x1b[0J\r"; + pub const TERM_DELETE_ABOVE: &'static str = "\x1b[1J\r"; + pub const TERM_DELETE_ALL: &'static str = "\x1b[2J\r"; + pub const TERM_DELETE_SAVED: &'static str = "\x1b[3J\r"; + + pub const TERM_CURSOR_SAVE: &'static str = "\x1b[s"; + pub const TERM_CURSOR_RESTORE: &'static str = "\x1b[u"; + + pub const TERM_WRAPAROUND: &'static str = "\x1b[?7h"; + pub const TERM_REVERSE_WRAPAROUND: &'static str = "\x1b[?45h"; + + pub const TERM_NO_WRAPAROUND: &'static str = "\x1b[?7l"; + pub const TERM_NO_REVERSE_WRAPAROUND: &'static str = "\x1b[?45l"; + + pub const COL_RESET: &'static str = "\x1B[0m"; + pub const COL_BLACK: &'static str = "\x1B[0;30m"; + pub const COL_GRAY: &'static str = "\x1B[1;30m"; + pub const COL_RED: &'static str = "\x1B[0;31m"; + pub const COL_LIGHT_RED: &'static str = "\x1B[1;31m"; + pub const COL_GREEN: &'static str = "\x1B[0;32m"; + pub const COL_LIGHT_GREEN: &'static str = "\x1B[1;32m"; + pub const COL_BROWN: &'static str = "\x1B[0;33m"; + pub const COL_YELLOW: &'static str = "\x1B[1;33m"; + pub const COL_BLUE: &'static str = "\x1B[0;34m"; + pub const COL_LIGHT_BLUE: &'static str = "\x1B[1;34m"; + pub const COL_PURPLE: &'static str = "\x1B[0;35m"; + pub const COL_LIGHT_PURPLE: &'static str = "\x1B[1;35m"; + pub const COL_CYAN: &'static str = "\x1B[0;36m"; + pub const COL_LIGHT_CYAN: &'static str = "\x1B[1;36m"; + pub const COL_LIGHT_GRAY: &'static str = "\x1B[0;37m"; + pub const COL_WHITE: &'static str = "\x1B[1;37m"; + + pub const WELCOME_LARGE: &'static str = include_str!("txt/welcome_large.txt"); + pub const WELCOME_MEDIUM: &'static str = include_str!("txt/welcome_medium.txt"); + pub const WELCOME_SMALL: &'static str = include_str!("txt/welcome_small.txt"); + + pub const ABOUT: &'static str = include_str!("txt/about.md"); + pub const ABOUT_DEPLOY: &'static str = include_str!("txt/about_deploy.md"); + pub const ABOUT_WASMER: &'static str = include_str!("txt/about_wasmer.md"); + pub const HELP: &'static str = include_str!("txt/help.md"); + pub const BAD_WORKER: &'static str = include_str!("txt/bad_worker.md"); +} diff --git a/lib/wasi/src/os/common.rs b/lib/wasi/src/os/common.rs new file mode 100644 index 00000000000..0c453b304db --- /dev/null +++ b/lib/wasi/src/os/common.rs @@ -0,0 +1,18 @@ +pub type Pid = u32; + +pub const MAX_MPSC: usize = std::usize::MAX >> 3; + +pub fn is_mobile(user_agent: &str) -> bool { + user_agent.contains("Android") + || user_agent.contains("BlackBerry") + || user_agent.contains("iPhone") + || user_agent.contains("iPad") + || user_agent.contains("iPod") + || user_agent.contains("Open Mini") + || user_agent.contains("IEMobile") + || user_agent.contains("WPDesktop") +} + +pub fn is_ssh(user_agent: &str) -> bool { + user_agent.contains("ssh") +} diff --git a/lib/wasi/src/os/console.rs b/lib/wasi/src/os/console.rs new file mode 100644 index 00000000000..c4163ee8eff --- /dev/null +++ b/lib/wasi/src/os/console.rs @@ -0,0 +1,243 @@ +#![allow(unused_imports)] +#![allow(dead_code)] +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; +use std::io::Write; +use std::path::Path; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::atomic::AtomicBool; +use derivative::*; +use linked_hash_set::LinkedHashSet; +use tokio::sync::mpsc; +use tokio::sync::RwLock; +#[allow(unused_imports, dead_code)] +use tracing::{debug, error, info, trace, warn}; +#[cfg(feature = "sys")] +use wasmer::Engine; +use wasmer_vbus::{SpawnOptionsConfig, BusSpawnedProcess}; +use wasmer_vfs::FileSystem; + +use crate::{WasiControlPlane, WasiEnv, WasiProcess, WasiState}; +use crate::WasiRuntimeImplementation; +use crate::bin_factory::BinFactory; +use crate::bin_factory::CachedCompiledModules; +use crate::bin_factory::spawn_exec; +use crate::WasiPipe; +use crate::runtime::RuntimeStdout; +use crate::runtime::RuntimeStderr; + +use super::common::*; +use super::posix_err; +use super::cconst::ConsoleConst; + +//pub const DEFAULT_BOOT_WEBC: &'static str = "sharrattj/bash"; +pub const DEFAULT_BOOT_WEBC: &'static str = "sharrattj/dash"; +//pub const DEFAULT_BOOT_USES: [&'static str; 2] = [ "sharrattj/coreutils", "sharrattj/catsay" ]; +pub const DEFAULT_BOOT_USES: [&'static str; 0] = [ ]; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Console { + user_agent: Option, + boot_cmd: String, + uses: LinkedHashSet, + is_mobile: bool, + is_ssh: bool, + whitelabel: bool, + token: Option, + no_welcome: bool, + prompt: String, + env: HashMap, + runtime: Arc, + compiled_modules: Arc, + stdin: Option, +} + +impl Console { + pub fn new( + runtime: Arc, + compiled_modules: Arc, + ) -> Self { + let mut uses = DEFAULT_BOOT_USES.iter().map(|a| a.to_string()).collect::>(); + let prog = DEFAULT_BOOT_WEBC.split_once(" ").map(|a| a.1).unwrap_or(DEFAULT_BOOT_WEBC); + uses.insert(prog.to_string()); + Self { + boot_cmd: DEFAULT_BOOT_WEBC.to_string(), + uses, + is_mobile: false, + is_ssh: false, + user_agent: None, + whitelabel: false, + token: None, + no_welcome: false, + env: HashMap::new(), + runtime, + prompt: "wasmer.sh".to_string(), + compiled_modules, + stdin: None, + } + } + + pub fn with_stdin(mut self, stdin: WasiPipe) -> Self { + self.stdin = Some(stdin); + self + } + + pub fn with_prompt(mut self, prompt: String) -> Self { + self.prompt = prompt; + self + } + + pub fn with_boot_cmd(mut self, cmd: String) -> Self { + let prog = cmd.split_once(" ").map(|a| a.0).unwrap_or(cmd.as_str()); + self.uses.insert(prog.to_string()); + self.boot_cmd = cmd; + self + } + + pub fn with_uses(mut self, uses: Vec) -> Self { + self.uses = uses.into_iter().collect(); + self + } + + pub fn with_env(mut self, env: HashMap) -> Self { + self.env = env; + self + } + + pub fn with_user_agent(mut self, user_agent: &str) -> Self { + self.is_mobile = is_mobile(user_agent); + self.is_ssh = is_ssh(user_agent); + self.user_agent = Some(user_agent.to_string()); + self + } + + pub fn with_no_welcome(mut self, no_welcome: bool) -> Self { + self.no_welcome = no_welcome; + self + } + + pub fn with_token(mut self, token: String) -> Self { + self.token = Some(token); + self + } + + pub fn run(&mut self) -> wasmer_vbus::Result + { + // Extract the program name from the arguments + let empty_args: Vec<&[u8]> = Vec::new(); + let (webc, prog, args) = match self.boot_cmd.split_once(" ") { + Some((webc, args)) => { + ( + webc, + webc.split_once("/").map(|a| a.1).unwrap_or(webc), + args.split(" ").map(|a| a.as_bytes()).collect::>() + ) + }, + None => { + ( + self.boot_cmd.as_str(), + self.boot_cmd.split_once("/").map(|a| a.1).unwrap_or(self.boot_cmd.as_str()), + empty_args + ) + } + }; + let envs = self.env.clone(); + + // Display the welcome message + if self.whitelabel == false && self.no_welcome == false { + self.draw_welcome(); + } + + // Build a new store that will be passed to the thread + let store = self.compiled_modules.new_store(); + + // Create the control plane, process and thread + let control_plane = WasiControlPlane::default(); + let process = control_plane.new_process(); + let thread = process.new_thread(); + + // Create the state + let mut state = WasiState::new(prog); + if let Some(stdin) = self.stdin.take() { + state.stdin(Box::new(stdin)); + } + + // Open the root + state + .args(args.iter()) + .envs(envs.iter()) + .preopen_dir(Path::new("/")) + .unwrap() + .map_dir(".", "/") + .unwrap(); + + let state = state + .stdout(Box::new(RuntimeStdout::new(self.runtime.clone()))) + .stderr(Box::new(RuntimeStderr::new(self.runtime.clone()))) + .build() + .unwrap(); + + // Create the environment + let env = WasiEnv::new_ext( + Arc::new(state), + self.compiled_modules.clone(), + process, + thread, + self.runtime.clone() + ); + + // Find the binary + if let Some(binary) = self.compiled_modules.get_webc(webc, self.runtime.deref(), env.tasks.deref()) + { + if let Err(err) = env.uses(self.uses.clone()) { + let _ = self.runtime.stderr( + format!("{}\r\n", err).as_bytes() + ); + return Err(wasmer_vbus::VirtualBusError::BadRequest); + } + + // Build the config + let config = SpawnOptionsConfig { + reuse: false, + env, + remote_instance: None, + access_token: self.token.clone(), + }; + + // Run the binary + let process = spawn_exec( + binary, + prog, + store, + config, + &self.runtime, + self.compiled_modules.as_ref() + ).unwrap(); + + // Return the process + Ok(process) + } else { + let _ = self.runtime.stderr( + format!("package not found [{}]\r\n", self.boot_cmd).as_bytes() + ); + Err(wasmer_vbus::VirtualBusError::NotFound) + } + } + + pub fn draw_welcome(&self) { + let welcome = match (self.is_mobile, self.is_ssh) { + (true, _) => ConsoleConst::WELCOME_MEDIUM, + (_, true) => ConsoleConst::WELCOME_SMALL, + (_, _) => ConsoleConst::WELCOME_LARGE, + }; + let mut data = welcome + .replace("\\x1B", "\x1B") + .replace("\\r", "\r") + .replace("\\n", "\n"); + data.insert_str(0, ConsoleConst::TERM_NO_WRAPAROUND); + + let _ = self.runtime.stdout(data.as_str().as_bytes()); + } +} diff --git a/lib/wasi/src/os/mod.rs b/lib/wasi/src/os/mod.rs new file mode 100644 index 00000000000..9782f70215b --- /dev/null +++ b/lib/wasi/src/os/mod.rs @@ -0,0 +1,8 @@ +mod tty; +pub mod posix_err; +pub mod common; +pub mod cconst; +mod console; + +pub use tty::*; +pub use console::*; \ No newline at end of file diff --git a/lib/wasi/src/os/posix_err.rs b/lib/wasi/src/os/posix_err.rs new file mode 100644 index 00000000000..22febb0030e --- /dev/null +++ b/lib/wasi/src/os/posix_err.rs @@ -0,0 +1,262 @@ +pub const ERR_OK: u32 = 0; +pub const ERR_EPERM: u32 = 1; /* Operation not permitted */ +pub const ERR_ENOENT: u32 = 2; +pub const ERR_ESRCH: u32 = 3; /* No such process */ +pub const ERR_EINTR: u32 = 4; /* Interrupted system call */ +pub const ERR_EIO: u32 = 5; /* I/O error */ +pub const ERR_ENXIO: u32 = 6; /* No such device or address */ +pub const ERR_E2BIG: u32 = 7; /* Arg list too long */ +pub const ERR_ENOEXEC: u32 = 8; /* Exec format error */ +pub const ERR_EBADF: u32 = 9; /* Bad file number */ +pub const ERR_ECHILD: u32 = 10; /* No child processes */ +pub const ERR_EAGAIN: u32 = 11; /* Try again */ +pub const ERR_ENOMEM: u32 = 12; /* Out of memory */ +pub const ERR_EACCES: u32 = 13; /* Permission denied */ +pub const ERR_EFAULT: u32 = 14; /* Bad address */ +pub const ERR_ENOTBLK: u32 = 15; /* Block device required */ +pub const ERR_EBUSY: u32 = 16; /* Device or resource busy */ +pub const ERR_EEXIST: u32 = 17; /* File exists */ +pub const ERR_EXDEV: u32 = 18; /* Cross-device link */ +pub const ERR_ENODEV: u32 = 19; /* No such device */ +pub const ERR_ENOTDIR: u32 = 20; /* Not a directory */ +pub const ERR_EISDIR: u32 = 21; /* Is a directory */ +pub const ERR_EINVAL: u32 = 22; /* Invalid argument */ +pub const ERR_ENFILE: u32 = 23; /* File table overflow */ +pub const ERR_EMFILE: u32 = 24; /* Too many open files */ +pub const ERR_ENOTTY: u32 = 25; /* Not a typewriter */ +pub const ERR_ETXTBSY: u32 = 26; /* Text file busy */ +pub const ERR_EFBIG: u32 = 27; /* File too large */ +pub const ERR_ENOSPC: u32 = 28; /* No space left on device */ +pub const ERR_ESPIPE: u32 = 29; /* Illegal seek */ +pub const ERR_EROFS: u32 = 30; /* Read-only file system */ +pub const ERR_EMLINK: u32 = 31; /* Too many links */ +pub const ERR_EPIPE: u32 = 32; /* Broken pipe */ +pub const ERR_EDOM: u32 = 33; /* Math argument out of domain of func */ +pub const ERR_ERANGE: u32 = 34; /* Math result not representable */ +pub const ERR_EDEADLK: u32 = 35; /* Resource deadlock would occur */ +pub const ERR_ENAMETOOLONG: u32 = 36; /* File name too long */ +pub const ERR_ENOLCK: u32 = 37; /* No record locks available */ +pub const ERR_ENOSYS: u32 = 38; /* Function not implemented */ +pub const ERR_ENOTEMPTY: u32 = 39; /* Directory not empty */ +pub const ERR_ELOOP: u32 = 40; /* Too many symbolic links encountered */ +pub const ERR_EWOULDBLOCK: u32 = ERR_EAGAIN; /* Operation would block */ +pub const ERR_ENOMSG: u32 = 42; /* No message of desired type */ +pub const ERR_EIDRM: u32 = 43; /* Identifier removed */ +pub const ERR_ECHRNG: u32 = 44; /* Channel number out of range */ +pub const ERR_EL2NSYNC: u32 = 45; /* Level 2 not synchronized */ +pub const ERR_EL3HLT: u32 = 46; /* Level 3 halted */ +pub const ERR_EL3RST: u32 = 47; /* Level 3 reset */ +pub const ERR_ELNRNG: u32 = 48; /* Link number out of range */ +pub const ERR_EUNATCH: u32 = 49; /* Protocol driver not attached */ +pub const ERR_ENOCSI: u32 = 50; /* No CSI structure available */ +pub const ERR_EL2HLT: u32 = 51; /* Level 2 halted */ +pub const ERR_EBADE: u32 = 52; /* Invalid exchange */ +pub const ERR_EBADR: u32 = 53; /* Invalid request descriptor */ +pub const ERR_EXFULL: u32 = 54; /* Exchange full */ +pub const ERR_ENOANO: u32 = 55; /* No anode */ +pub const ERR_EBADRQC: u32 = 56; /* Invalid request code */ +pub const ERR_EBADSLT: u32 = 57; /* Invalid slot */ + +pub const ERR_EDEADLOCK: u32 = ERR_EDEADLK; + +pub const ERR_EBFONT: u32 = 59; /* Bad font file format */ +pub const ERR_ENOSTR: u32 = 60; /* Device not a stream */ +pub const ERR_ENODATA: u32 = 61; /* No data available */ +pub const ERR_ETIME: u32 = 62; /* Timer expired */ +pub const ERR_ENOSR: u32 = 63; /* Out of streams resources */ +pub const ERR_ENONET: u32 = 64; /* Machine is not on the network */ +pub const ERR_ENOPKG: u32 = 65; /* Package not installed */ +pub const ERR_EREMOTE: u32 = 66; /* Object is remote */ +pub const ERR_ENOLINK: u32 = 67; /* Link has been severed */ +pub const ERR_EADV: u32 = 68; /* Advertise error */ +pub const ERR_ESRMNT: u32 = 69; /* Srmount error */ +pub const ERR_ECOMM: u32 = 70; /* Communication error on send */ +pub const ERR_EPROTO: u32 = 71; /* Protocol error */ +pub const ERR_EMULTIHOP: u32 = 72; /* Multihop attempted */ +pub const ERR_EDOTDOT: u32 = 73; /* RFS specific error */ +pub const ERR_EBADMSG: u32 = 74; /* Not a data message */ +pub const ERR_EOVERFLOW: u32 = 75; /* Value too large for defined data type */ +pub const ERR_ENOTUNIQ: u32 = 76; /* Name not unique on network */ +pub const ERR_EBADFD: u32 = 77; /* File descriptor in bad state */ +pub const ERR_EREMCHG: u32 = 78; /* Remote address changed */ +pub const ERR_ELIBACC: u32 = 79; /* Can not access a needed shared library */ +pub const ERR_ELIBBAD: u32 = 80; /* Accessing a corrupted shared library */ +pub const ERR_ELIBSCN: u32 = 81; /* .lib section in a.out corrupted */ +pub const ERR_ELIBMAX: u32 = 82; /* Attempting to link in too many shared libraries */ +pub const ERR_ELIBEXEC: u32 = 83; /* Cannot exec a shared library directly */ +pub const ERR_EILSEQ: u32 = 84; /* Illegal byte sequence */ +pub const ERR_ERESTART: u32 = 85; /* Interrupted system call should be restarted */ +pub const ERR_ESTRPIPE: u32 = 86; /* Streams pipe error */ +pub const ERR_EUSERS: u32 = 87; /* Too many users */ +pub const ERR_ENOTSOCK: u32 = 88; /* Socket operation on non-socket */ +pub const ERR_EDESTADDRREQ: u32 = 89; /* Destination address required */ +pub const ERR_EMSGSIZE: u32 = 90; /* Message too long */ +pub const ERR_EPROTOTYPE: u32 = 91; /* Protocol wrong type for socket */ +pub const ERR_ENOPROTOOPT: u32 = 92; /* Protocol not available */ +pub const ERR_EPROTONOSUPPORT: u32 = 93; /* Protocol not supported */ +pub const ERR_ESOCKTNOSUPPORT: u32 = 94; /* Socket type not supported */ +pub const ERR_EOPNOTSUPP: u32 = 95; /* Operation not supported on transport endpoint */ +pub const ERR_EPFNOSUPPORT: u32 = 96; /* Protocol family not supported */ +pub const ERR_EAFNOSUPPORT: u32 = 97; /* Address family not supported by protocol */ +pub const ERR_EADDRINUSE: u32 = 98; /* Address already in use */ +pub const ERR_EADDRNOTAVAIL: u32 = 99; /* Cannot assign requested address */ +pub const ERR_ENETDOWN: u32 = 100; /* Network is down */ +pub const ERR_ENETUNREACH: u32 = 101; /* Network is unreachable */ +pub const ERR_ENETRESET: u32 = 102; /* Network dropped connection because of reset */ +pub const ERR_ECONNABORTED: u32 = 103; /* Software caused connection abort */ +pub const ERR_ECONNRESET: u32 = 104; /* Connection reset by peer */ +pub const ERR_ENOBUFS: u32 = 105; /* No buffer space available */ +pub const ERR_EISCONN: u32 = 106; /* Transport endpoint is already connected */ +pub const ERR_ENOTCONN: u32 = 107; /* Transport endpoint is not connected */ +pub const ERR_ESHUTDOWN: u32 = 108; /* Cannot send after transport endpoint shutdown */ +pub const ERR_ETOOMANYREFS: u32 = 109; /* Too many references: cannot splice */ +pub const ERR_ETIMEDOUT: u32 = 110; /* Connection timed out */ +pub const ERR_ECONNREFUSED: u32 = 111; /* Connection refused */ +pub const ERR_EHOSTDOWN: u32 = 112; /* Host is down */ +pub const ERR_EHOSTUNREACH: u32 = 113; /* No route to host */ +pub const ERR_EALREADY: u32 = 114; /* Operation already in progress */ +pub const ERR_EINPROGRESS: u32 = 115; /* Operation now in progress */ +pub const ERR_ESTALE: u32 = 116; /* Stale NFS file handle */ +pub const ERR_EUCLEAN: u32 = 117; /* Structure needs cleaning */ +pub const ERR_ENOTNAM: u32 = 118; /* Not a XENIX named type file */ +pub const ERR_ENAVAIL: u32 = 119; /* No XENIX semaphores available */ +pub const ERR_EISNAM: u32 = 120; /* Is a named type file */ +pub const ERR_EREMOTEIO: u32 = 121; /* Remote I/O error */ +pub const ERR_EDQUOT: u32 = 122; /* Quota exceeded */ + +pub const ERR_ENOMEDIUM: u32 = 123; /* No medium found */ +pub const ERR_EMEDIUMTYPE: u32 = 124; /* Wrong medium type */ + +pub const ERR_TERMINATED: u32 = 130; /* Process was terminated */ +pub const ERR_PANIC: u32 = 99999; /* Process has panicked */ + +pub fn exit_code_to_message(code: u32) -> &'static str { + match code { + ERR_OK => "Ok", + ERR_EPERM => "Operation not permitted", + ERR_ENOENT => "No such file or directory", + ERR_ESRCH => "No such process", + ERR_EINTR => "Interrupted system call", + ERR_EIO => "I/O error", + ERR_ENXIO => "No such device or address", + ERR_E2BIG => "Arg list too long", + ERR_ENOEXEC => "Exec format error", + ERR_EBADF => "Bad file number", + ERR_ECHILD => "No child processes", + ERR_EAGAIN => "Try again", + ERR_ENOMEM => "Out of memory", + ERR_EACCES => "Permission denied", + ERR_EFAULT => "Bad address", + ERR_ENOTBLK => "Block device required", + ERR_EBUSY => "Device or resource busy", + ERR_EEXIST => "File exists", + ERR_EXDEV => "Cross-device link", + ERR_ENODEV => "No such device", + ERR_ENOTDIR => "Not a directory", + ERR_EISDIR => "Is a directory", + ERR_EINVAL => "Invalid argument", + ERR_ENFILE => "File table overflow", + ERR_EMFILE => "Too many open files", + ERR_ENOTTY => "Not a typewriter", + ERR_ETXTBSY => "Text file busy", + ERR_EFBIG => "File too large", + ERR_ENOSPC => "No space left on device", + ERR_ESPIPE => "Illegal seek", + ERR_EROFS => "Read-only file system", + ERR_EMLINK => "Too many links", + ERR_EPIPE => "Broken pipe", + ERR_EDOM => "Math argument out of domain of func", + ERR_ERANGE => "Math result not representable", + ERR_EDEADLK => "Resource deadlock would occur", + ERR_ENAMETOOLONG => "File name too long", + ERR_ENOLCK => "No record locks available", + ERR_ENOSYS => "Function not implemented", + ERR_ENOTEMPTY => "Directory not empty", + ERR_ELOOP => "Too many symbolic links encountered", + ERR_ENOMSG => "No message of desired type", + ERR_EIDRM => "Identifier removed", + ERR_ECHRNG => "Channel number out of range", + ERR_EL2NSYNC => "Level 2 not synchronized", + ERR_EL3HLT => "Level 3 halted", + ERR_EL3RST => "Level 3 reset", + ERR_ELNRNG => "Link number out of range", + ERR_EUNATCH => "Protocol driver not attached", + ERR_ENOCSI => "No CSI structure available", + ERR_EL2HLT => "Level 2 halted", + ERR_EBADE => "Invalid exchange", + ERR_EBADR => "Invalid request descriptor", + ERR_EXFULL => "Exchange full", + ERR_ENOANO => "No anode", + ERR_EBADRQC => "Invalid request code", + ERR_EBADSLT => "Invalid slot", + ERR_EBFONT => "Bad font file format", + ERR_ENOSTR => "Device not a stream", + ERR_ENODATA => "No data available", + ERR_ETIME => "Timer expired", + ERR_ENOSR => "Out of streams resources", + ERR_ENONET => "Machine is not on the network", + ERR_ENOPKG => "Package not installed", + ERR_EREMOTE => "Object is remote", + ERR_ENOLINK => "Link has been severed", + ERR_EADV => "Advertise error", + ERR_ESRMNT => "Srmount error", + ERR_ECOMM => "Communication error on send", + ERR_EPROTO => "Protocol error", + ERR_EMULTIHOP => "Multihop attempted", + ERR_EDOTDOT => "RFS specific error", + ERR_EBADMSG => "Not a data message", + ERR_EOVERFLOW => "Value too large for defined data type", + ERR_ENOTUNIQ => "Name not unique on network", + ERR_EBADFD => "File descriptor in bad state", + ERR_EREMCHG => "Remote address changed", + ERR_ELIBACC => "Can not access a needed shared library", + ERR_ELIBBAD => "Accessing a corrupted shared library", + ERR_ELIBSCN => ".lib section in a.out corrupted", + ERR_ELIBMAX => "Attempting to link in too many shared libraries", + ERR_ELIBEXEC => "Cannot exec a shared library directly", + ERR_EILSEQ => "Illegal byte sequence", + ERR_ERESTART => "Interrupted system call should be restarted", + ERR_ESTRPIPE => "Streams pipe error", + ERR_EUSERS => "Too many users", + ERR_ENOTSOCK => "Socket operation on non-socket", + ERR_EDESTADDRREQ => "Destination address required", + ERR_EMSGSIZE => "Message too long", + ERR_EPROTOTYPE => "Protocol wrong type for socket", + ERR_ENOPROTOOPT => "Protocol not available", + ERR_EPROTONOSUPPORT => "Protocol not supported", + ERR_ESOCKTNOSUPPORT => "Socket type not supported", + ERR_EOPNOTSUPP => "Operation not supported on transport endpoint", + ERR_EPFNOSUPPORT => "Protocol family not supported", + ERR_EAFNOSUPPORT => "Address family not supported by protocol", + ERR_EADDRINUSE => "Address already in use", + ERR_EADDRNOTAVAIL => "Cannot assign requested address", + ERR_ENETDOWN => "Network is down", + ERR_ENETUNREACH => "Network is unreachable", + ERR_ENETRESET => "Network dropped connection because of reset", + ERR_ECONNABORTED => "Software caused connection abort", + ERR_ECONNRESET => "Connection reset by peer", + ERR_ENOBUFS => "No buffer space available", + ERR_EISCONN => "Transport endpoint is already connected", + ERR_ENOTCONN => "Transport endpoint is not connected", + ERR_ESHUTDOWN => "Cannot send after transport endpoint shutdown", + ERR_ETOOMANYREFS => "Too many references: cannot splice", + ERR_ETIMEDOUT => "Connection timed out", + ERR_ECONNREFUSED => "Connection refused", + ERR_EHOSTDOWN => "Host is down", + ERR_EHOSTUNREACH => "No route to host", + ERR_EALREADY => "Operation already in progress", + ERR_EINPROGRESS => "Operation now in progress", + ERR_ESTALE => "Stale NFS file handle", + ERR_EUCLEAN => "Structure needs cleaning", + ERR_ENOTNAM => "Not a XENIX named type file", + ERR_ENAVAIL => "No XENIX semaphores available", + ERR_EISNAM => "Is a named type file", + ERR_EREMOTEIO => "Remote I/O error", + ERR_EDQUOT => "Quota exceeded", + ERR_ENOMEDIUM => "No medium found", + ERR_EMEDIUMTYPE => "Wrong medium type", + ERR_PANIC => "Process has panicked", + ERR_TERMINATED => "Process was terminated", + _ => "Unknown error", + } +} diff --git a/lib/wasi/src/os/tty.rs b/lib/wasi/src/os/tty.rs new file mode 100644 index 00000000000..ff285afa20e --- /dev/null +++ b/lib/wasi/src/os/tty.rs @@ -0,0 +1,389 @@ +use std::{sync::{Mutex, Arc}, io::Write}; +use derivative::*; + +use wasmer_vfs::VirtualFile; +use wasmer_vbus::SignalHandlerAbi; +use wasmer_wasi_types::__WASI_CLOCK_MONOTONIC; + +use crate::{ + types::__WASI_SIGINT, + syscalls::platform_clock_time_get +}; + +const TTY_MOBILE_PAUSE: u128 = std::time::Duration::from_millis(200).as_nanos(); + +#[derive(Debug)] +pub enum InputEvent { + Key, + Data(String), + Raw(Vec), +} + +#[derive(Debug)] +pub struct ConsoleRect { + pub cols: u32, + pub rows: u32, +} + +impl Default +for ConsoleRect { + fn default() -> Self { + Self { + cols: 80, + rows: 25 + } + } +} + +#[derive(Debug)] +pub struct TtyOptionsInner { + echo: bool, + line_buffering: bool, + line_feeds: bool, + rect: ConsoleRect, +} + +#[derive(Debug, Clone)] +pub struct TtyOptions { + inner: Arc> +} + +impl Default +for TtyOptions { + fn default() -> Self { + Self { + inner: Arc::new(Mutex::new(TtyOptionsInner { + echo: true, + line_buffering: true, + line_feeds: true, + rect: ConsoleRect { + cols: 80, + rows: 25 + } + })) + } + } +} + +impl TtyOptions { + pub fn cols(&self) -> u32 { + let inner = self.inner.lock().unwrap(); + inner.rect.cols + } + + pub fn set_cols(&self, cols: u32) { + let mut inner = self.inner.lock().unwrap(); + inner.rect.cols = cols; + } + + pub fn rows(&self) -> u32 { + let inner = self.inner.lock().unwrap(); + inner.rect.rows + } + + pub fn set_rows(&self, rows: u32) { + let mut inner = self.inner.lock().unwrap(); + inner.rect.rows = rows; + } + + pub fn echo(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.echo + } + + pub fn set_echo(&self, echo: bool) { + let mut inner = self.inner.lock().unwrap(); + inner.echo = echo; + } + + pub fn line_buffering(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.line_buffering + } + + pub fn set_line_buffering(&self, line_buffering: bool) { + let mut inner = self.inner.lock().unwrap(); + inner.line_buffering = line_buffering; + } + + pub fn line_feeds(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.line_feeds + } + + pub fn set_line_feeds(&self, line_feeds: bool) { + let mut inner = self.inner.lock().unwrap(); + inner.line_feeds = line_feeds; + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Tty { + stdin: Box, + stdout: Box, + signaler: Option>, + is_mobile: bool, + last: Option<(String, u128)>, + options: TtyOptions, + line: String, +} + +impl Tty { + pub fn new( + stdin: Box, + stdout: Box, + is_mobile: bool, + options: TtyOptions + ) -> Self { + Self { + stdin, + stdout, + signaler: None, + last: None, + options, + is_mobile, + line: String::new() + } + } + + pub fn options(&self) -> TtyOptions { + self.options.clone() + } + + pub fn set_signaler(&mut self, signaler: Box) { + self.signaler.replace(signaler); + } + + pub fn on_event(&mut self, event: InputEvent) { + match event { + InputEvent::Key => { + // do nothing + } + InputEvent::Data(data) => { + // Due to a nasty bug in xterm.js on Android mobile it sends the keys you press + // twice in a row with a short interval between - this hack will avoid that bug + if self.is_mobile { + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + if let Some((what, when)) = self.last.as_ref() { + if what.as_str() == data && now - *when < TTY_MOBILE_PAUSE { + self.last = None; + return; + } + } + self.last = Some((data.clone(), now)) + } + + self.on_data(data.as_bytes()) + } + InputEvent::Raw(data) => { + self.on_data(&data[..]) + } + } + } + + fn on_enter(&mut self, _data: &str) + { + // Add a line feed on the end and take the line + let mut data = self.line.clone(); + self.line.clear(); + data.push_str("\n"); + + // If echo is on then write a new line + { + let options = self.options.inner.lock().unwrap(); + if options.echo { + drop(options); + self.stdout("\n".as_bytes()); + } + } + + // Send the data to the process + let _ = self.stdin.write(data.as_bytes()); + } + + fn on_ctrl_c(&mut self, _data: &str) + { + if let Some(signaler) = self.signaler.as_ref() { + signaler.signal(__WASI_SIGINT); + + let (echo, _line_buffering) = { + let options = self.options.inner.lock().unwrap(); + (options.echo, options.line_buffering) + }; + + self.line.clear(); + if echo { + self.stdout("\n".as_bytes()); + } + let _ = self.stdin.write("\n".as_bytes()); + } + } + + fn on_backspace(&mut self, _data: &str) + { + // Remove a character (if there are none left we are done) + if self.line.is_empty() { + return; + } + let len = self.line.len(); + self.line = (&self.line[..len-1]).to_string(); + + // If echo is on then write the backspace + { + let options = self.options.inner.lock().unwrap(); + if options.echo { + drop(options); + self.stdout("\u{0008} \u{0008}".as_bytes()); + } + } + } + + fn on_tab(&mut self, _data: &str) + { + } + + fn on_cursor_left(&mut self, _data: &str) + { + } + + fn on_cursor_right(&mut self, _data: &str) + { + } + + fn on_cursor_up(&mut self, _data: &str) + { + } + + fn on_cursor_down(&mut self, _data: &str) + { + } + + fn on_home(&mut self, _data: &str) + { + } + + fn on_end(&mut self, _data: &str) + { + } + + fn on_ctrl_l(&mut self, _data: &str) + { + } + + fn on_page_up(&mut self, _data: &str) + { + } + + fn on_page_down(&mut self, _data: &str) + { + } + + fn on_f1(&mut self, _data: &str) + { + } + + fn on_f2(&mut self, _data: &str) + { + } + + fn on_f3(&mut self, _data: &str) + { + } + + fn on_f4(&mut self, _data: &str) + { + } + + fn on_f5(&mut self, _data: &str) + { + } + + fn on_f6(&mut self, _data: &str) + { + } + + fn on_f7(&mut self, _data: &str) + { + } + + fn on_f8(&mut self, _data: &str) + { + } + + fn on_f9(&mut self, _data: &str) + { + } + + fn on_f10(&mut self, _data: &str) + { + } + + fn on_f11(&mut self, _data: &str) + { + } + + fn on_f12(&mut self, _data: &str) + { + } + + fn on_data(&mut self, data: &[u8]) + { + // If we are line buffering then we need to check for some special cases + let options = self.options.inner.lock().unwrap(); + if options.line_buffering { + let echo = options.echo; + drop(options); + let data = String::from_utf8_lossy(data); + let data = data.as_ref(); + return match data { + "\r" | "\u{000A}" => self.on_enter(data), + "\u{0003}" => self.on_ctrl_c(data), + "\u{007F}" => self.on_backspace(data), + "\u{0009}" => self.on_tab(data), + "\u{001B}\u{005B}\u{0044}" => self.on_cursor_left(data), + "\u{001B}\u{005B}\u{0043}" => self.on_cursor_right(data), + "\u{0001}" | "\u{001B}\u{005B}\u{0048}" => self.on_home(data), + "\u{001B}\u{005B}\u{0046}" => self.on_end(data), + "\u{001B}\u{005B}\u{0041}" => self.on_cursor_up(data), + "\u{001B}\u{005B}\u{0042}" => self.on_cursor_down(data), + "\u{000C}" => self.on_ctrl_l(data), + "\u{001B}\u{005B}\u{0035}\u{007E}" => self.on_page_up(data), + "\u{001B}\u{005B}\u{0036}\u{007E}" => self.on_page_down(data), + "\u{001B}\u{004F}\u{0050}" => self.on_f1(data), + "\u{001B}\u{004F}\u{0051}" => self.on_f2(data), + "\u{001B}\u{004F}\u{0052}" => self.on_f3(data), + "\u{001B}\u{004F}\u{0053}" => self.on_f4(data), + "\u{001B}\u{005B}\u{0031}\u{0035}\u{007E}" => self.on_f5(data), + "\u{001B}\u{005B}\u{0031}\u{0037}\u{007E}" => self.on_f6(data), + "\u{001B}\u{005B}\u{0031}\u{0038}\u{007E}" => self.on_f7(data), + "\u{001B}\u{005B}\u{0031}\u{0039}\u{007E}" => self.on_f8(data), + "\u{001B}\u{005B}\u{0032}\u{0030}\u{007E}" => self.on_f9(data), + "\u{001B}\u{005B}\u{0032}\u{0031}\u{007E}" => self.on_f10(data), + "\u{001B}\u{005B}\u{0032}\u{0033}\u{007E}" => self.on_f11(data), + "\u{001B}\u{005B}\u{0032}\u{0034}\u{007E}" => self.on_f12(data), + data => { + if echo == true { + self.stdout(data.as_bytes()); + } + self.line.push_str(data); + } + }; + }; + + // If the echo is enabled then write it to the terminal + if options.echo == true { + drop(options); + self.stdout(data); + } else { + drop(options); + } + + // Now send it to the process + let _ = self.stdin.write(data); + } + + fn stdout(&mut self, data: &[u8]) { + let _ = self.stdout.write(&data[..]); + } +} \ No newline at end of file diff --git a/lib/wasi/src/os/txt/about.md b/lib/wasi/src/os/txt/about.md new file mode 100644 index 00000000000..0b302c32239 --- /dev/null +++ b/lib/wasi/src/os/txt/about.md @@ -0,0 +1,9 @@ +# Wasmer Terminal + +This terminal is an Wasmer powered terminal hosted in a browser which implements +a basic operating system and is natively integrated with ATE and WAPM. + +For more information try: + +about wasmer +about deploy \ No newline at end of file diff --git a/lib/wasi/src/os/txt/about_deploy.md b/lib/wasi/src/os/txt/about_deploy.md new file mode 100644 index 00000000000..f767da9c298 --- /dev/null +++ b/lib/wasi/src/os/txt/about_deploy.md @@ -0,0 +1,14 @@ +# wasmer.sh + +The Wasmer Shell is an browser based operating system powered by wasmer.io +that allows the WebAssembly community to assembly and build browser hosted + applications. + +Including: +- MemFS file system with mount points +- stdin, stdout, stderr and tty support +- Private file system space per process. +- Full support for piping and TTY. +- Fully multi-threaded. +- Full networking support. +- Support for dash and bash commands. \ No newline at end of file diff --git a/lib/wasi/src/os/txt/about_wasmer.md b/lib/wasi/src/os/txt/about_wasmer.md new file mode 100644 index 00000000000..8d0922893fd --- /dev/null +++ b/lib/wasi/src/os/txt/about_wasmer.md @@ -0,0 +1,14 @@ +# Wasmer + +Wasmer is a fast and secure WebAssembly runtime that enables super +lightweight containers to run anywhere: from Desktop to the Cloud, Edge and +IoT devices. + +Features: +• Secure by default. No file, network, or environment access, unless + explicitly enabled. +• Supports WASI and Emscripten out of the box. +• Fast. Run WebAssembly at near-native speeds. +• Embeddable in multiple programming languages +• Compliant with latest WebAssembly Proposals (SIMD, Reference Types, + Threads, ...) \ No newline at end of file diff --git a/lib/wasi/src/os/txt/bad_worker.md b/lib/wasi/src/os/txt/bad_worker.md new file mode 100644 index 00000000000..589c5ce26d1 --- /dev/null +++ b/lib/wasi/src/os/txt/bad_worker.md @@ -0,0 +1,19 @@ + +\x1B[1;31mBackground worker threads failed - {error}\x1B[30;1m + +It would appear that your browser does not support background worker threads +which means that https://wasmer.sh will not be able to launch processes and +effectively becomes very limited.\x1B[37;1m + +List supported major browsers: + +- Chrome for Desktop - \x1B[30;1mversion 68 and above\x1B[37;1m +- Chrome for Android - \x1B[30;1mversion 96 and above\x1B[37;1m +- Firefox for Desktop - \x1B[30;1mversion 79 and above\x1B[37;1m +- Firefox for Android - \x1B[30;1mversion 92 and above\x1B[37;1m +- Edge - \x1B[30;1mversion 79 and above\x1B[30;1m + +The full list is provided here: +https://caniuse.com/sharedarraybuffer\x1B[37;1m + +Please install and/or upgrade your browser to continue diff --git a/lib/wasi/src/os/txt/help.md b/lib/wasi/src/os/txt/help.md new file mode 100644 index 00000000000..a16e21d9ae0 --- /dev/null +++ b/lib/wasi/src/os/txt/help.md @@ -0,0 +1,25 @@ +# wasmer.sh + +## The Shell + +The Wasmer WASM shell is an browser based operating system that integrates +with the WebAssembly community to assembly and build micro-applications. + +Including: +- MemFS file system with mount points +- stdin, stdout, stderr and tty support +- Private file system space per process. +- Full support for piping and TTY. +- Fully multi-threaded. +- Support for basic bash commands. + +## coreutil commands: + + arch, base32, base64, basename, cat, cksum, comm, cp, csplit, cut, + date, dircolors, dirname, echo, env, expand, factor, false, fmt, fold, + hashsum, head, join, link, ln, ls, md5sum, mkdir, mktemp, mv, nl, nproc, + numfmt, od, paste, printenv, printf, ptx, pwd, readlink, realpath, + relpath, rm, rmdir, seq, sha1sum, sha224sum, sha256sum, sha3-224sum, + sha3-256sum, sha3-384sum, sha3-512sum, sha384sum, sha3sum, sha512sum, + shake128sum, shake256sum, shred, shuf, sleep, sum, tee, touch, tr, true, + truncate, tsort, unexpand, uniq, unlink, wc, yes \ No newline at end of file diff --git a/lib/wasi/src/os/txt/welcome_large.txt b/lib/wasi/src/os/txt/welcome_large.txt new file mode 100644 index 00000000000..2c32f502518 --- /dev/null +++ b/lib/wasi/src/os/txt/welcome_large.txt @@ -0,0 +1,10 @@ +\x1B[1;34m██╗ ██╗ █████╗ ███████╗███╗ ███╗███████╗██████╗ ███████╗██╗ ██╗ +██║ ██║██╔══██╗██╔════╝████╗ ████║██╔════╝██╔══██╗ ██╔════╝██║ ██║ +██║ █╗ ██║███████║███████╗██╔████╔██║█████╗ ██████╔╝ ███████╗███████║ +██║███╗██║██╔══██║╚════██║██║╚██╔╝██║██╔══╝ ██╔══██╗ ╚════██║██╔══██║ +╚███╔███╔╝██║ ██║███████║██║ ╚═╝ ██║███████╗██║ ██║██╗███████║██║ ██║ + ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝\x1B[37;1m\r + QUICK START: MORE INFO:\x1B[1;30m\r +• Wasmer commands: wasmer • Usage Information: help\r +• Core utils: coreutils • About Wasmer: about wasmer\r +• Pipe: echo blah | cat\x1B[37;0m\r\r\n \ No newline at end of file diff --git a/lib/wasi/src/os/txt/welcome_medium.txt b/lib/wasi/src/os/txt/welcome_medium.txt new file mode 100644 index 00000000000..ebfde1a56b4 --- /dev/null +++ b/lib/wasi/src/os/txt/welcome_medium.txt @@ -0,0 +1,7 @@ +\x1B[1;34m██╗ ██╗ █████╗ ███████╗███╗ ███╗███████╗██████╗ \r +██║ ██║██╔══██╗██╔════╝████╗ ████║██╔════╝██╔══██╗\r +██║ █╗ ██║███████║███████╗██╔████╔██║█████╗ ██████╔╝\r +██║███╗██║██╔══██║╚════██║██║╚██╔╝██║██╔══╝ ██╔══██╗\r +╚███╔███╔╝██║ ██║███████║██║ ╚═╝ ██║███████╗██║ ██║\r + ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝\x1B[37;1m\r + Type 'help' for commands.\x1B[37;0m\r\r\n \ No newline at end of file diff --git a/lib/wasi/src/os/txt/welcome_small.txt b/lib/wasi/src/os/txt/welcome_small.txt new file mode 100644 index 00000000000..181669ac80b --- /dev/null +++ b/lib/wasi/src/os/txt/welcome_small.txt @@ -0,0 +1,4 @@ +\x1B[1;34m _ _ _ _____ ___ ____ _____ ____ \r +| | | (____ |/___| \| ___ |/ ___)\r +| | | / ___ |___ | | | | ____| | \r + \___/\_____(___/|_|_|_|_____|_| \x1B[37;0m\r\r\n \ No newline at end of file diff --git a/lib/wasi/src/runtime.rs b/lib/wasi/src/runtime.rs deleted file mode 100644 index 1394e010b8a..00000000000 --- a/lib/wasi/src/runtime.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::fmt; -use std::ops::Deref; -use std::sync::atomic::{AtomicU32, Ordering}; -use thiserror::Error; -use wasmer_vbus::{UnsupportedVirtualBus, VirtualBus}; -use wasmer_vnet::VirtualNetworking; -use wasmer_wasi_types::wasi::Errno; - -use super::WasiError; -use super::WasiThreadId; - -#[derive(Error, Debug)] -pub enum WasiThreadError { - #[error("Multithreading is not supported")] - Unsupported, - #[error("The method named is not an exported function")] - MethodNotFound, -} - -impl From for Errno { - fn from(a: WasiThreadError) -> Errno { - match a { - WasiThreadError::Unsupported => Errno::Notsup, - WasiThreadError::MethodNotFound => Errno::Inval, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct WasiTtyState { - pub cols: u32, - pub rows: u32, - pub width: u32, - pub height: u32, - pub stdin_tty: bool, - pub stdout_tty: bool, - pub stderr_tty: bool, - pub echo: bool, - pub line_buffered: bool, -} - -/// Represents an implementation of the WASI runtime - by default everything is -/// unimplemented. -pub trait WasiRuntimeImplementation: fmt::Debug + Sync { - /// For WASI runtimes that support it they can implement a message BUS implementation - /// which allows runtimes to pass serialized messages between each other similar to - /// RPC's. BUS implementation can be implemented that communicate across runtimes - /// thus creating a distributed computing architecture. - fn bus(&self) -> &(dyn VirtualBus); - - /// Provides access to all the networking related functions such as sockets. - /// By default networking is not implemented. - fn networking(&self) -> &(dyn VirtualNetworking); - - /// Generates a new thread ID - fn thread_generate_id(&self) -> WasiThreadId; - - /// Gets the TTY state - fn tty_get(&self) -> WasiTtyState { - WasiTtyState { - rows: 25, - cols: 80, - width: 800, - height: 600, - stdin_tty: false, - stdout_tty: false, - stderr_tty: false, - echo: true, - line_buffered: true, - } - } - - /// Sets the TTY state - fn tty_set(&self, _tty_state: WasiTtyState) {} - - /// Spawns a new thread by invoking the - fn thread_spawn( - &self, - _callback: Box, - ) -> Result<(), WasiThreadError> { - Err(WasiThreadError::Unsupported) - } - - /// Returns the amount of parallelism that is possible on this platform - fn thread_parallelism(&self) -> Result { - Err(WasiThreadError::Unsupported) - } - - /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded - /// execution environments) they will need to do asynchronous work whenever the main - /// thread goes idle and this is the place to hook for that. - fn yield_now(&self, _id: WasiThreadId) -> Result<(), WasiError> { - std::thread::yield_now(); - Ok(()) - } - - /// Gets the current process ID - fn getpid(&self) -> Option { - None - } -} - -#[derive(Debug)] -pub struct PluggableRuntimeImplementation { - pub bus: Box, - pub networking: Box, - pub thread_id_seed: AtomicU32, -} - -impl PluggableRuntimeImplementation { - pub fn set_bus_implementation(&mut self, bus: I) - where - I: VirtualBus + Sync, - { - self.bus = Box::new(bus) - } - - pub fn set_networking_implementation(&mut self, net: I) - where - I: VirtualNetworking + Sync, - { - self.networking = Box::new(net) - } -} - -impl Default for PluggableRuntimeImplementation { - fn default() -> Self { - Self { - #[cfg(not(feature = "host-vnet"))] - networking: Box::new(wasmer_vnet::UnsupportedVirtualNetworking::default()), - #[cfg(feature = "host-vnet")] - networking: Box::new(wasmer_wasi_local_networking::LocalNetworking::default()), - bus: Box::new(UnsupportedVirtualBus::default()), - thread_id_seed: Default::default(), - } - } -} - -impl WasiRuntimeImplementation for PluggableRuntimeImplementation { - fn bus(&self) -> &(dyn VirtualBus) { - self.bus.deref() - } - - fn networking(&self) -> &(dyn VirtualNetworking) { - self.networking.deref() - } - - fn thread_generate_id(&self) -> WasiThreadId { - self.thread_id_seed.fetch_add(1, Ordering::Relaxed).into() - } -} diff --git a/lib/wasi/src/runtime/host_ws.rs b/lib/wasi/src/runtime/host_ws.rs new file mode 100644 index 00000000000..2beca6e5c4e --- /dev/null +++ b/lib/wasi/src/runtime/host_ws.rs @@ -0,0 +1,85 @@ +use async_trait::async_trait; +use futures::stream::SplitSink; +use futures::stream::SplitStream; +use futures::SinkExt; +use futures_util::StreamExt; +use wasmer_os::wasmer_wasi::WasiRuntimeImplementation; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::Mutex; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +use wasmer_os::wasmer_wasi::WebSocketAbi; + +#[allow(unused_imports)] +use tracing::{debug, error, info, instrument, span, trace, warn, Level}; + +pub struct TerminalWebSocket { + sink: SplitSink>, Message>, + stream: Option>>>, + on_close: Arc>>>, +} + +impl TerminalWebSocket { + pub async fn new(url: &str) -> Result { + let url = url::Url::parse(url) + .map_err(|err| err.to_string())?; + + let (ws_stream, _) = connect_async(url).await + .map_err(|err| format!("failed to connect - {}", err))?; + let (sink, stream) = ws_stream.split(); + + Ok( + TerminalWebSocket { + sink, + stream: Some(stream), + on_close: Arc::new(Mutex::new(None)), + } + ) + } +} + +#[async_trait] +impl WebSocketAbi for TerminalWebSocket { + fn set_onopen(&mut self, mut callback: Box) { + // We instantly notify that we are open + callback(); + } + + fn set_onclose(&mut self, callback: Box) { + let mut guard = self.on_close.lock().unwrap(); + guard.replace(callback); + } + + fn set_onmessage(&mut self, callback: Box) + Send + 'static>, runtime: &dyn WasiRuntimeImplementation) + { + if let Some(mut stream) = self.stream.take() { + let on_close = self.on_close.clone(); + runtime.task_shared(Box::new(move || Pin::new(Box::new(async move { + while let Some(msg) = stream.next().await { + match msg { + Ok(Message::Binary(msg)) => { + callback(msg); + } + a => { + debug!("received invalid msg: {:?}", a); + } + } + } + let on_close = on_close.lock().unwrap(); + if let Some(on_close) = on_close.as_ref() { + on_close(); + } + })))); + } + } + + async fn send(&mut self, data: Vec) -> Result<(), String> { + self.sink + .send(Message::binary(data)) + .await + .map_err(|err| err.to_string())?; + Ok(()) + } +} diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs new file mode 100644 index 00000000000..5bd41994d87 --- /dev/null +++ b/lib/wasi/src/runtime/mod.rs @@ -0,0 +1,768 @@ +use std::io::Write; +use std::sync::{Arc, Mutex}; +use std::task::Waker; +use std::{fmt, io}; +use std::future::Future; +use std::pin::Pin; +use thiserror::Error; +use wasmer::{Module, Store, MemoryType}; +use wasmer::vm::VMMemory; +#[cfg(feature = "sys")] +use wasmer_types::MemoryStyle; +use wasmer_vbus::{DefaultVirtualBus, VirtualBus}; +use wasmer_vnet::VirtualNetworking; +use derivative::Derivative; +use tracing::*; + +use crate::{WasiCallingId, WasiEnv}; + +use super::types::*; + +mod ws; +pub use ws::*; + +mod stdio; +pub use stdio::*; + +#[cfg(feature = "termios")] +pub mod term; +#[cfg(feature = "termios")] +pub use term::*; + +use tokio::runtime::{ + Builder, Runtime +}; + +#[derive(Error, Debug)] +pub enum WasiThreadError { + #[error("Multithreading is not supported")] + Unsupported, + #[error("The method named is not an exported function")] + MethodNotFound, + #[error("Failed to create the requested memory")] + MemoryCreateFailed, + /// This will happen if WASM is running in a thread has not been created by the spawn_wasm call + #[error("WASM context is invalid")] + InvalidWasmContext, +} + +impl From for __wasi_errno_t { + fn from(a: WasiThreadError) -> __wasi_errno_t { + match a { + WasiThreadError::Unsupported => __WASI_ENOTSUP, + WasiThreadError::MethodNotFound => __WASI_EINVAL, + WasiThreadError::MemoryCreateFailed => __WASI_EFAULT, + WasiThreadError::InvalidWasmContext => __WASI_ENOEXEC, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct WasiTtyState { + pub cols: u32, + pub rows: u32, + pub width: u32, + pub height: u32, + pub stdin_tty: bool, + pub stdout_tty: bool, + pub stderr_tty: bool, + pub echo: bool, + pub line_buffered: bool, + pub line_feeds: bool, +} + +impl Default +for WasiTtyState { + fn default() -> Self { + Self { + rows: 80, + cols: 25, + width: 800, + height: 600, + stdin_tty: true, + stdout_tty: true, + stderr_tty: true, + echo: false, + line_buffered: false, + line_feeds: true, + } + } +} + +#[derive(Debug)] +pub struct SpawnedMemory +{ + pub ty: MemoryType, + #[cfg(feature = "sys")] + pub style: MemoryStyle, +} + +#[derive(Debug)] +pub enum SpawnType { + Create, + CreateWithType(SpawnedMemory), + NewThread(VMMemory), +} + +#[derive(Debug, Default)] +pub struct ReqwestOptions { + pub gzip: bool, + pub cors_proxy: Option, +} + +pub struct ReqwestResponse { + pub pos: usize, + pub data: Option>, + pub ok: bool, + pub redirected: bool, + pub status: u16, + pub status_text: String, + pub headers: Vec<(String, String)>, +} + +/// An implementation of task management +#[allow(unused_variables)] +pub trait VirtualTaskManager: fmt::Debug + Send + Sync + 'static +{ + /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded + /// execution environments) they will need to do asynchronous work whenever the main + /// thread goes idle and this is the place to hook for that. + fn sleep_now(&self, _id: WasiCallingId, ms: u128) -> Pin + Send + Sync + 'static>>; + + /// Starts an asynchronous task that will run on a shared worker pool + /// This task must not block the execution or it could cause a deadlock + fn task_shared( + &self, + task: Box< + dyn FnOnce() -> Pin + Send + 'static>> + Send + 'static, + >, + ) -> Result<(), WasiThreadError>; + + /// Starts an asynchronous task on the local thread (by running it in a runtime) + fn block_on( + &self, + task: Pin>>, + ); + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool that has a stateful thread local variable + /// It is ok for this task to block execution and any async futures within its scope + fn task_wasm( + &self, + task: Box) + Send + 'static>, + store: Store, + module: Module, + spawn_type: SpawnType, + ) -> Result<(), WasiThreadError>; + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated( + &self, + task: Box, + ) -> Result<(), WasiThreadError>; + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated_async( + &self, + task: Box Pin + 'static>> + Send + 'static>, + ) -> Result<(), WasiThreadError>; + + /// Returns the amount of parallelism that is possible on this platform + fn thread_parallelism(&self) -> Result; + + /// Returns a list of periodic wakers that need to be woken on a regular basis + fn periodic_wakers(&self) -> Arc, tokio::sync::broadcast::Sender<()>)>>; + + /// Gets a function that will register a root periodic waker + fn register_root_waker(&self) -> Arc { + let periodic_wakers = self.periodic_wakers(); + Arc::new(move |waker: Waker| { + let mut guard = periodic_wakers.lock().unwrap(); + guard.0.push(waker); + let _ = guard.1.send(()); + }) + } + + /// Wakes all the root wakers + fn wake_root_wakers(&self) { + let wakers = { + let periodic_wakers = self.periodic_wakers(); + let mut guard = periodic_wakers.lock().unwrap(); + guard.0.drain(..).collect::>() + }; + for waker in wakers { + waker.wake(); + } + } + + /// Waits for a periodic period (if there is anyone waiting on it) + fn wait_for_root_waker(&self) -> Pin + Send + Sync + 'static>> { + let (has_wakers, mut new_wakers) = { + let periodic_wakers = self.periodic_wakers(); + let guard = periodic_wakers.lock().unwrap(); + let has_wakers = guard.0.is_empty() == false; + let new_wakers = guard.1.subscribe(); + (has_wakers, new_wakers) + }; + let sleep_now = self.sleep_now(crate::current_caller_id(), 5); + Box::pin(async move { + if has_wakers { + tokio::select! { + _ = sleep_now => { }, + _ = new_wakers.recv() => { }, + } + } else { + let _ = new_wakers.recv().await; + } + }) + } +} + +/// Represents an implementation of the WASI runtime - by default everything is +/// unimplemented. +#[allow(unused_variables)] +pub trait WasiRuntimeImplementation +where Self: fmt::Debug + Sync, +{ + /// For WASI runtimes that support it they can implement a message BUS implementation + /// which allows runtimes to pass serialized messages between each other similar to + /// RPC's. BUS implementation can be implemented that communicate across runtimes + /// thus creating a distributed computing architecture. + fn bus(&self) -> Arc + Send + Sync + 'static>; + + /// Provides access to all the networking related functions such as sockets. + /// By default networking is not implemented. + fn networking(&self) -> Arc; + + /// Create a new task management runtime + fn new_task_manager(&self) -> Arc { + Arc::new(DefaultTaskManager::default()) + } + + /// Gets the TTY state + #[cfg(not(feature = "host-termios"))] + fn tty_get(&self) -> WasiTtyState { + Default::default() + } + + /// Sets the TTY state + #[cfg(not(feature = "host-termios"))] + fn tty_set(&self, _tty_state: WasiTtyState) { + } + + #[cfg(feature = "host-termios")] + fn tty_get(&self) -> WasiTtyState { + let mut echo = false; + let mut line_buffered = false; + let mut line_feeds = false; + + if let Ok(termios) = termios::Termios::from_fd(0) { + echo = (termios.c_lflag & termios::ECHO) != 0; + line_buffered = (termios.c_lflag & termios::ICANON) != 0; + line_feeds = (termios.c_lflag & termios::ONLCR) != 0; + } + + if let Some((w, h)) = term_size::dimensions() { + WasiTtyState { + cols: w as u32, + rows: h as u32, + width: 800, + height: 600, + stdin_tty: true, + stdout_tty: true, + stderr_tty: true, + echo, + line_buffered, + line_feeds, + } + } else { + WasiTtyState { + rows: 80, + cols: 25, + width: 800, + height: 600, + stdin_tty: true, + stdout_tty: true, + stderr_tty: true, + echo, + line_buffered, + line_feeds, + } + } + } + + /// Sets the TTY state + #[cfg(feature = "host-termios")] + fn tty_set(&self, tty_state: WasiTtyState) { + if tty_state.echo { + set_mode_echo(); + } else { + set_mode_no_echo(); + } + if tty_state.line_buffered { + set_mode_line_buffered(); + } else { + set_mode_no_line_buffered(); + } + if tty_state.line_feeds { + set_mode_line_feeds(); + } else { + set_mode_no_line_feeds(); + } + } + + /// Performs a HTTP or HTTPS request to a destination URL + #[cfg(not(feature = "host-reqwest"))] + fn reqwest( + &self, + tasks: &dyn VirtualTaskManager, + url: &str, + method: &str, + options: ReqwestOptions, + headers: Vec<(String, String)>, + data: Option>, + ) -> Result { + Err(__WASI_ENOTSUP as u32) + } + + /// Performs a HTTP or HTTPS request to a destination URL + #[cfg(feature = "host-reqwest")] + fn reqwest( + &self, + tasks: &dyn VirtualTaskManager, + url: &str, + method: &str, + _options: ReqwestOptions, + headers: Vec<(String, String)>, + data: Option>, + ) -> Result { + use std::convert::TryFrom; + + let work = { + let url = url.to_string(); + let method = method.to_string(); + async move { + let method = reqwest::Method::try_from(method.as_str()).map_err(|err| { + debug!("failed to convert method ({}) - {}", method, err); + __WASI_EIO as u32 + })?; + + let client = reqwest::ClientBuilder::default().build().map_err(|err| { + debug!("failed to build reqwest client - {}", err); + __WASI_EIO as u32 + })?; + + let mut builder = client.request(method, url.as_str()); + for (header, val) in headers { + if let Ok(header) = + reqwest::header::HeaderName::from_bytes(header.as_bytes()) + { + builder = builder.header(header, val); + } else { + debug!("failed to parse header - {}", header); + } + } + + if let Some(data) = data { + builder = builder.body(reqwest::Body::from(data)); + } + + let request = builder.build().map_err(|err| { + debug!("failed to convert request (url={}) - {}", url.as_str(), err); + __WASI_EIO as u32 + })?; + + let response = client.execute(request) + .await + .map_err(|err| + { + debug!("failed to execute reqest - {}", err); + __WASI_EIO as u32 + })?; + + let status = response.status().as_u16(); + let status_text = response.status().as_str().to_string(); + let data = response.bytes().await.map_err(|err| { + debug!("failed to read response bytes - {}", err); + __WASI_EIO as u32 + })?; + let data = data.to_vec(); + + Ok(ReqwestResponse { + pos: 0usize, + ok: true, + status, + status_text, + redirected: false, + data: Some(data), + headers: Vec::new(), + }) + } + }; + + let (tx, rx) = std::sync::mpsc::channel(); + tasks + .block_on(Box::pin(async move { + let ret = work.await; + let _ = tx.send(ret); + })); + rx.try_recv().map_err(|_| __WASI_EIO)? + } + + /// Make a web socket connection to a particular URL + #[cfg(feature = "os")] + #[cfg(not(feature = "host-ws"))] + fn web_socket(&self, url: &str) -> Result, String> { + Err("not supported".to_string()) + } + + /// Make a web socket connection to a particular URL + #[cfg(feature = "os")] + #[cfg(feature = "host-ws")] + fn web_socket(&self, url: &str) -> Result, String> { + let url = url.to_string(); + let (tx_done, rx_done) = mpsc::unbounded_channel(); + self.task_shared(Box::new(move || + Box::pin(async move { + let ret = move || async move { + Box::new(TerminalWebSocket::new(url.as_str())).await + }; + let ret = ret().await; + let _ = tx_done.send(ret); + }) + )); + tokio::task::block_in_place(move || { + rx_done.blocking_recv() + .ok_or("failed to create web socket".to_string()) + }) + } + + /// Writes output to the console + fn stdout(&self, data: &[u8]) -> io::Result<()> { + let mut handle = io::stdout(); + handle.write_all(data) + } + + /// Writes output to the console + fn stderr(&self, data: &[u8]) -> io::Result<()> { + let mut handle = io::stderr(); + handle.write_all(data) + } + + /// Flushes the output to the console + fn flush(&self) -> io::Result<()> { + io::stdout().flush()?; + io::stderr().flush()?; + Ok(()) + } + + /// Writes output to the log + #[cfg(feature = "tracing")] + fn log(&self, text: String) -> io::Result<()> { + tracing::info!("{}", text); + Ok(()) + } + + /// Writes output to the log + #[cfg(not(feature = "tracing"))] + fn log(&self, text: String) -> io::Result<()> { + let text = format!("{}\r\n", text); + self.stderr(text.as_bytes()) + } + + /// Clears the terminal + fn cls(&self) -> io::Result<()> { + self.stdout("\x1B[H\x1B[2J".as_bytes()) + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct PluggableRuntimeImplementation +{ + pub bus: Arc + Send + Sync + 'static>, + pub networking: Arc, +} + +impl PluggableRuntimeImplementation +{ + pub fn set_bus_implementation(&mut self, bus: I) + where + I: VirtualBus + Sync, + { + self.bus = Arc::new(bus) + } + + pub fn set_networking_implementation(&mut self, net: I) + where + I: VirtualNetworking + Sync, + { + self.networking = Arc::new(net) + } +} + +impl Default +for PluggableRuntimeImplementation +{ + fn default() -> Self { + Self { + #[cfg(not(feature = "host-vnet"))] + networking: Arc::new(wasmer_vnet::UnsupportedVirtualNetworking::default()), + #[cfg(feature = "host-vnet")] + networking: Arc::new(wasmer_wasi_local_networking::LocalNetworking::default()), + bus: Arc::new(DefaultVirtualBus::default()), + } + } +} + +#[derive(Debug)] +pub struct DefaultTaskManager { + /// This is the tokio runtime used for ASYNC operations that is + /// used for non-javascript environments + runtime: std::sync::Arc, + /// List of periodic wakers to wake (this is used by IO subsystems) + /// that do not support async operations + periodic_wakers: Arc, tokio::sync::broadcast::Sender<()>)>> +} + +impl Default +for DefaultTaskManager { + fn default() -> Self { + let runtime: std::sync::Arc + = std::sync::Arc::new(Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + ); + let (tx, _) = tokio::sync::broadcast::channel(100); + Self { + runtime, + periodic_wakers: Arc::new(Mutex::new((Vec::new(), tx))) + } + } +} + +#[allow(unused_variables)] +#[cfg(not(feature = "sys-thread"))] +impl VirtualTaskManager +for DefaultTaskManager +{ + /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded + /// execution environments) they will need to do asynchronous work whenever the main + /// thread goes idle and this is the place to hook for that. + fn sleep_now(&self, id: WasiCallingId, ms: u128) -> Pin + Send + Sync + 'static>> { + if ms == 0 { + std::thread::yield_now(); + } else { + std::thread::sleep(std::time::Duration::from_millis(ms as u64)); + } + Box::pin(async move { + }) + } + + /// Starts an asynchronous task that will run on a shared worker pool + /// This task must not block the execution or it could cause a deadlock + fn task_shared( + &self, + task: Box< + dyn FnOnce() -> Pin + Send + 'static>> + Send + 'static, + >, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + /// Starts an asynchronous task on the local thread (by running it in a runtime) + fn block_on( + &self, + task: Pin>>, + ) + { + let _guard = self.runtime.enter(); + self.runtime.block_on(async move { + task.await; + }); + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool that has a stateful thread local variable + /// It is ok for this task to block execution and any async futures within its scope + fn task_wasm( + &self, + task: Box) + Send + 'static>, + store: Store, + module: Module, + spawn_type: SpawnType, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated( + &self, + task: Box, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated_async( + &self, + task: Box Pin + 'static>> + Send + 'static>, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + /// Returns the amount of parallelism that is possible on this platform + fn thread_parallelism(&self) -> Result { + Err(WasiThreadError::Unsupported) + } + + /// Returns a reference to the periodic wakers used by this task manager + fn periodic_wakers(&self) -> Arc, tokio::sync::broadcast::Sender<()>)>> { + self.periodic_wakers.clone() + } +} + +#[cfg(feature = "sys-thread")] +impl VirtualTaskManager +for DefaultTaskManager +{ + /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded + /// execution environments) they will need to do asynchronous work whenever the main + /// thread goes idle and this is the place to hook for that. + fn sleep_now(&self, _id: WasiCallingId, ms: u128) -> Pin + Send + Sync + 'static>> { + Box::pin(async move { + if ms == 0 { + tokio::task::yield_now().await; + } else { + tokio::time::sleep(std::time::Duration::from_millis(ms as u64)).await; + } + }) + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool that has a stateful thread local variable + /// It is ok for this task to block execution and any async futures within its scope + fn task_shared( + &self, + task: Box< + dyn FnOnce() -> Pin + Send + 'static>> + Send + 'static, + >, + ) -> Result<(), WasiThreadError> { + self.runtime.spawn(async move { + let fut = task(); + fut.await + }); + Ok(()) + } + + /// Starts an asynchronous task on the local thread (by running it in a runtime) + fn block_on( + &self, + task: Pin>>, + ) + { + let _guard = self.runtime.enter(); + self.runtime.block_on(async move { + task.await; + }); + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool that has a stateful thread local variable + /// It is ok for this task to block execution and any async futures within its scope + fn task_wasm( + &self, + task: Box) + Send + 'static>, + store: Store, + module: Module, + spawn_type: SpawnType, + ) -> Result<(), WasiThreadError> { + use wasmer::vm::VMSharedMemory; + + let memory: Option = match spawn_type { + SpawnType::CreateWithType(mem) => { + Some( + VMSharedMemory::new(&mem.ty, &mem.style) + .map_err(|err| { + error!("failed to create memory - {}", err); + }) + .unwrap() + .into() + ) + }, + SpawnType::NewThread(mem) => Some(mem), + SpawnType::Create => None, + }; + + std::thread::spawn(move || { + // Invoke the callback + task(store, module, memory); + }); + Ok(()) + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated( + &self, + task: Box, + ) -> Result<(), WasiThreadError> { + std::thread::spawn(move || { + task(); + }); + Ok(()) + } + + /// Starts an asynchronous task will will run on a dedicated thread + /// pulled from the worker pool. It is ok for this task to block execution + /// and any async futures within its scope + fn task_dedicated_async( + &self, + task: Box Pin + 'static>> + Send + 'static>, + ) -> Result<(), WasiThreadError> { + let runtime = self.runtime.clone(); + std::thread::spawn(move || { + let fut = task(); + runtime.block_on(fut); + }); + Ok(()) + } + + /// Number of concurrent threads supported on this machine + /// in a stable way (ideally we should aim for this number + /// of background threads) + fn thread_parallelism(&self) -> Result { + Ok( + std::thread::available_parallelism() + .map(|a| usize::from(a)) + .unwrap_or(8) + ) + } + + /// Returns a reference to the periodic wakers used by this task manager + fn periodic_wakers(&self) -> Arc, tokio::sync::broadcast::Sender<()>)>> { + self.periodic_wakers.clone() + } +} + +impl WasiRuntimeImplementation +for PluggableRuntimeImplementation +{ + fn bus<'a>(&'a self) -> Arc + Send + Sync + 'static> { + self.bus.clone() + } + + fn networking<'a>(&'a self) -> Arc { + self.networking.clone() + } +} diff --git a/lib/wasi/src/runtime/stdio.rs b/lib/wasi/src/runtime/stdio.rs new file mode 100644 index 00000000000..7b478b41a81 --- /dev/null +++ b/lib/wasi/src/runtime/stdio.rs @@ -0,0 +1,176 @@ +use std::sync::Arc; +use std::io::{self, Read, Write, Seek}; + +#[derive(Debug)] +pub struct RuntimeStdout { + runtime: Arc, +} + +impl RuntimeStdout { + pub fn new(runtime: Arc) -> Self { + Self { + runtime + } + } +} + +impl Read for RuntimeStdout { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stdout", + )) + } + + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stdout", + )) + } + + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stdout", + )) + } + + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stdout", + )) + } +} + +impl Seek for RuntimeStdout { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(io::Error::new(io::ErrorKind::Other, "can not seek stdout")) + } +} + +impl Write for RuntimeStdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.runtime.stdout(buf)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.runtime.flush() + } +} + +impl wasmer_vfs::VirtualFile for RuntimeStdout { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + 0 + } + + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + tracing::debug!("Calling VirtualFile::set_len on stderr; this is probably a bug"); + Err(wasmer_vfs::FsError::PermissionDenied) + } + + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } +} + +#[derive(Debug)] +pub struct RuntimeStderr { + runtime: Arc, +} + +impl RuntimeStderr { + pub fn new(runtime: Arc) -> Self { + Self { + runtime + } + } +} + +impl Read for RuntimeStderr { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stderr", + )) + } + + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stderr", + )) + } + + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stderr", + )) + } + + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(io::Error::new( + io::ErrorKind::Other, + "can not read from stderr", + )) + } +} + +impl Seek for RuntimeStderr { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(io::Error::new(io::ErrorKind::Other, "can not seek stderr")) + } +} + +impl Write for RuntimeStderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.runtime.stderr(buf)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.runtime.flush() + } +} + +impl wasmer_vfs::VirtualFile for RuntimeStderr { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + 0 + } + + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { + tracing::debug!("Calling VirtualFile::set_len on stderr; this is probably a bug"); + Err(wasmer_vfs::FsError::PermissionDenied) + } + + fn unlink(&mut self) -> wasmer_vfs::Result<()> { + Ok(()) + } +} diff --git a/lib/wasi/src/runtime/term.rs b/lib/wasi/src/runtime/term.rs new file mode 100644 index 00000000000..e1fb9b5ca47 --- /dev/null +++ b/lib/wasi/src/runtime/term.rs @@ -0,0 +1,119 @@ +#![allow(unused_imports)] +#[cfg(unix)] +use { + libc::{ + c_int, tcsetattr, termios, ECHO, ECHOE, ECHONL, ICANON, ICRNL, IEXTEN, ISIG, IXON, OPOST, TCSANOW, + }, + std::mem, + std::os::unix::io::AsRawFd, +}; + +#[cfg(unix)] +pub fn io_result(ret: libc::c_int) -> std::io::Result<()> { + match ret { + 0 => Ok(()), + _ => Err(std::io::Error::last_os_error()), + } +} + +#[cfg(unix)] +pub fn set_mode_no_echo() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag &= !ECHO; + termios.c_lflag &= !ECHOE; + termios.c_lflag &= !ISIG; + termios.c_lflag &= !IXON; + termios.c_lflag &= !IEXTEN; + termios.c_lflag &= !ICRNL; + termios.c_lflag &= !OPOST; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} + +#[cfg(unix)] +pub fn set_mode_echo() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag |= ECHO; + termios.c_lflag |= ECHOE; + termios.c_lflag |= ISIG; + termios.c_lflag |= IXON; + termios.c_lflag |= IEXTEN; + termios.c_lflag |= ICRNL; + termios.c_lflag |= OPOST; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} + +#[cfg(unix)] +pub fn set_mode_no_line_feeds() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag &= !ICANON; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} + +#[cfg(unix)] +pub fn set_mode_line_feeds() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag |= ICANON; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} + +#[cfg(unix)] +pub fn set_mode_no_line_feeds() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag &= !ONLCR; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} + +#[cfg(unix)] +pub fn set_mode_line_feeds() -> std::fs::File { + let tty = std::fs::File::open("/dev/tty").unwrap(); + let fd = tty.as_raw_fd(); + + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag |= ONLCR; + + unsafe { tcsetattr(fd, TCSANOW, &termios) }; + tty +} diff --git a/lib/wasi/src/runtime/ws.rs b/lib/wasi/src/runtime/ws.rs new file mode 100644 index 00000000000..86af35fc164 --- /dev/null +++ b/lib/wasi/src/runtime/ws.rs @@ -0,0 +1,20 @@ +#[cfg(feature = "async_ws")] +use async_trait::async_trait; + +use crate::WasiRuntimeImplementation; + +// This ABI implements a general purpose web socket +#[cfg_attr(feature = "async_ws", async_trait)] +pub trait WebSocketAbi { + fn set_onopen(&mut self, callback: Box); + + fn set_onclose(&mut self, callback: Box); + + fn set_onmessage(&mut self, callback: Box) + Send + 'static>, runtime: &dyn WasiRuntimeImplementation); + + #[cfg(feature = "async_ws")] + async fn send(&mut self, data: Vec) -> Result<(), String>; + + #[cfg(not(feature = "async_ws"))] + fn send(&mut self, data: Vec) -> Result<(), String>; +} diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index 61e2451d807..b71418e7d77 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -1,9 +1,13 @@ //! Builder system for configuring a [`WasiState`] and creating it. -use crate::state::{default_fs_backing, WasiFs, WasiState}; +#[cfg(feature = "os")] +use crate::bin_factory::CachedCompiledModules; +use crate::fs::{ArcFile, TmpFileSystem}; +use crate::state::{WasiFs, WasiState, WasiFsRoot}; use crate::syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}; -use crate::{WasiEnv, WasiFunctionEnv, WasiInodes}; +use crate::{WasiEnv, WasiFunctionEnv, WasiInodes, WasiControlPlane, PluggableRuntimeImplementation}; use generational_arena::Arena; +use rand::Rng; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; @@ -18,7 +22,7 @@ use wasmer_vfs::{FsError, VirtualFile}; /// Internal method only, users should call [`WasiState::new`]. pub(crate) fn create_wasi_state(program_name: &str) -> WasiStateBuilder { WasiStateBuilder { - args: vec![program_name.bytes().collect()], + args: vec![program_name.to_string()], ..WasiStateBuilder::default() } } @@ -41,16 +45,21 @@ pub(crate) fn create_wasi_state(program_name: &str) -> WasiStateBuilder { /// ``` #[derive(Default)] pub struct WasiStateBuilder { - args: Vec>, - envs: Vec<(Vec, Vec)>, + args: Vec, + envs: Vec<(String, Vec)>, preopens: Vec, + uses: Vec, + #[cfg(feature = "sys")] + map_commands: HashMap, vfs_preopens: Vec, + #[cfg(feature = "os")] + compiled_modules: Arc, #[allow(clippy::type_complexity)] setup_fs_fn: Option Result<(), String> + Send>>, stdout_override: Option>, stderr_override: Option>, stdin_override: Option>, - fs_override: Option>, + fs_override: Option, runtime_override: Option>, } @@ -61,6 +70,7 @@ impl std::fmt::Debug for WasiStateBuilder { .field("args", &self.args) .field("envs", &self.envs) .field("preopens", &self.preopens) + .field("uses", &self.uses) .field("setup_fs_fn exists", &self.setup_fs_fn.is_some()) .field("stdout_override exists", &self.stdout_override.is_some()) .field("stderr_override exists", &self.stderr_override.is_some()) @@ -89,6 +99,8 @@ pub enum WasiStateCreationError { WasiFsSetupError(String), #[error(transparent)] FileSystemError(FsError), + #[error("wasi inherit error: `{0}`")] + WasiInheritError(String), } fn validate_mapped_dir_alias(alias: &str) -> Result<(), WasiStateCreationError> { @@ -117,7 +129,7 @@ impl WasiStateBuilder { Value: AsRef<[u8]>, { self.envs - .push((key.as_ref().to_vec(), value.as_ref().to_vec())); + .push((String::from_utf8_lossy(key.as_ref()).to_string(), value.as_ref().to_vec())); self } @@ -129,7 +141,7 @@ impl WasiStateBuilder { where Arg: AsRef<[u8]>, { - self.args.push(arg.as_ref().to_vec()); + self.args.push(String::from_utf8_lossy(arg.as_ref()).to_string()); self } @@ -152,6 +164,53 @@ impl WasiStateBuilder { self } + /// Adds a container this module inherits from + pub fn use_webc(&mut self, webc: Name) -> &mut Self + where + Name: AsRef, + { + self.uses.push(webc.as_ref().to_string()); + self + } + + /// Adds a list of other containers this module inherits from + pub fn uses(&mut self, uses: I) -> &mut Self + where + I: IntoIterator, + { + uses.into_iter().for_each(|inherit| { + self.uses.push(inherit); + }); + self + } + + /// Map an atom to a local binary + #[cfg(feature = "sys")] + pub fn map_command(&mut self, name: Name, target: Target) -> &mut Self + where + Name: AsRef, + Target: AsRef, + { + let path_buf = PathBuf::from(target.as_ref().to_string()); + self.map_commands.insert(name.as_ref().to_string(), path_buf); + self + } + + /// Maps a series of atoms to the local binaries + #[cfg(feature = "sys")] + pub fn map_commands(&mut self, map_commands: I) -> &mut Self + where + I: IntoIterator, + Name: AsRef, + Target: AsRef, + { + map_commands.into_iter().for_each(|(name, target)| { + let path_buf = PathBuf::from(target.as_ref().to_string()); + self.map_commands.insert(name.as_ref().to_string(), path_buf); + }); + self + } + /// Add multiple arguments. /// /// Arguments must not contain the nul (0x0) byte @@ -312,9 +371,16 @@ impl WasiStateBuilder { /// Sets the FileSystem to be used with this WASI instance. /// /// This is usually used in case a custom `wasmer_vfs::FileSystem` is needed. - pub fn set_fs(&mut self, fs: Box) -> &mut Self { - self.fs_override = Some(fs); + pub fn set_fs(&mut self, fs: Box) -> &mut Self { + self.fs_override = Some(WasiFsRoot::Backing(Arc::new(fs))); + self + } + /// Sets a new sandbox FileSystem to be used with this WASI instance. + /// + /// This is usually used in case a custom `wasmer_vfs::FileSystem` is needed. + pub fn set_sandbox_fs(&mut self, fs: TmpFileSystem) -> &mut Self { + self.fs_override = Some(WasiFsRoot::Sandbox(Arc::new(fs))); self } @@ -328,11 +394,20 @@ impl WasiStateBuilder { /// Sets the WASI runtime implementation and overrides the default /// implementation - pub fn runtime(&mut self, runtime: R) -> &mut Self + pub fn runtime(&mut self, runtime: &Arc) -> &mut Self where R: crate::WasiRuntimeImplementation + Send + Sync + 'static, { - self.runtime_override = Some(Arc::new(runtime)); + self.runtime_override = Some(runtime.clone()); + self + } + + /// Sets the compiled modules to use with this builder (sharing the + /// cached modules is better for performance and memory consumption) + #[cfg(feature = "os")] + pub fn compiled_modules(&mut self, compiled_modules: &Arc) -> &mut Self { + let mut compiled_modules = compiled_modules.clone(); + std::mem::swap(&mut self.compiled_modules, &mut compiled_modules); self } @@ -356,17 +431,11 @@ impl WasiStateBuilder { /// to `mut self` for every _builder method_, but it will break /// existing code. It will be addressed in a next major release. pub fn build(&mut self) -> Result { - for (i, arg) in self.args.iter().enumerate() { - for b in arg.iter() { + for arg in self.args.iter() { + for b in arg.as_bytes().iter() { if *b == 0 { return Err(WasiStateCreationError::ArgumentContainsNulByte( - std::str::from_utf8(arg) - .unwrap_or(if i == 0 { - "Inner error: program name is invalid utf8!" - } else { - "Inner error: arg is invalid utf8!" - }) - .to_string(), + arg.clone(), )); } } @@ -378,7 +447,7 @@ impl WasiStateBuilder { } for (env_key, env_value) in self.envs.iter() { - match env_key.iter().find_map(|&ch| { + match env_key.as_bytes().iter().find_map(|&ch| { if ch == 0 { Some(InvalidCharacter::Nul) } else if ch == b'=' { @@ -391,7 +460,7 @@ impl WasiStateBuilder { return Err(WasiStateCreationError::EnvironmentVariableFormatError( format!( "found nul byte in env var key \"{}\" (key=value)", - String::from_utf8_lossy(env_key) + env_key ), )) } @@ -400,7 +469,7 @@ impl WasiStateBuilder { return Err(WasiStateCreationError::EnvironmentVariableFormatError( format!( "found equal sign in env var key \"{}\" (key=value)", - String::from_utf8_lossy(env_key) + env_key ), )) } @@ -412,14 +481,31 @@ impl WasiStateBuilder { return Err(WasiStateCreationError::EnvironmentVariableFormatError( format!( "found nul byte in env var value \"{}\" (key=value)", - String::from_utf8_lossy(env_value) + String::from_utf8_lossy(env_value), ), )); } } - let fs_backing = self.fs_override.take().unwrap_or_else(default_fs_backing); + // Get a reference to the runtime + let runtime = self.runtime_override.clone().unwrap_or_else( || { + Arc::new(PluggableRuntimeImplementation::default()) + }); + // Determine the STDIN + let stdin: Box = self.stdin_override + .take() + .map(|a| Box::new(ArcFile::new(a))) + .unwrap_or_else(|| { + Box::new(ArcFile::new(Box::new(super::Stdin::default()))) + }); + + // If we are running WASIX then we start a full sandbox FS + // otherwise we drop through to a default file system + let fs_backing = self.fs_override + .take() + .unwrap_or_else(|| WasiFsRoot::Sandbox(Arc::new(TmpFileSystem::new()))); + // self.preopens are checked in [`PreopenDirBuilder::build`] let inodes = RwLock::new(crate::state::WasiInodes { arena: Arena::new(), @@ -433,16 +519,14 @@ impl WasiStateBuilder { inodes.deref_mut(), &self.preopens, &self.vfs_preopens, - fs_backing, + fs_backing ) .map_err(WasiStateCreationError::WasiFsCreationError)?; - + // set up the file system, overriding base files and calling the setup function - if let Some(stdin_override) = self.stdin_override.take() { - wasi_fs - .swap_file(inodes.deref(), __WASI_STDIN_FILENO, stdin_override) - .map_err(WasiStateCreationError::FileSystemError)?; - } + wasi_fs + .swap_file(inodes.deref(), __WASI_STDIN_FILENO, stdin) + .map_err(WasiStateCreationError::FileSystemError)?; if let Some(stdout_override) = self.stdout_override.take() { wasi_fs @@ -462,18 +546,25 @@ impl WasiStateBuilder { } wasi_fs }; + let inodes = Arc::new(inodes); Ok(WasiState { fs: wasi_fs, - inodes: Arc::new(inodes), + secret: rand::thread_rng().gen::<[u8; 32]>(), + inodes, args: self.args.clone(), + preopen: self.vfs_preopens.clone(), threading: Default::default(), + futexs: Default::default(), + clock_offset: Default::default(), + bus: Default::default(), + runtime, envs: self .envs .iter() .map(|(key, value)| { let mut env = Vec::with_capacity(key.len() + value.len() + 1); - env.extend_from_slice(key); + env.extend_from_slice(key.as_bytes()); env.push(b'='); env.extend_from_slice(value); @@ -497,12 +588,47 @@ impl WasiStateBuilder { &mut self, store: &mut impl AsStoreMut, ) -> Result { - let state = self.build()?; + let control_plane = WasiControlPlane::default(); + self.finalize_with(store, &control_plane) + } + + /// Consumes the [`WasiStateBuilder`] and produces a [`WasiEnv`] + /// with a particular control plane + /// + /// Returns the error from `WasiFs::new` if there's an error. + /// + /// # Calling `finalize` multiple times + /// + /// Calling this method multiple times might not produce a + /// determinisic result. This method is calling [Self::build], + /// which is changing the builder's internal state. See + /// [Self::build]'s documentation to learn more. + pub fn finalize_with( + &mut self, + store: &mut impl AsStoreMut, + control_plane: &WasiControlPlane, + ) -> Result { + let state = Arc::new(self.build()?); + let runtime = state.runtime.clone(); + + let process = control_plane.new_process(); + let thread = process.new_thread(); + + let env = WasiEnv::new_ext( + state, + #[cfg(feature = "os")] + self.compiled_modules.clone(), + process, + thread, + runtime + ); + + #[cfg(feature = "os")] + env.uses(self.uses.clone())?; + #[cfg(feature = "os")] + #[cfg(feature = "sys")] + env.map_commands(self.map_commands.clone())?; - let mut env = WasiEnv::new(state); - if let Some(runtime) = self.runtime_override.as_ref() { - env.runtime = runtime.clone(); - } Ok(WasiFunctionEnv::new(store, env)) } } @@ -518,7 +644,7 @@ pub struct PreopenDirBuilder { } /// The built version of `PreopenDirBuilder` -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub(crate) struct PreopenedDir { pub(crate) path: PathBuf, pub(crate) alias: Option, diff --git a/lib/wasi/src/state/guard.rs b/lib/wasi/src/state/guard.rs index d3a84ffcabd..32fd8133254 100644 --- a/lib/wasi/src/state/guard.rs +++ b/lib/wasi/src/state/guard.rs @@ -1,45 +1,470 @@ +use tokio::sync::mpsc; +use wasmer_vnet::{net_error_into_io_err, NetworkError}; + +use crate::VirtualTaskManager; + use super::*; use std::{ io::{Read, Seek}, - sync::{RwLockReadGuard, RwLockWriteGuard}, + sync::RwLockReadGuard, future::Future, pin::Pin, task::Poll, }; -#[derive(Debug)] -pub(crate) struct InodeValFileReadGuard<'a> { - pub(crate) guard: RwLockReadGuard<'a, Kind>, +pub(crate) enum InodeValFilePollGuardMode { + File(Arc>>), + EventNotifications { + immediate: bool, + waker: Mutex>, + counter: Arc, + wakers: Arc>>> + }, + Socket(InodeSocket) } -impl<'a> Deref for InodeValFileReadGuard<'a> { - type Target = Option>; - fn deref(&self) -> &Self::Target { - if let Kind::File { handle, .. } = self.guard.deref() { - return handle; +pub(crate) struct InodeValFilePollGuard { + pub(crate) fd: u32, + pub(crate) mode: InodeValFilePollGuardMode, + pub(crate) subscriptions: HashMap, + pub(crate) tasks: Arc, +} +impl<'a> InodeValFilePollGuard { + pub(crate) fn new(fd: u32, guard: &Kind, subscriptions: HashMap, tasks: Arc) -> Option { + let mode = match guard.deref() { + Kind::EventNotifications { counter, wakers, immediate, .. } => { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let immediate = { + let mut wakers = wakers.lock().unwrap(); + wakers.push_back(tx); + immediate.compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed).is_ok() + }; + InodeValFilePollGuardMode::EventNotifications { + immediate, + waker: Mutex::new(rx), + counter: counter.clone(), + wakers: wakers.clone(), + } + }, + Kind::Socket { socket } => InodeValFilePollGuardMode::Socket(socket.clone()), + Kind::File { handle, .. } => { + if let Some(handle) = handle { + InodeValFilePollGuardMode::File(handle.clone()) + } else { + return None; + } + }, + _ => { + return None; + } + }; + Some( + Self { + fd, + mode, + subscriptions, + tasks + } + ) + } +} + +impl std::fmt::Debug +for InodeValFilePollGuard +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.mode { + InodeValFilePollGuardMode::File(..) => write!(f, "guard-file"), + InodeValFilePollGuardMode::EventNotifications { .. } => write!(f, "guard-notifications"), + InodeValFilePollGuardMode::Socket(socket) => { + let socket = socket.inner.read().unwrap(); + match socket.kind { + InodeSocketKind::TcpListener(..) => write!(f, "guard-tcp-listener"), + InodeSocketKind::TcpStream(..) => write!(f, "guard-tcp-stream"), + InodeSocketKind::UdpSocket(..) => write!(f, "guard-udp-socket"), + InodeSocketKind::Raw(..) => write!(f, "guard-raw-socket"), + InodeSocketKind::HttpRequest(..) => write!(f, "guard-http-request"), + InodeSocketKind::WebSocket(..) => write!(f, "guard-web-socket"), + _ => write!(f, "guard-socket") + } + } + } + } +} + +impl InodeValFilePollGuard { + pub fn bytes_available_read(&self) -> wasmer_vfs::Result> { + match &self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.bytes_available_read() + }, + InodeValFilePollGuardMode::EventNotifications { counter, .. } => { + Ok( + Some(counter.load(std::sync::atomic::Ordering::Acquire) as usize) + ) + }, + InodeValFilePollGuardMode::Socket(socket) => { + socket.peek() + .map(|a| Some(a)) + .map_err(fs_error_from_wasi_err) + } + } + } + + pub fn bytes_available_write(&self) -> wasmer_vfs::Result> { + match &self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.bytes_available_write() + }, + InodeValFilePollGuardMode::EventNotifications { wakers, .. } => { + let wakers = wakers.lock().unwrap(); + Ok( + Some(wakers.len()) + ) + }, + InodeValFilePollGuardMode::Socket(socket) => { + if socket.can_write() { + Ok(Some(4096)) + } else { + Ok(Some(0)) + } + } + } + } + + pub fn is_open(&self) -> bool{ + match &self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.is_open() + }, + InodeValFilePollGuardMode::EventNotifications { .. } | + InodeValFilePollGuardMode::Socket(..) => { + true + } + } + } + + pub async fn wait(&self) -> Vec<__wasi_event_t> { + InodeValFilePollGuardJoin::new(self).await + } +} + +struct InodeValFilePollGuardJoin<'a> { + mode: &'a InodeValFilePollGuardMode, + subscriptions: HashMap, + tasks: Arc, +} +impl<'a> InodeValFilePollGuardJoin<'a> { + fn new(guard: &'a InodeValFilePollGuard) -> Self { + Self { + mode: &guard.mode, + subscriptions: guard.subscriptions.clone(), + tasks: guard.tasks.clone(), + } + } +} +impl<'a> Future +for InodeValFilePollGuardJoin<'a> +{ + type Output = Vec<__wasi_event_t>; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let mut has_read = None; + let mut has_write = None; + let mut has_close = None; + let mut has_hangup = false; + + let register_root_waker = self.tasks + .register_root_waker(); + + let mut ret = Vec::new(); + for (set, s) in self.subscriptions.iter() { + for in_event in iterate_poll_events(*set) { + match in_event { + PollEvent::PollIn => { has_read = Some(s.clone()); }, + PollEvent::PollOut => { has_write = Some(s.clone()); }, + PollEvent::PollHangUp => { + has_hangup = true; + has_close = Some(s.clone()); + } + PollEvent::PollError | + PollEvent::PollInvalid => { + if has_hangup == false { + has_close = Some(s.clone()); + } + } + } + } + } + if let Some(s) = has_close.as_ref() { + let is_closed = match self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.poll_close_ready(cx, ®ister_root_waker).is_ready() + }, + InodeValFilePollGuardMode::EventNotifications { .. } => { + false + }, + InodeValFilePollGuardMode::Socket(socket) => { + let inner = socket.inner.read().unwrap(); + if let InodeSocketKind::Closed = inner.kind { + true + } else { + if has_read.is_some() || has_write.is_some() + { + // this will be handled in the read/write poll instead + false + } else { + // we do a read poll which will error out if its closed + match socket.poll_read_ready(cx) { + Poll::Ready(Err(NetworkError::ConnectionAborted)) | + Poll::Ready(Err(NetworkError::ConnectionRefused)) | + Poll::Ready(Err(NetworkError::ConnectionReset)) | + Poll::Ready(Err(NetworkError::BrokenPipe)) | + Poll::Ready(Err(NetworkError::NotConnected)) | + Poll::Ready(Err(NetworkError::UnexpectedEof)) => { + true + }, + _ => { + false + } + } + } + } + } + }; + if is_closed { + ret.push(__wasi_event_t { + userdata: s.user_data, + error: __WASI_ESUCCESS, + type_: s.event_type.raw_tag(), + u: { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: if has_hangup { + __WASI_EVENT_FD_READWRITE_HANGUP + } else { 0 }, + }, + } + }, + }); + } + } + if let Some(s) = has_read { + let mut poll_result = match &self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.poll_read_ready(cx, ®ister_root_waker) + }, + InodeValFilePollGuardMode::EventNotifications { waker, counter, immediate, .. } => { + if *immediate { + let cnt = counter.load(Ordering::Acquire); + Poll::Ready(Ok(cnt as usize)) + } else { + let counter = counter.clone(); + let mut waker = waker.lock().unwrap(); + let mut notifications = Pin::new(waker.deref_mut()); + notifications.poll_recv(cx).map(|_| { + let cnt = counter.load(Ordering::Acquire); + Ok(cnt as usize) + }) + } + }, + InodeValFilePollGuardMode::Socket(socket) => { + socket.poll_read_ready(cx) + .map_err(net_error_into_io_err) + .map_err(Into::::into) + } + }; + if let Some(s) = has_close.as_ref() { + poll_result = match poll_result { + Poll::Ready(Err(FsError::ConnectionAborted)) | + Poll::Ready(Err(FsError::ConnectionRefused)) | + Poll::Ready(Err(FsError::ConnectionReset)) | + Poll::Ready(Err(FsError::BrokenPipe)) | + Poll::Ready(Err(FsError::NotConnected)) | + Poll::Ready(Err(FsError::UnexpectedEof)) => { + ret.push(__wasi_event_t { + userdata: s.user_data, + error: __WASI_ESUCCESS, + type_: s.event_type.raw_tag(), + u: { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: if has_hangup { + __WASI_EVENT_FD_READWRITE_HANGUP + } else { 0 }, + }, + } + }, + }); + Poll::Pending + } + a => a + }; + } + if let Poll::Ready(bytes_available) = poll_result { + ret.push(__wasi_event_t { + userdata: s.user_data, + error: bytes_available.clone().map(|_| __WASI_ESUCCESS).unwrap_or_else(fs_error_into_wasi_err), + type_: s.event_type.raw_tag(), + u: { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: bytes_available.unwrap_or_default() as u64, + flags: 0, + }, + } + }, + }); + } + } + if let Some(s) = has_write { + let mut poll_result = match self.mode { + InodeValFilePollGuardMode::File(file) => { + let guard = file.read().unwrap(); + guard.poll_write_ready(cx, ®ister_root_waker) + }, + InodeValFilePollGuardMode::EventNotifications { waker, counter, immediate, .. } => { + if *immediate { + let cnt = counter.load(Ordering::Acquire); + Poll::Ready(Ok(cnt as usize)) + } else { + let counter = counter.clone(); + let mut waker = waker.lock().unwrap(); + let mut notifications = Pin::new(waker.deref_mut()); + notifications.poll_recv(cx).map(|_| { + let cnt = counter.load(Ordering::Acquire); + Ok(cnt as usize) + }) + } + }, + InodeValFilePollGuardMode::Socket(socket) => { + socket.poll_write_ready(cx) + .map_err(net_error_into_io_err) + .map_err(Into::::into) + } + }; + if let Some(s) = has_close.as_ref() { + poll_result = match poll_result { + Poll::Ready(Err(FsError::ConnectionAborted)) | + Poll::Ready(Err(FsError::ConnectionRefused)) | + Poll::Ready(Err(FsError::ConnectionReset)) | + Poll::Ready(Err(FsError::BrokenPipe)) | + Poll::Ready(Err(FsError::NotConnected)) | + Poll::Ready(Err(FsError::UnexpectedEof)) => { + ret.push(__wasi_event_t { + userdata: s.user_data, + error: __WASI_ESUCCESS, + type_: s.event_type.raw_tag(), + u: { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: if has_hangup { + __WASI_EVENT_FD_READWRITE_HANGUP + } else { 0 }, + }, + } + }, + }); + Poll::Pending + } + a => a + }; + } + if let Poll::Ready(bytes_available) = poll_result { + ret.push(__wasi_event_t { + userdata: s.user_data, + error: bytes_available.clone().map(|_| __WASI_ESUCCESS).unwrap_or_else(fs_error_into_wasi_err), + type_: s.event_type.raw_tag(), + u: { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: bytes_available.unwrap_or_default() as u64, + flags: 0, + }, + } + }, + }); + } + } + if ret.len() > 0 { + Poll::Ready(ret) + } else { + Poll::Pending } - unreachable!() } } #[derive(Debug)] -pub struct InodeValFileWriteGuard<'a> { - pub(crate) guard: RwLockWriteGuard<'a, Kind>, +pub(crate) struct InodeValFileReadGuard { + #[allow(dead_code)] + file: Arc>>, + guard: RwLockReadGuard<'static, Box>, +} + +impl InodeValFileReadGuard { + pub(crate) fn new(file: &Arc>>) -> Self { + let guard = file.read().unwrap(); + Self { + file: file.clone(), + guard: unsafe { std::mem::transmute(guard) } + } + } +} + +impl InodeValFileReadGuard { + pub fn into_poll_guard(self, fd: u32, subscriptions: HashMap, tasks: Arc) -> InodeValFilePollGuard { + InodeValFilePollGuard { + fd, + subscriptions, + mode: InodeValFilePollGuardMode::File(self.file), + tasks, + } + } } -impl<'a> Deref for InodeValFileWriteGuard<'a> { - type Target = Option>; +impl Deref for InodeValFileReadGuard { + type Target = dyn VirtualFile + Send + Sync + 'static; fn deref(&self) -> &Self::Target { - if let Kind::File { handle, .. } = self.guard.deref() { - return handle; + self.guard.deref().deref() + } +} + +#[derive(Debug)] +pub struct InodeValFileWriteGuard { + #[allow(dead_code)] + file: Arc>>, + guard: RwLockWriteGuard<'static, Box>, +} + +impl InodeValFileWriteGuard { + pub(crate) fn new(file: &Arc>>) -> Self { + let guard = file.write().unwrap(); + Self { + file: file.clone(), + guard: unsafe { std::mem::transmute(guard) } } - unreachable!() + } + pub(crate) fn swap(&mut self, mut file: Box) -> Box { + std::mem::swap(self.guard.deref_mut(), &mut file); + file + } +} + +impl<'a> Deref for InodeValFileWriteGuard { + type Target = dyn VirtualFile + Send + Sync + 'static; + fn deref(&self) -> &Self::Target { + self.guard.deref().deref() } } -impl<'a> DerefMut for InodeValFileWriteGuard<'a> { +impl DerefMut for InodeValFileWriteGuard { fn deref_mut(&mut self) -> &mut Self::Target { - if let Kind::File { handle, .. } = self.guard.deref_mut() { - return handle; - } - unreachable!() + self.guard.deref_mut().deref_mut() } } @@ -69,26 +494,34 @@ impl WasiStateFileGuard { } } - pub fn lock_read<'a>( + pub fn lock_read( &self, - inodes: &'a RwLockReadGuard, - ) -> InodeValFileReadGuard<'a> { + inodes: &RwLockReadGuard, + ) -> Option { let guard = inodes.arena[self.inode].read(); - if let Kind::File { .. } = guard.deref() { - InodeValFileReadGuard { guard } + if let Kind::File { handle, .. } = guard.deref() { + if let Some(handle) = handle.as_ref() { + Some(InodeValFileReadGuard::new(handle)) + } else { + None + } } else { // Our public API should ensure that this is not possible unreachable!("Non-file found in standard device location") } } - pub fn lock_write<'a>( + pub fn lock_write( &self, - inodes: &'a RwLockReadGuard, - ) -> InodeValFileWriteGuard<'a> { - let guard = inodes.arena[self.inode].write(); - if let Kind::File { .. } = guard.deref() { - InodeValFileWriteGuard { guard } + inodes: &RwLockReadGuard, + ) -> Option { + let guard = inodes.arena[self.inode].read(); + if let Kind::File { handle, .. } = guard.deref() { + if let Some(handle) = handle.as_ref() { + Some(InodeValFileWriteGuard::new(handle)) + } else { + None + } } else { // Our public API should ensure that this is not possible unreachable!("Non-file found in standard device location") @@ -100,7 +533,7 @@ impl VirtualFile for WasiStateFileGuard { fn last_accessed(&self) -> u64 { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.last_accessed() } else { 0 @@ -110,7 +543,7 @@ impl VirtualFile for WasiStateFileGuard { fn last_modified(&self) -> u64 { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.last_modified() } else { 0 @@ -120,7 +553,7 @@ impl VirtualFile for WasiStateFileGuard { fn created_time(&self) -> u64 { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.created_time() } else { 0 @@ -130,7 +563,7 @@ impl VirtualFile for WasiStateFileGuard { fn size(&self) -> u64 { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.size() } else { 0 @@ -140,7 +573,7 @@ impl VirtualFile for WasiStateFileGuard { fn set_len(&mut self, new_size: u64) -> Result<(), FsError> { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.set_len(new_size) } else { Err(FsError::IOError) @@ -150,7 +583,7 @@ impl VirtualFile for WasiStateFileGuard { fn unlink(&mut self) -> Result<(), FsError> { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.unlink() } else { Err(FsError::IOError) @@ -160,7 +593,7 @@ impl VirtualFile for WasiStateFileGuard { fn sync_to_disk(&self) -> Result<(), FsError> { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.sync_to_disk() } else { Err(FsError::IOError) @@ -170,7 +603,7 @@ impl VirtualFile for WasiStateFileGuard { fn bytes_available(&self) -> Result { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.bytes_available() } else { Err(FsError::IOError) @@ -180,7 +613,7 @@ impl VirtualFile for WasiStateFileGuard { fn bytes_available_read(&self) -> Result, FsError> { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.bytes_available_read() } else { Err(FsError::IOError) @@ -190,7 +623,7 @@ impl VirtualFile for WasiStateFileGuard { fn bytes_available_write(&self) -> Result, FsError> { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.bytes_available_write() } else { Err(FsError::IOError) @@ -200,7 +633,7 @@ impl VirtualFile for WasiStateFileGuard { fn is_open(&self) -> bool { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.is_open() } else { false @@ -210,7 +643,7 @@ impl VirtualFile for WasiStateFileGuard { fn get_fd(&self) -> Option { let inodes = self.inodes.read().unwrap(); let guard = self.lock_read(&inodes); - if let Some(file) = guard.deref() { + if let Some(file) = guard.as_ref() { file.get_fd() } else { None @@ -222,7 +655,7 @@ impl Write for WasiStateFileGuard { fn write(&mut self, buf: &[u8]) -> std::io::Result { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut () { file.write(buf) } else { Err(std::io::ErrorKind::Unsupported.into()) @@ -232,7 +665,7 @@ impl Write for WasiStateFileGuard { fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.write_vectored(bufs) } else { Err(std::io::ErrorKind::Unsupported.into()) @@ -242,7 +675,7 @@ impl Write for WasiStateFileGuard { fn flush(&mut self) -> std::io::Result<()> { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.flush() } else { Err(std::io::ErrorKind::Unsupported.into()) @@ -254,7 +687,7 @@ impl Read for WasiStateFileGuard { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.read(buf) } else { Err(std::io::ErrorKind::Unsupported.into()) @@ -264,7 +697,7 @@ impl Read for WasiStateFileGuard { fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.read_vectored(bufs) } else { Err(std::io::ErrorKind::Unsupported.into()) @@ -276,7 +709,7 @@ impl Seek for WasiStateFileGuard { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { let inodes = self.inodes.read().unwrap(); let mut guard = self.lock_write(&inodes); - if let Some(file) = guard.deref_mut() { + if let Some(file) = guard.as_mut() { file.seek(pos) } else { Err(std::io::ErrorKind::Unsupported.into()) diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index 69f83ee548e..b6a74384e48 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -20,26 +20,46 @@ mod guard; mod pipe; mod socket; mod types; +mod thread; +mod parking; pub use self::builder::*; pub use self::guard::*; pub use self::pipe::*; pub use self::socket::*; pub use self::types::*; +pub use self::guard::*; +pub use self::thread::*; +pub use self::parking::*; +use crate::WasiCallingId; +use crate::WasiFunctionEnv; +use crate::WasiRuntimeImplementation; +#[cfg(feature = "os")] +use crate::bin_factory::BinaryPackage; use crate::syscalls::types::*; use crate::utils::map_io_err; -use crate::WasiBusProcessId; -use crate::WasiThread; -use crate::WasiThreadId; +use cooked_waker::ViaRawPointer; +use cooked_waker::Wake; +use cooked_waker::WakeRef; +use derivative::Derivative; use generational_arena::Arena; pub use generational_arena::Index as Inode; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; +use wasmer::Store; +use wasmer_vbus::VirtualBusCalled; +use wasmer_vbus::VirtualBusInvocation; +use wasmer_vfs::FileOpener; use std::borrow::Cow; +use std::cell::RefCell; use std::collections::HashMap; +use std::collections::HashSet; use std::collections::VecDeque; -use std::sync::mpsc; +use std::sync::Condvar; +use std::sync::MutexGuard; use std::sync::Arc; +use std::task::Waker; +use std::time::Duration; use std::{ borrow::Borrow, io::Write, @@ -51,11 +71,6 @@ use std::{ }, }; use tracing::{debug, trace}; -use wasmer_vbus::BusSpawnedProcess; -use wasmer_wasi_types::wasi::{ - Errno, Fd as WasiFd, Fdflags, Fdstat, Filesize, Filestat, Filetype, Preopentype, Rights, -}; -use wasmer_wasi_types::wasi::{Prestat, PrestatEnum}; use wasmer_vfs::{FileSystem, FsError, OpenOptions, VirtualFile}; @@ -99,7 +114,7 @@ pub const MAX_SYMLINKS: u32 = 128; pub struct InodeVal { pub stat: RwLock, pub is_preopened: bool, - pub name: String, + pub name: Cow<'static, str>, pub kind: RwLock, } @@ -121,7 +136,7 @@ pub enum Kind { File { /// The open file, if it's open #[cfg_attr(feature = "enable-serde", serde(skip))] - handle: Option>, + handle: Option>>>, /// The path on the host system where the file is located /// This is deprecated and will be removed soon path: PathBuf, @@ -176,22 +191,28 @@ pub enum Kind { }, EventNotifications { /// Used for event notifications by the user application or operating system + /// (positive number means there are events waiting to be processed) counter: Arc, /// Flag that indicates if this is operating is_semaphore: bool, /// Receiver that wakes sleeping threads #[cfg_attr(feature = "enable-serde", serde(skip))] - wakers: Arc>>>, + wakers: Arc>>>, + /// Immediate waker + immediate: Arc, }, } #[derive(Debug, Clone)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Fd { - pub rights: Rights, - pub rights_inheriting: Rights, - pub flags: Fdflags, - pub offset: u64, + /// The reference count is only increased when the FD is + /// duplicates - fd_close will not kill the inode until this reaches zero + pub ref_cnt: Arc, + pub rights: __wasi_rights_t, + pub rights_inheriting: __wasi_rights_t, + pub flags: __wasi_fdflags_t, + pub offset: Arc, /// Flags that determine how the [`Fd`] can be used. /// /// Used when reopening a [`VirtualFile`] during [`WasiState`] deserialization. @@ -298,12 +319,16 @@ impl WasiInodes { fn std_dev_get<'a>( &'a self, fd_map: &RwLock>, - fd: WasiFd, - ) -> Result, FsError> { + fd: __wasi_fd_t, + ) -> Result { if let Some(fd) = fd_map.read().unwrap().get(&fd) { let guard = self.arena[fd.inode].read(); - if let Kind::File { .. } = guard.deref() { - Ok(InodeValFileReadGuard { guard }) + if let Kind::File { handle, .. } = guard.deref() { + if let Some(handle) = handle { + Ok(InodeValFileReadGuard::new(handle)) + } else { + Err(FsError::NotAFile) + } } else { // Our public API should ensure that this is not possible Err(FsError::NotAFile) @@ -318,12 +343,16 @@ impl WasiInodes { fn std_dev_get_mut<'a>( &'a self, fd_map: &RwLock>, - fd: WasiFd, - ) -> Result, FsError> { + fd: __wasi_fd_t, + ) -> Result { if let Some(fd) = fd_map.read().unwrap().get(&fd) { - let guard = self.arena[fd.inode].write(); - if let Kind::File { .. } = guard.deref() { - Ok(InodeValFileWriteGuard { guard }) + let guard = self.arena[fd.inode].read(); + if let Kind::File { handle, .. } = guard.deref() { + if let Some(handle) = handle { + Ok(InodeValFileWriteGuard::new(handle)) + } else { + Err(FsError::NotAFile) + } } else { // Our public API should ensure that this is not possible Err(FsError::NotAFile) @@ -335,6 +364,65 @@ impl WasiInodes { } } +#[derive(Debug, Clone)] +pub enum WasiFsRoot { + Sandbox(Arc), + Backing(Arc>) +} + +impl FileSystem +for WasiFsRoot +{ + fn read_dir(&self, path: &Path) -> wasmer_vfs::Result { + match self { + WasiFsRoot::Sandbox(fs) => fs.read_dir(path), + WasiFsRoot::Backing(fs) => fs.read_dir(path) + } + } + fn create_dir(&self, path: &Path) -> wasmer_vfs::Result<()> { + match self { + WasiFsRoot::Sandbox(fs) => fs.create_dir(path), + WasiFsRoot::Backing(fs) => fs.create_dir(path) + } + } + fn remove_dir(&self, path: &Path) -> wasmer_vfs::Result<()> { + match self { + WasiFsRoot::Sandbox(fs) => fs.remove_dir(path), + WasiFsRoot::Backing(fs) => fs.remove_dir(path) + } + } + fn rename(&self, from: &Path, to: &Path) -> wasmer_vfs::Result<()> { + match self { + WasiFsRoot::Sandbox(fs) => fs.rename(from, to), + WasiFsRoot::Backing(fs) => fs.rename(from, to) + } + } + fn metadata(&self, path: &Path) -> wasmer_vfs::Result { + match self { + WasiFsRoot::Sandbox(fs) => fs.metadata(path), + WasiFsRoot::Backing(fs) => fs.metadata(path) + } + } + fn symlink_metadata(&self, path: &Path) -> wasmer_vfs::Result { + match self { + WasiFsRoot::Sandbox(fs) => fs.symlink_metadata(path), + WasiFsRoot::Backing(fs) => fs.symlink_metadata(path) + } + } + fn remove_file(&self, path: &Path) -> wasmer_vfs::Result<()> { + match self { + WasiFsRoot::Sandbox(fs) => fs.remove_file(path), + WasiFsRoot::Backing(fs) => fs.remove_file(path) + } + } + fn new_open_options(&self) -> OpenOptions { + match self { + WasiFsRoot::Sandbox(fs) => fs.new_open_options(), + WasiFsRoot::Backing(fs) => fs.new_open_options() + } + } +} + /// Warning, modifying these fields directly may cause invariants to break and /// should be considered unsafe. These fields may be made private in a future release #[derive(Debug)] @@ -343,17 +431,76 @@ pub struct WasiFs { //pub repo: Repo, pub preopen_fds: RwLock>, pub name_map: HashMap, - pub fd_map: RwLock>, + pub fd_map: Arc>>, pub next_fd: AtomicU32, inode_counter: AtomicU64, pub current_dir: Mutex, pub is_wasix: AtomicBool, - #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_fs_backing"))] - pub fs_backing: Box, + #[cfg_attr(feature = "enable-serde", serde(skip, default))] + pub root_fs: WasiFsRoot, + pub has_unioned: Arc>>, +} + +impl WasiFs +{ + /// Forking the WasiState is used when either fork or vfork is called + pub fn fork(&self) -> Self + { + let fd_map = self.fd_map.read().unwrap().clone(); + for fd in fd_map.values() { + fd.ref_cnt.fetch_add(1, Ordering::Relaxed); + } + Self { + preopen_fds: RwLock::new(self.preopen_fds.read().unwrap().clone()), + name_map: self.name_map.clone(), + fd_map: Arc::new(RwLock::new(fd_map)), + next_fd: AtomicU32::new(self.next_fd.load(Ordering::Acquire)), + inode_counter: AtomicU64::new(self.inode_counter.load(Ordering::Acquire)), + current_dir: Mutex::new(self.current_dir.lock().unwrap().clone()), + is_wasix: AtomicBool::new(self.is_wasix.load(Ordering::Acquire)), + root_fs: self.root_fs.clone(), + has_unioned: Arc::new(Mutex::new(HashSet::new())) + } + } + + /// Closes all the file handles + pub fn close_all(&self, inodes: &WasiInodes) { + let mut guard = self.fd_map.write().unwrap(); + let fds = { + guard.iter().map(|a| *a.0).collect::>() + }; + + for fd in fds { + _ = self.close_fd_ext(inodes, &mut guard, fd); + } + } + + /// Will conditionally union the binary file system with this one + /// if it has not already been unioned + #[cfg(feature = "os")] + pub fn conditional_union(&self, binary: &BinaryPackage) -> bool { + let sandbox_fs = match &self.root_fs { + WasiFsRoot::Sandbox(fs) => fs, + WasiFsRoot::Backing(_) => { + tracing::error!("can not perform a union on a backing file system"); + return false; + } + }; + let package_name = binary.package_name.to_string(); + let mut guard = self.has_unioned.lock().unwrap(); + if guard.contains(&package_name) == false { + guard.insert(package_name); + + if let Some(fs) = binary.webc_fs.clone() { + sandbox_fs.union(&fs); + } + } + true + } } /// Returns the default filesystem backing -pub(crate) fn default_fs_backing() -> Box { +pub fn default_fs_backing() -> Box { cfg_if::cfg_if! { if #[cfg(feature = "host-fs")] { Box::new(wasmer_vfs::host_fs::FileSystem::default()) @@ -407,9 +554,12 @@ impl WasiFs { inodes: &mut WasiInodes, preopens: &[PreopenedDir], vfs_preopens: &[String], - fs_backing: Box, + fs_backing: WasiFsRoot ) -> Result { - let (wasi_fs, root_inode) = Self::new_init(fs_backing, inodes)?; + let (wasi_fs, root_inode) = Self::new_init( + fs_backing, + inodes + )?; for preopen_name in vfs_preopens { let kind = Kind::Dir { @@ -472,7 +622,7 @@ impl WasiFs { &alias ); let cur_dir_metadata = wasi_fs - .fs_backing + .root_fs .metadata(path) .map_err(|e| format!("Could not get metadata for file {:?}: {}", path, e))?; @@ -581,22 +731,35 @@ impl WasiFs { Ok(wasi_fs) } + /// Converts a relative path into an absolute path + pub(crate) fn relative_path_to_absolute(&self, mut path: String) -> String + { + if path.starts_with("./") { + let current_dir = self.current_dir.lock().unwrap(); + path = format!("{}{}", current_dir.as_str(), &path[1..]); + path = path.replace("//", "/").to_string(); + } + path + } + /// Private helper function to init the filesystem, called in `new` and /// `new_with_preopen` fn new_init( - fs_backing: Box, + fs_backing: WasiFsRoot, inodes: &mut WasiInodes, ) -> Result<(Self, Inode), String> { debug!("Initializing WASI filesystem"); + let wasi_fs = Self { preopen_fds: RwLock::new(vec![]), name_map: HashMap::new(), - fd_map: RwLock::new(HashMap::new()), + fd_map: Arc::new(RwLock::new(HashMap::new())), next_fd: AtomicU32::new(3), inode_counter: AtomicU64::new(1024), current_dir: Mutex::new("/".to_string()), is_wasix: AtomicBool::new(false), - fs_backing, + root_fs: fs_backing, + has_unioned: Arc::new(Mutex::new(HashSet::new())) }; wasi_fs.create_stdin(inodes); wasi_fs.create_stdout(inodes); @@ -668,8 +831,7 @@ impl WasiFs { for c in path.components() { let segment_name = c.as_os_str().to_string_lossy().to_string(); let guard = inodes.arena[cur_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { ref entries, .. } | Kind::Root { ref entries } => { if let Some(_entry) = entries.get(&segment_name) { // TODO: this should be fixed @@ -687,14 +849,13 @@ impl WasiFs { inodes, kind, false, - segment_name.clone(), + segment_name.clone().into(), ); // reborrow to insert { let mut guard = inodes.arena[cur_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { ref mut entries, .. } @@ -741,8 +902,7 @@ impl WasiFs { let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?; let guard = inodes.arena[base_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { ref entries, .. } | Kind::Root { ref entries } => { if let Some(_entry) = entries.get(&name) { // TODO: eventually change the logic here to allow overwrites @@ -750,7 +910,7 @@ impl WasiFs { } let kind = Kind::File { - handle: Some(file), + handle: Some(Arc::new(RwLock::new(file))), path: PathBuf::from(""), fd: Some(self.next_fd.load(Ordering::Acquire)), }; @@ -762,8 +922,7 @@ impl WasiFs { { let mut guard = inodes.arena[base_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { ref mut entries, .. } @@ -788,37 +947,55 @@ impl WasiFs { pub fn swap_file( &self, inodes: &WasiInodes, - fd: WasiFd, - file: Box, + fd: __wasi_fd_t, + mut file: Box, ) -> Result>, FsError> { - let mut ret = Some(file); match fd { __WASI_STDIN_FILENO => { let mut target = inodes.stdin_mut(&self.fd_map)?; - std::mem::swap(target.deref_mut(), &mut ret); + Ok(Some(target.swap(file))) } __WASI_STDOUT_FILENO => { let mut target = inodes.stdout_mut(&self.fd_map)?; - std::mem::swap(target.deref_mut(), &mut ret); + Ok(Some(target.swap(file))) } __WASI_STDERR_FILENO => { let mut target = inodes.stderr_mut(&self.fd_map)?; - std::mem::swap(target.deref_mut(), &mut ret); + Ok(Some(target.swap(file))) } _ => { let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?; + { + // happy path + let guard = inodes.arena[base_inode].read(); + match guard.deref() { + Kind::File { ref handle, .. } => { + if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); + std::mem::swap(handle.deref_mut(), &mut file); + return Ok(Some(file)); + } + } + _ => return Err(FsError::NotAFile), + } + } + // slow path let mut guard = inodes.arena[base_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { - std::mem::swap(handle, &mut ret); + if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); + std::mem::swap(handle.deref_mut(), &mut file); + return Ok(Some(file)); + } else { + handle.replace(Arc::new(RwLock::new(file))); + Ok(None) + } } _ => return Err(FsError::NotAFile), } } } - - Ok(ret) } /// refresh size from filesystem @@ -829,18 +1006,18 @@ impl WasiFs { ) -> Result { let inode = self.get_fd_inode(fd)?; let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(h) = handle { - let new_size = h.size(); + let h = h.read().unwrap(); + let new_size = h.size().clone(); + drop(h); drop(guard); inodes.arena[inode].stat.write().unwrap().st_size = new_size; - Ok(new_size as Filesize) - } else { - Err(Errno::Badf) + return Ok(new_size as __wasi_filesize_t); } + Err(__WASI_EBADF) } Kind::Dir { .. } | Kind::Root { .. } => Err(Errno::Isdir), _ => Err(Errno::Inval), @@ -919,8 +1096,7 @@ impl WasiFs { // loading inodes as necessary 'symlink_resolution: while symlink_count < MAX_SYMLINKS { let mut guard = inodes.arena[cur_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"), Kind::Dir { ref mut entries, @@ -952,8 +1128,7 @@ impl WasiFs { cd.push(component); cd }; - let metadata = self - .fs_backing + let metadata = self.root_fs .symlink_metadata(&file) .ok() .ok_or(Errno::Noent)?; @@ -1024,8 +1199,8 @@ impl WasiFs { inodes, kind, false, - file.to_string_lossy().to_string(), - Filestat { + file.to_string_lossy().to_string().into(), + __wasi_filestat_t { st_filetype: file_type, ..Filestat::default() }, @@ -1186,12 +1361,10 @@ impl WasiFs { let mut res = BaseFdAndRelPath::None; // for each preopened directory let preopen_fds = self.preopen_fds.read().unwrap(); - let deref = preopen_fds.deref(); - for po_fd in deref { + for po_fd in preopen_fds.deref() { let po_inode = self.fd_map.read().unwrap()[po_fd].inode; let guard = inodes.arena[po_inode].read(); - let deref = guard.deref(); - let po_path = match deref { + let po_path = match guard.deref() { Kind::Dir { path, .. } => &**path, Kind::Root { .. } => Path::new("/"), _ => unreachable!("Preopened FD that's not a directory or the root"), @@ -1233,8 +1406,7 @@ impl WasiFs { while cur_inode != base_inode { counter += 1; let guard = inodes.arena[cur_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { parent, .. } => { if let Some(p) = parent { cur_inode = *p; @@ -1358,13 +1530,12 @@ impl WasiFs { debug!("fdstat: {:?}", fd); let guard = inodes.arena[fd.inode].read(); - let deref = guard.deref(); - Ok(Fdstat { - fs_filetype: match deref { - Kind::File { .. } => Filetype::RegularFile, - Kind::Dir { .. } => Filetype::Directory, - Kind::Symlink { .. } => Filetype::SymbolicLink, - _ => Filetype::Unknown, + Ok(__wasi_fdstat_t { + fs_filetype: match guard.deref() { + Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE, + Kind::Dir { .. } => __WASI_FILETYPE_DIRECTORY, + Kind::Symlink { .. } => __WASI_FILETYPE_SYMBOLIC_LINK, + _ => __WASI_FILETYPE_UNKNOWN, }, fs_flags: fd.flags, fs_rights_base: fd.rights, @@ -1374,7 +1545,7 @@ impl WasiFs { pub fn prestat_fd(&self, inodes: &WasiInodes, fd: WasiFd) -> Result { let inode = self.get_fd_inode(fd)?; - trace!("in prestat_fd {:?}", self.get_fd(fd)?); + //trace!("in prestat_fd {:?}", self.get_fd(fd)?); let inode_val = &inodes.arena[inode]; @@ -1402,27 +1573,27 @@ impl WasiFs { __WASI_STDOUT_FILENO => inodes .stdout_mut(&self.fd_map) .map_err(fs_error_into_wasi_err)? - .as_mut() - .map(|f| f.flush().map_err(map_io_err)) - .unwrap_or_else(|| Err(Errno::Io))?, + .flush() + .map_err(map_io_err)?, __WASI_STDERR_FILENO => inodes .stderr_mut(&self.fd_map) .map_err(fs_error_into_wasi_err)? - .as_mut() - .and_then(|f| f.flush().ok()) - .ok_or(Errno::Io)?, + .flush() + .map_err(map_io_err)?, _ => { let fd = self.get_fd(fd)?; if !fd.rights.contains(Rights::FD_DATASYNC) { return Err(Errno::Access); } - let mut guard = inodes.arena[fd.inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + let guard = inodes.arena[fd.inode].read(); + match guard.deref() { Kind::File { handle: Some(file), .. - } => file.flush().map_err(|_| Errno::Io)?, + } => { + let mut file = file.write().unwrap(); + file.flush().map_err(|_| __WASI_EIO)? + }, // TODO: verify this behavior Kind::Dir { .. } => return Err(Errno::Isdir), Kind::Symlink { .. } => unimplemented!("WasiFs::flush Kind::Symlink"), @@ -1443,7 +1614,7 @@ impl WasiFs { name: String, ) -> Result { let stat = self.get_stat_for_kind(inodes, &kind)?; - Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)) + Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name.into(), stat)) } /// Creates an inode and inserts it given a Kind, does not assume the file exists. @@ -1452,7 +1623,7 @@ impl WasiFs { inodes: &mut WasiInodes, kind: Kind, is_preopened: bool, - name: String, + name: Cow<'static, str>, ) -> Inode { let stat = Filestat::default(); self.create_inode_with_stat(inodes, kind, is_preopened, name, stat) @@ -1464,8 +1635,8 @@ impl WasiFs { inodes: &mut WasiInodes, kind: Kind, is_preopened: bool, - name: String, - mut stat: Filestat, + name: Cow<'static, str>, + mut stat: __wasi_filestat_t, ) -> Inode { stat.st_ino = self.get_next_inode_index(); @@ -1483,33 +1654,49 @@ impl WasiFs { rights_inheriting: Rights, flags: Fdflags, open_flags: u16, - inode: Inode, - ) -> Result { + inode: Inode + ) -> Result<__wasi_fd_t, __wasi_errno_t> { let idx = self.next_fd.fetch_add(1, Ordering::AcqRel); + self.create_fd_ext(rights, rights_inheriting, flags, open_flags, inode, idx)?; + Ok(idx) + } + + pub fn create_fd_ext( + &self, + rights: __wasi_rights_t, + rights_inheriting: __wasi_rights_t, + flags: __wasi_fdflags_t, + open_flags: u16, + inode: Inode, + idx: __wasi_fd_t + ) -> Result<(), __wasi_errno_t> { self.fd_map.write().unwrap().insert( idx, Fd { + ref_cnt: Arc::new(AtomicU32::new(1)), rights, rights_inheriting, flags, - offset: 0, + offset: Arc::new(AtomicU64::new(0)), open_flags, inode, }, ); - Ok(idx) + Ok(()) } pub fn clone_fd(&self, fd: WasiFd) -> Result { let fd = self.get_fd(fd)?; let idx = self.next_fd.fetch_add(1, Ordering::AcqRel); + fd.ref_cnt.fetch_add(1, Ordering::Acquire); self.fd_map.write().unwrap().insert( idx, Fd { + ref_cnt: fd.ref_cnt.clone(), rights: fd.rights, rights_inheriting: fd.rights_inheriting, flags: fd.flags, - offset: fd.offset, + offset: fd.offset.clone(), open_flags: fd.open_flags, inode: fd.inode, }, @@ -1542,7 +1729,7 @@ impl WasiFs { inodes.arena.insert(InodeVal { stat: RwLock::new(stat), is_preopened: true, - name: "/".to_string(), + name: "/".into(), kind: RwLock::new(root_kind), }) } @@ -1594,26 +1781,27 @@ impl WasiFs { }; let kind = Kind::File { fd: Some(raw_fd), - handle: Some(handle), + handle: Some(Arc::new(RwLock::new(handle))), path: "".into(), }; let inode = { inodes.arena.insert(InodeVal { stat: RwLock::new(stat), is_preopened: true, - name: name.to_string(), + name: name.to_string().into(), kind: RwLock::new(kind), }) }; self.fd_map.write().unwrap().insert( raw_fd, Fd { + ref_cnt: Arc::new(AtomicU32::new(1)), rights, rights_inheriting: Rights::empty(), flags: fd_flags, // since we're not calling open on this, we don't need open flags open_flags: 0, - offset: 0, + offset: Arc::new(AtomicU64::new(0)), inode, }, ); @@ -1623,8 +1811,9 @@ impl WasiFs { let md = match kind { Kind::File { handle, path, .. } => match handle { Some(wf) => { - return Ok(Filestat { - st_filetype: Filetype::RegularFile, + let wf = wf.read().unwrap(); + return Ok(__wasi_filestat_t { + st_filetype: __WASI_FILETYPE_REGULAR_FILE, st_size: wf.size(), st_atim: wf.last_accessed(), st_mtim: wf.last_modified(), @@ -1633,13 +1822,11 @@ impl WasiFs { ..Filestat::default() }) } - None => self - .fs_backing + None => self.root_fs .metadata(path) .map_err(fs_error_into_wasi_err)?, }, - Kind::Dir { path, .. } => self - .fs_backing + Kind::Dir { path, .. } => self.root_fs .metadata(path) .map_err(fs_error_into_wasi_err)?, Kind::Symlink { @@ -1650,10 +1837,9 @@ impl WasiFs { let base_po_inode = &self.fd_map.read().unwrap()[base_po_dir].inode; let base_po_inode_v = &inodes.arena[*base_po_inode]; let guard = base_po_inode_v.read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Root { .. } => { - self.fs_backing.symlink_metadata(path_to_symlink).map_err(fs_error_into_wasi_err)? + self.root_fs.symlink_metadata(path_to_symlink).map_err(fs_error_into_wasi_err)? } Kind::Dir { path, .. } => { let mut real_path = path.clone(); @@ -1664,7 +1850,7 @@ impl WasiFs { // TODO: adjust size of symlink, too // for all paths adjusted think about this real_path.push(path_to_symlink); - self.fs_backing.symlink_metadata(&real_path).map_err(fs_error_into_wasi_err)? + self.root_fs.symlink_metadata(&real_path).map_err(fs_error_into_wasi_err)? } // if this triggers, there's a bug in the symlink code _ => unreachable!("Symlink pointing to something that's not a directory as its base preopened directory"), @@ -1683,14 +1869,38 @@ impl WasiFs { } /// Closes an open FD, handling all details such as FD being preopen - pub(crate) fn close_fd(&self, inodes: &WasiInodes, fd: WasiFd) -> Result<(), Errno> { - let inode = self.get_fd_inode(fd)?; + pub(crate) fn close_fd( + &self, + inodes: &WasiInodes, + fd: __wasi_fd_t, + ) -> Result<(), __wasi_errno_t> { + let mut fd_map = self.fd_map.write().unwrap(); + self.close_fd_ext(inodes, &mut fd_map, fd) + } + + /// Closes an open FD, handling all details such as FD being preopen + pub(crate) fn close_fd_ext( + &self, + inodes: &WasiInodes, + fd_map: &mut RwLockWriteGuard>, + fd: __wasi_fd_t, + ) -> Result<(), __wasi_errno_t> { + + let pfd = fd_map.get(&fd) + .ok_or(__WASI_EBADF)?; + if pfd.ref_cnt.fetch_sub(1, Ordering::AcqRel) > 1 { + trace!("closing file descriptor({}) - ref-cnt", fd); + fd_map.remove(&fd); + return Ok(()); + } + trace!("closing file descriptor({}) - inode", fd); + + let inode = pfd.inode; let inodeval = inodes.get_inodeval(inode)?; let is_preopened = inodeval.is_preopened; let mut guard = inodeval.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { let mut empty_handle = None; std::mem::swap(handle, &mut empty_handle); @@ -1712,16 +1922,14 @@ impl WasiFs { if let Some(p) = *parent { drop(guard); let mut guard = inodes.arena[p].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { entries, .. } | Kind::Root { entries } => { - self.fd_map.write().unwrap().remove(&fd).unwrap(); + fd_map.remove(&fd).unwrap(); if is_preopened { let mut idx = None; { let preopen_fds = self.preopen_fds.read().unwrap(); - let preopen_fds_iter = preopen_fds.iter().enumerate(); - for (i, po_fd) in preopen_fds_iter { + for (i, po_fd) in preopen_fds.iter().enumerate() { if *po_fd == fd { idx = Some(i); break; @@ -1761,23 +1969,20 @@ impl WasiState { pub(crate) fn fs_read_dir>( &self, path: P, - ) -> Result { - self.fs - .fs_backing + ) -> Result { + self.fs.root_fs .read_dir(path.as_ref()) .map_err(fs_error_into_wasi_err) } - pub(crate) fn fs_create_dir>(&self, path: P) -> Result<(), Errno> { - self.fs - .fs_backing + pub(crate) fn fs_create_dir>(&self, path: P) -> Result<(), __wasi_errno_t> { + self.fs.root_fs .create_dir(path.as_ref()) .map_err(fs_error_into_wasi_err) } - pub(crate) fn fs_remove_dir>(&self, path: P) -> Result<(), Errno> { - self.fs - .fs_backing + pub(crate) fn fs_remove_dir>(&self, path: P) -> Result<(), __wasi_errno_t> { + self.fs.root_fs .remove_dir(path.as_ref()) .map_err(fs_error_into_wasi_err) } @@ -1786,40 +1991,130 @@ impl WasiState { &self, from: P, to: Q, - ) -> Result<(), Errno> { - self.fs - .fs_backing + ) -> Result<(), __wasi_errno_t> { + self.fs.root_fs .rename(from.as_ref(), to.as_ref()) .map_err(fs_error_into_wasi_err) } - pub(crate) fn fs_remove_file>(&self, path: P) -> Result<(), Errno> { - self.fs - .fs_backing + pub(crate) fn fs_remove_file>(&self, path: P) -> Result<(), __wasi_errno_t> { + self.fs.root_fs .remove_file(path.as_ref()) .map_err(fs_error_into_wasi_err) } pub(crate) fn fs_new_open_options(&self) -> OpenOptions { - self.fs.fs_backing.new_open_options() + OpenOptions::new( + Box::new( + WasiStateOpener { + root_fs: self.fs.root_fs.clone(), + } + ) + ) } } +struct WasiStateOpener +{ + root_fs: WasiFsRoot +} + +impl FileOpener +for WasiStateOpener +{ + fn open( + &mut self, + path: &Path, + conf: &wasmer_vfs::OpenOptionsConfig, + ) -> wasmer_vfs::Result> { + let mut new_options = self.root_fs.new_open_options(); + new_options.options(conf.clone()); + new_options.open(path) + } +} + +pub(crate) struct WasiThreadContext { + pub ctx: WasiFunctionEnv, + pub store: RefCell, +} + +/// The code itself makes safe use of the struct so multiple threads don't access +/// it (without this the JS code prevents the reference to the module from being stored +/// which is needed for the multithreading mode) +unsafe impl Send for WasiThreadContext { } +unsafe impl Sync for WasiThreadContext { } + /// Structures used for the threading and sub-processes /// /// These internal implementation details are hidden away from the /// consumer who should instead implement the vbus trait on the runtime -#[derive(Debug, Default)] + +#[derive(Derivative, Default)] +#[derivative(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub(crate) struct WasiStateThreading { - #[cfg_attr(feature = "enable-serde", serde(skip))] - pub threads: HashMap, - pub thread_seed: u32, - #[cfg_attr(feature = "enable-serde", serde(skip))] - pub processes: HashMap, - #[cfg_attr(feature = "enable-serde", serde(skip))] - pub process_reuse: HashMap, WasiBusProcessId>, - pub process_seed: u32, + #[derivative(Debug = "ignore")] + pub thread_ctx: HashMap>, +} + +/// Represents a futex which will make threads wait for completion in a more +/// CPU efficient manner +#[derive(Debug, Clone)] +pub struct WasiFutex { + pub(crate) refcnt: Arc, + pub(crate) inner: Arc<(Mutex<()>, Condvar)>, +} + +#[derive(Debug)] +pub struct WasiBusCall +{ + pub bid: WasiProcessId, + pub invocation: Box, +} + +/// Protected area of the BUS state +#[derive(Debug, Default)] +pub struct WasiBusProtectedState +{ + pub call_seed: u64, + pub called: HashMap>, + pub calls: HashMap, +} + +/// Structure that holds the state of BUS calls to this process and from +/// this process. BUS calls are the equivalent of RPC's with support +/// for all the major serializers +#[derive(Debug, Default)] +pub struct WasiBusState +{ + protected: Mutex, + poll_waker: WasiParkingLot, +} + +impl WasiBusState +{ + /// Gets a reference to the waker that can be used for + /// asynchronous calls + pub fn get_poll_waker(&self) -> Waker { + self.poll_waker.get_waker() + } + + /// Wakes one of the reactors thats currently waiting + pub fn poll_wake(&self) { + self.poll_waker.wake() + } + + /// Will wait until either the reactor is triggered + /// or the timeout occurs + pub fn poll_wait(&self, timeout: Duration) -> bool { + self.poll_waker.wait(timeout) + } + + /// Locks the protected area of the BUS and returns a guard that + /// can be used to access it + pub fn protected<'a>(&'a self) -> MutexGuard<'a, WasiBusProtectedState> { + self.protected.lock().unwrap() + } } /// Top level data type containing all* the state with which WASI can @@ -1854,10 +2149,16 @@ pub(crate) struct WasiStateThreading { #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct WasiState { pub fs: WasiFs, + pub secret: [u8; 32], pub inodes: Arc>, - pub(crate) threading: Mutex, - pub args: Vec>, + pub(crate) threading: RwLock, + pub(crate) futexs: Mutex>, + pub(crate) clock_offset: Mutex>, + pub(crate) bus: WasiBusState, + pub args: Vec, pub envs: Vec>, + pub preopen: Vec, + pub(crate) runtime: Arc } impl WasiState { @@ -1932,12 +2233,30 @@ impl WasiState { fd: WasiFd, ) -> Result>, FsError> { let ret = WasiStateFileGuard::new(self, fd)?.map(|a| { - let ret = Box::new(a); - let ret: Box = ret; - ret - }); + let ret = Box::new(a); + let ret: Box = ret; + ret + }); Ok(ret) } + + /// Forking the WasiState is used when either fork or vfork is called + pub fn fork(&self) -> Self + { + WasiState { + fs: self.fs.fork(), + secret: self.secret.clone(), + inodes: self.inodes.clone(), + threading: Default::default(), + futexs: Default::default(), + clock_offset: Mutex::new(self.clock_offset.lock().unwrap().clone()), + bus: Default::default(), + args: self.args.clone(), + envs: self.envs.clone(), + preopen: self.preopen.clone(), + runtime: self.runtime.clone(), + } + } } pub fn virtual_file_type_to_wasi_file_type(file_type: wasmer_vfs::FileType) -> Filetype { @@ -1952,3 +2271,27 @@ pub fn virtual_file_type_to_wasi_file_type(file_type: wasmer_vfs::FileType) -> F Filetype::Unknown } } + +#[derive(Debug, Clone)] +pub struct WasiDummyWaker; + +impl WakeRef for WasiDummyWaker { + fn wake_by_ref(&self) { + } +} + +impl Wake for WasiDummyWaker { + fn wake(self) { + } +} + +unsafe impl ViaRawPointer for WasiDummyWaker { + type Target = (); + fn into_raw(self) -> *mut () { + std::mem::forget(self); + std::ptr::null_mut() + } + unsafe fn from_raw(_ptr: *mut ()) -> Self { + WasiDummyWaker + } +} diff --git a/lib/wasi/src/state/parking.rs b/lib/wasi/src/state/parking.rs new file mode 100644 index 00000000000..bb25f5d8f95 --- /dev/null +++ b/lib/wasi/src/state/parking.rs @@ -0,0 +1,82 @@ +use std::{ + task::Waker, + sync::{ + Mutex, + Arc, + Condvar + }, + time::Duration +}; + +/// Represents a waker that can be used to put a thread to +/// sleep while it waits for an event to occur +#[derive(Debug)] +pub struct WasiParkingLot +{ + waker: Waker, + run: Arc<(Mutex, Condvar)>, +} + +impl Default +for WasiParkingLot +{ + fn default() -> Self + { + Self::new(true) + } +} + +impl WasiParkingLot +{ + /// Creates a new parking lot with a specific value + pub fn new(initial_val: bool) -> Self + { + let run = Arc::new((Mutex::new(initial_val), Condvar::default())); + let waker = { + let run = run.clone(); + waker_fn::waker_fn(move || + { + let mut guard = run.0.lock().unwrap(); + *guard = true; + run.1.notify_one(); + }) + }; + + Self { + waker, + run, + } + } + + /// Gets a reference to the waker that can be used for + /// asynchronous calls + pub fn get_waker(&self) -> Waker { + self.waker.clone() + } + + /// Wakes one of the reactors thats currently waiting + pub fn wake(&self) { + self.waker.wake_by_ref(); + } + + /// Will wait until either the reactor is triggered + /// or the timeout occurs + pub fn wait(&self, timeout: Duration) -> bool { + let mut run = self.run.0.lock().unwrap(); + if *run == true { + *run = false; + return true; + } + loop { + let woken = self.run.1.wait_timeout(run, timeout).unwrap(); + if woken.1.timed_out() { + return false; + } + run = woken.0; + if *run == true { + *run = false; + return true; + } + } + } +} \ No newline at end of file diff --git a/lib/wasi/src/state/pipe.rs b/lib/wasi/src/state/pipe.rs index 60754a0cb0f..00844fb182b 100644 --- a/lib/wasi/src/state/pipe.rs +++ b/lib/wasi/src/state/pipe.rs @@ -1,12 +1,13 @@ use crate::syscalls::types::*; use crate::syscalls::{read_bytes, write_bytes}; use bytes::{Buf, Bytes}; +use wasmer_vfs::VirtualFile; use std::convert::TryInto; -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::io::{Read, Write, Seek}; use std::ops::DerefMut; -use std::sync::mpsc; -use std::sync::Arc; +use std::sync::mpsc::{self, TryRecvError}; use std::sync::Mutex; +use std::time::Duration; use wasmer::WasmSlice; use wasmer::{MemorySize, MemoryView}; use wasmer_vfs::{FsError, VirtualFile}; @@ -19,9 +20,7 @@ pub struct WasiPipe { /// Receives bytes from the pipe rx: Mutex>>, /// Buffers the last read message from the pipe while its being consumed - read_buffer: Option, - /// Whether the pipe should block or not block to wait for stdin reads - block: bool, + read_buffer: Mutex>, } /// Pipe pair of (a, b) WasiPipes that are connected together @@ -90,15 +89,13 @@ impl WasiBidirectionalPipePair { let pipe1 = WasiPipe { tx: Mutex::new(tx1), rx: Mutex::new(rx2), - read_buffer: None, - block: true, + read_buffer: Mutex::new(None), }; let pipe2 = WasiPipe { tx: Mutex::new(tx2), rx: Mutex::new(rx1), - read_buffer: None, - block: true, + read_buffer: Mutex::new(None), }; WasiBidirectionalPipePair { @@ -233,20 +230,46 @@ impl WasiPipe { &mut self, memory: &MemoryView, iov: WasmSlice<__wasi_iovec_t>, - ) -> Result { + timeout: Duration, + ) -> Result { + let mut elapsed = Duration::ZERO; + let mut tick_wait = 0u64; loop { - if let Some(buf) = self.read_buffer.as_mut() { - let buf_len = buf.len(); - if buf_len > 0 { - let reader = buf.as_ref(); - let read = read_bytes(reader, memory, iov).map(|_| buf_len as usize)?; - buf.advance(read); - return Ok(read); + { + let mut read_buffer = self.read_buffer.lock().unwrap(); + if let Some(buf) = read_buffer.as_mut() { + let buf_len = buf.len(); + if buf_len > 0 { + let reader = buf.as_ref(); + let read = read_bytes(reader, memory, iov).map(|a| a as usize)?; + buf.advance(read); + return Ok(read); + } } } let rx = self.rx.lock().unwrap(); - let data = rx.recv().map_err(|_| Errno::Io)?; - self.read_buffer.replace(Bytes::from(data)); + let data = match rx.try_recv() { + Ok(a) => a, + Err(TryRecvError::Empty) => { + if elapsed > timeout { + return Err(__WASI_ETIMEDOUT); + } + // Linearly increasing wait time + tick_wait += 1; + let wait_time = u64::min(tick_wait / 10, 20); + let wait_time = std::time::Duration::from_millis(wait_time); + std::thread::park_timeout(wait_time); + elapsed += wait_time; + continue; + } + Err(TryRecvError::Disconnected) => { + return Ok(0); + } + }; + drop(rx); + + let mut read_buffer = self.read_buffer.lock().unwrap(); + read_buffer.replace(Bytes::from(data)); } } @@ -279,8 +302,11 @@ impl WasiPipe { let mut guard = self.tx.lock().unwrap(); std::mem::swap(guard.deref_mut(), &mut null_tx); } - self.read_buffer.take(); - } + { + let mut read_buffer = self.read_buffer.lock().unwrap(); + read_buffer.take(); + } + } } impl Write for WasiPipe { @@ -305,15 +331,11 @@ impl Seek for WasiPipe { impl Read for WasiPipe { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { loop { - if let Some(inner_buf) = self.read_buffer.as_mut() { - let buf_len = inner_buf.len(); - if buf_len > 0 { - if inner_buf.len() > buf.len() { - let mut reader = inner_buf.as_ref(); - let read = reader.read_exact(buf).map(|_| buf.len())?; - inner_buf.advance(read); - return Ok(read); - } else { + { + let mut read_buffer = self.read_buffer.lock().unwrap(); + if let Some(inner_buf) = read_buffer.as_mut() { + let buf_len = inner_buf.len(); + if buf_len > 0 { let mut reader = inner_buf.as_ref(); let read = reader.read(buf).map(|_| buf_len as usize)?; inner_buf.advance(read); @@ -322,74 +344,130 @@ impl Read for WasiPipe { } } let rx = self.rx.lock().unwrap(); + if let Ok(data) = rx.recv() { + drop(rx); - // We need to figure out whether we need to block here. - // The problem is that in cases of multiple buffered reads like: - // - // println!("abc"); - // println!("def"); - // - // get_stdout() // would only return "abc\n" instead of "abc\ndef\n" - - let data = match rx.try_recv() { - Ok(mut s) => { - s.append(&mut rx.try_iter().flat_map(|f| f.into_iter()).collect()); - s - } - Err(_) => { - if !self.block { - // If self.block is explicitly set to false, never block - Vec::new() - } else { - // could not immediately receive bytes, so we need to block - match rx.recv() { - Ok(o) => o, - // Errors can happen if the sender has been dropped already - // In this case, just return 0 to indicate that we can't read any - // bytes anymore - Err(_) => { - return Ok(0); - } - } - } - } - }; - if data.is_empty() && self.read_buffer.as_ref().map(|s| s.len()).unwrap_or(0) == 0 { + let mut read_buffer = self.read_buffer.lock().unwrap(); + read_buffer.replace(Bytes::from(data)); + } else { return Ok(0); } - self.read_buffer.replace(Bytes::from(data)); } } } -impl VirtualFile for WasiPipe { +impl Write for WasiPipe { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let tx = self.tx.lock().unwrap(); + tx.send(buf.to_vec()) + .map_err(|_| Into::::into(std::io::ErrorKind::BrokenPipe))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl Seek for WasiPipe { + fn seek(&mut self, _pos: std::io::SeekFrom) -> std::io::Result { + Ok(0) + } +} + +impl VirtualFile +for WasiPipe +{ + /// the last time the file was accessed in nanoseconds as a UNIX timestamp fn last_accessed(&self) -> u64 { 0 } + + /// the last time the file was modified in nanoseconds as a UNIX timestamp fn last_modified(&self) -> u64 { 0 } + + /// the time at which the file was created in nanoseconds as a UNIX timestamp fn created_time(&self) -> u64 { 0 } + + /// the size of the file in bytes fn size(&self) -> u64 { - self.read_buffer - .as_ref() - .map(|s| s.len() as u64) - .unwrap_or_default() + 0 } - fn set_len(&mut self, _: u64) -> Result<(), FsError> { + + /// Change the size of the file, if the `new_size` is greater than the current size + /// the extra bytes will be allocated and zeroed + fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> { Ok(()) } - fn unlink(&mut self) -> Result<(), FsError> { + + /// Request deletion of the file + fn unlink(&mut self) -> wasmer_vfs::Result<()> { Ok(()) } - fn bytes_available_read(&self) -> Result, FsError> { - Ok(Some( - self.read_buffer - .as_ref() - .map(|s| s.len()) - .unwrap_or_default(), - )) + + /// Store file contents and metadata to disk + /// Default implementation returns `Ok(())`. You should implement this method if you care + /// about flushing your cache to permanent storage + fn sync_to_disk(&self) -> wasmer_vfs::Result<()> { + Ok(()) + } + + /// Returns the number of bytes available. This function must not block + fn bytes_available(&self) -> wasmer_vfs::Result { + Ok(self.bytes_available_read()?.unwrap_or(0usize) + + self.bytes_available_write()?.unwrap_or(0usize)) + } + + /// Returns the number of bytes available. This function must not block + /// Defaults to `None` which means the number of bytes is unknown + fn bytes_available_read(&self) -> wasmer_vfs::Result> { + loop { + { + let read_buffer = self.read_buffer.lock().unwrap(); + if let Some(inner_buf) = read_buffer.as_ref() { + let buf_len = inner_buf.len(); + if buf_len > 0 { + return Ok(Some(buf_len)); + } + } + } + let rx = self.rx.lock().unwrap(); + if let Ok(data) = rx.try_recv() { + drop(rx); + + let mut read_buffer = self.read_buffer.lock().unwrap(); + read_buffer.replace(Bytes::from(data)); + } else { + return Ok(Some(0)); + } + } + } + + /// Returns the number of bytes available. This function must not block + /// Defaults to `None` which means the number of bytes is unknown + fn bytes_available_write(&self) -> wasmer_vfs::Result> { + Ok(None) + } + + /// Indicates if the file is opened or closed. This function must not block + /// Defaults to a status of being constantly open + fn is_open(&self) -> bool { + true + } + + /// Returns a special file descriptor when opening this file rather than + /// generating a new one + fn get_special_fd(&self) -> Option { + None + } + + /// Used for polling. Default returns `None` because this method cannot be implemented for most types + /// Returns the underlying host fd + fn get_fd(&self) -> Option { + None } } diff --git a/lib/wasi/src/state/socket.rs b/lib/wasi/src/state/socket.rs index 788c0e17827..7a9e5f8a966 100644 --- a/lib/wasi/src/state/socket.rs +++ b/lib/wasi/src/state/socket.rs @@ -1,17 +1,16 @@ use super::types::net_error_into_wasi_err; use crate::syscalls::types::*; -use crate::syscalls::{read_bytes, write_bytes}; use bytes::{Buf, Bytes}; -use std::convert::TryInto; -use std::io::{self, Read}; +use std::future::Future; use std::mem::transmute; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::sync::Mutex; +use std::pin::Pin; +use std::sync::{Mutex, Arc, RwLock}; use std::time::Duration; #[allow(unused_imports)] use tracing::{debug, error, info, warn}; -use wasmer::{MemorySize, MemoryView, WasmPtr, WasmSlice}; -use wasmer_vnet::{net_error_into_io_err, TimeType}; +use wasmer::{MemorySize, MemoryView, WasmPtr}; +use wasmer_vnet::TimeType; use wasmer_vnet::{ IpCidr, IpRoute, SocketHttpRequest, VirtualIcmpSocket, VirtualNetworking, VirtualRawSocket, VirtualTcpListener, VirtualTcpSocket, VirtualUdpSocket, VirtualWebSocket, @@ -43,6 +42,7 @@ pub enum InodeSocketKind { only_v6: bool, reuse_port: bool, reuse_addr: bool, + nonblocking: bool, send_buf_size: Option, recv_buf_size: Option, send_timeout: Option, @@ -143,33 +143,45 @@ pub struct WasiHttpStatus { #[derive(Debug)] //#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub(crate) struct InodeSocketInner { + pub kind: InodeSocketKind, + pub read_buffer: Option, + pub read_addr: Option, + pub silence_write_ready: bool, +} + +#[derive(Debug, Clone)] +//#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct InodeSocket { - kind: InodeSocketKind, - read_buffer: Option, - read_addr: Option, + pub(crate) inner: Arc>, } impl InodeSocket { - pub fn new(kind: InodeSocketKind) -> InodeSocket { - InodeSocket { - kind, - read_buffer: None, - read_addr: None, + pub fn new(kind: InodeSocketKind) -> Self { + Self { + inner: Arc::new(RwLock::new(InodeSocketInner { + kind, + read_buffer: None, + read_addr: None, + silence_write_ready: false, + })) } } - pub fn bind( - &mut self, - net: &(dyn VirtualNetworking), + pub async fn bind( + &self, + net: Arc, set_addr: SocketAddr, - ) -> Result, Errno> { - match &mut self.kind { + ) -> Result, __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::PreSocket { family, ty, addr, reuse_port, reuse_addr, + nonblocking, .. } => { match *family { @@ -197,10 +209,12 @@ impl InodeSocket { // more to do at this time None } - Socktype::Dgram => { - let socket = net + __WASI_SOCK_TYPE_DGRAM => { + let mut socket = net .bind_udp(addr, *reuse_port, *reuse_addr) + .await .map_err(net_error_into_wasi_err)?; + socket.set_nonblocking(*nonblocking).map_err(net_error_into_wasi_err)?; Some(InodeSocket::new(InodeSocketKind::UdpSocket(socket))) } _ => return Err(Errno::Inval), @@ -210,12 +224,13 @@ impl InodeSocket { } } - pub fn listen( - &mut self, - net: &(dyn VirtualNetworking), + pub async fn listen( + &self, + net: Arc, _backlog: usize, - ) -> Result, Errno> { - match &self.kind { + ) -> Result, __wasi_errno_t> { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::PreSocket { ty, addr, @@ -223,16 +238,23 @@ impl InodeSocket { reuse_port, reuse_addr, accept_timeout, + nonblocking, .. } => Ok(match *ty { Socktype::Stream => { if addr.is_none() { - return Err(Errno::Inval); + tracing::warn!("wasi[?]::sock_listen - failed - address not set"); + return Err(__WASI_EINVAL); } let addr = *addr.as_ref().unwrap(); let mut socket = net .listen_tcp(addr, *only_v6, *reuse_port, *reuse_addr) - .map_err(net_error_into_wasi_err)?; + .await + .map_err(|err| { + tracing::warn!("wasi[?]::sock_listen - failed - {}", err); + net_error_into_wasi_err(err) + })?; + socket.set_nonblocking(*nonblocking).map_err(net_error_into_wasi_err)?; if let Some(accept_timeout) = accept_timeout { socket .set_timeout(Some(*accept_timeout)) @@ -240,48 +262,49 @@ impl InodeSocket { } Some(InodeSocket::new(InodeSocketKind::TcpListener(socket))) } - _ => return Err(Errno::Notsup), + _ => { + tracing::warn!("wasi[?]::sock_listen - failed - not supported(1)"); + return Err(__WASI_ENOTSUP) + }, }), - InodeSocketKind::Closed => Err(Errno::Io), - _ => Err(Errno::Notsup), + InodeSocketKind::Closed => { + tracing::warn!("wasi[?]::sock_listen - failed - socket closed"); + Err(__WASI_EIO) + }, + _ => { + tracing::warn!("wasi[?]::sock_listen - failed - not supported(2)"); + Err(__WASI_ENOTSUP) + }, } } - pub fn accept( + pub async fn accept( &self, - _fd_flags: Fdflags, - ) -> Result<(Box, SocketAddr), Errno> { - let (sock, addr) = match &self.kind { - InodeSocketKind::TcpListener(sock) => sock.accept().map_err(net_error_into_wasi_err), - InodeSocketKind::PreSocket { .. } => Err(Errno::Notconn), - InodeSocketKind::Closed => Err(Errno::Io), - _ => Err(Errno::Notsup), + _fd_flags: __wasi_fdflags_t, + ) -> Result<(Box, SocketAddr), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + let (sock, addr) = match &mut inner.kind { + InodeSocketKind::TcpListener(sock) => { + let (child, addr) = sock + .accept() + .await + .map_err(net_error_into_wasi_err)?; + Ok((child, addr)) + }, + InodeSocketKind::PreSocket { .. } => Err(__WASI_ENOTCONN), + InodeSocketKind::Closed => Err(__WASI_EIO), + _ => Err(__WASI_ENOTSUP), }?; Ok((sock, addr)) } - pub fn accept_timeout( + pub async fn connect( &self, - _fd_flags: Fdflags, - timeout: Duration, - ) -> Result<(Box, SocketAddr), Errno> { - let (sock, addr) = match &self.kind { - InodeSocketKind::TcpListener(sock) => sock - .accept_timeout(timeout) - .map_err(net_error_into_wasi_err), - InodeSocketKind::PreSocket { .. } => Err(Errno::Notconn), - InodeSocketKind::Closed => Err(Errno::Io), - _ => Err(Errno::Notsup), - }?; - Ok((sock, addr)) - } - - pub fn connect( - &mut self, - net: &(dyn VirtualNetworking), + net: Arc, peer: SocketAddr, - ) -> Result, Errno> { - match &mut self.kind { + ) -> Result, __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::PreSocket { ty, addr, @@ -303,6 +326,7 @@ impl InodeSocket { }; let mut socket = net .connect_tcp(addr, peer, *connect_timeout) + .await .map_err(net_error_into_wasi_err)?; if let Some(timeout) = send_timeout { socket @@ -320,7 +344,7 @@ impl InodeSocket { _ => return Err(Errno::Notsup), }), InodeSocketKind::UdpSocket(sock) => { - sock.connect(peer).map_err(net_error_into_wasi_err)?; + sock.connect(peer).await.map_err(net_error_into_wasi_err)?; Ok(None) } InodeSocketKind::Closed => Err(Errno::Io), @@ -328,8 +352,9 @@ impl InodeSocket { } } - pub fn status(&self) -> Result { - Ok(match &self.kind { + pub fn status(&self) -> Result { + let inner = self.inner.read().unwrap(); + Ok(match &inner.kind { InodeSocketKind::PreSocket { .. } => WasiSocketStatus::Opening, InodeSocketKind::WebSocket(_) => WasiSocketStatus::Opened, InodeSocketKind::HttpRequest(..) => WasiSocketStatus::Opened, @@ -341,8 +366,9 @@ impl InodeSocket { }) } - pub fn http_status(&self) -> Result { - Ok(match &self.kind { + pub fn http_status(&self) -> Result { + let inner = self.inner.read().unwrap(); + Ok(match &inner.kind { InodeSocketKind::HttpRequest(http, ..) => { let http = http.lock().unwrap(); let guard = http.status.lock().unwrap(); @@ -362,8 +388,9 @@ impl InodeSocket { }) } - pub fn addr_local(&self) -> Result { - Ok(match &self.kind { + pub fn addr_local(&self) -> Result { + let inner = self.inner.read().unwrap(); + Ok(match &inner.kind { InodeSocketKind::PreSocket { family, addr, .. } => { if let Some(addr) = addr { *addr @@ -393,8 +420,9 @@ impl InodeSocket { }) } - pub fn addr_peer(&self) -> Result { - Ok(match &self.kind { + pub fn addr_peer(&self) -> Result { + let inner = self.inner.read().unwrap(); + Ok(match &inner.kind { InodeSocketKind::PreSocket { family, .. } => SocketAddr::new( match *family { Addressfamily::Inet4 => IpAddr::V4(Ipv4Addr::UNSPECIFIED), @@ -428,8 +456,13 @@ impl InodeSocket { }) } - pub fn set_opt_flag(&mut self, option: WasiSocketOption, val: bool) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_opt_flag( + &self, + option: WasiSocketOption, + val: bool, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::PreSocket { only_v6, reuse_port, @@ -473,8 +506,9 @@ impl InodeSocket { Ok(()) } - pub fn get_opt_flag(&self, option: WasiSocketOption) -> Result { - Ok(match &self.kind { + pub fn get_opt_flag(&self, option: WasiSocketOption) -> Result { + let inner = self.inner.read().unwrap(); + Ok(match &inner.kind { InodeSocketKind::PreSocket { only_v6, reuse_port, @@ -511,8 +545,9 @@ impl InodeSocket { }) } - pub fn set_send_buf_size(&mut self, size: usize) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_send_buf_size(&self, size: usize) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::PreSocket { send_buf_size, .. } => { *send_buf_size = Some(size); } @@ -526,8 +561,9 @@ impl InodeSocket { Ok(()) } - pub fn send_buf_size(&self) -> Result { - match &self.kind { + pub fn send_buf_size(&self) -> Result { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::PreSocket { send_buf_size, .. } => { Ok((*send_buf_size).unwrap_or_default()) } @@ -539,8 +575,9 @@ impl InodeSocket { } } - pub fn set_recv_buf_size(&mut self, size: usize) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_recv_buf_size(&self, size: usize) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::PreSocket { recv_buf_size, .. } => { *recv_buf_size = Some(size); } @@ -554,8 +591,9 @@ impl InodeSocket { Ok(()) } - pub fn recv_buf_size(&self) -> Result { - match &self.kind { + pub fn recv_buf_size(&self) -> Result { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::PreSocket { recv_buf_size, .. } => { Ok((*recv_buf_size).unwrap_or_default()) } @@ -567,8 +605,12 @@ impl InodeSocket { } } - pub fn set_linger(&mut self, linger: Option) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_linger( + &self, + linger: Option, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::TcpStream(sock) => { sock.set_linger(linger).map_err(net_error_into_wasi_err) } @@ -578,8 +620,72 @@ impl InodeSocket { } } - pub fn linger(&self) -> Result, Errno> { - match &self.kind { + pub fn nonblocking( + &self, + ) -> Result { + let inner = self.inner.read().unwrap(); + Ok( + match &inner.kind { + InodeSocketKind::TcpStream(sock) => { + sock.nonblocking().map_err(net_error_into_wasi_err)? + } + InodeSocketKind::TcpListener(sock, ..) => { + sock.nonblocking().map_err(net_error_into_wasi_err)? + } + InodeSocketKind::UdpSocket(sock, ..) => { + sock.nonblocking().map_err(net_error_into_wasi_err)? + } + InodeSocketKind::Raw(sock, ..) => { + sock.nonblocking().map_err(net_error_into_wasi_err)? + } + InodeSocketKind::Icmp(sock, ..) => { + sock.nonblocking().map_err(net_error_into_wasi_err)? + } + InodeSocketKind::PreSocket { nonblocking, .. } => { + *nonblocking + } + _ => { + return Err(__WASI_ENOTSUP); + }, + } + ) + } + + pub fn set_nonblocking( + &self, + val: bool, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + Ok( + match &mut inner.kind { + InodeSocketKind::TcpStream(sock) => { + sock.set_nonblocking(val).map_err(net_error_into_wasi_err)? + } + InodeSocketKind::TcpListener(sock, ..) => { + sock.set_nonblocking(val).map_err(net_error_into_wasi_err)? + } + InodeSocketKind::UdpSocket(sock, ..) => { + sock.set_nonblocking(val).map_err(net_error_into_wasi_err)? + } + InodeSocketKind::Raw(sock, ..) => { + sock.set_nonblocking(val).map_err(net_error_into_wasi_err)? + } + InodeSocketKind::Icmp(sock, ..) => { + sock.set_nonblocking(val).map_err(net_error_into_wasi_err)? + } + InodeSocketKind::PreSocket { nonblocking, .. } => { + (*nonblocking) = val; + } + _ => { + return Err(__WASI_ENOTSUP); + }, + } + ) + } + + pub fn linger(&self) -> Result, __wasi_errno_t> { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::TcpStream(sock) => sock.linger().map_err(net_error_into_wasi_err), InodeSocketKind::PreSocket { .. } => Err(Errno::Io), InodeSocketKind::Closed => Err(Errno::Io), @@ -588,11 +694,12 @@ impl InodeSocket { } pub fn set_opt_time( - &mut self, + &self, ty: TimeType, timeout: Option, - ) -> Result<(), Errno> { - match &mut self.kind { + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::TcpStream(sock) => sock .set_opt_time(ty, timeout) .map_err(net_error_into_wasi_err), @@ -632,8 +739,9 @@ impl InodeSocket { } } - pub fn opt_time(&self, ty: TimeType) -> Result, Errno> { - match &self.kind { + pub fn opt_time(&self, ty: TimeType) -> Result, __wasi_errno_t> { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::TcpStream(sock) => sock.opt_time(ty).map_err(net_error_into_wasi_err), InodeSocketKind::TcpListener(sock) => match ty { TimeType::AcceptTimeout => sock.timeout().map_err(net_error_into_wasi_err), @@ -657,8 +765,9 @@ impl InodeSocket { } } - pub fn set_ttl(&mut self, ttl: u32) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_ttl(&self, ttl: u32) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::TcpStream(sock) => sock.set_ttl(ttl).map_err(net_error_into_wasi_err), InodeSocketKind::UdpSocket(sock) => sock.set_ttl(ttl).map_err(net_error_into_wasi_err), InodeSocketKind::PreSocket { .. } => Err(Errno::Io), @@ -667,8 +776,9 @@ impl InodeSocket { } } - pub fn ttl(&self) -> Result { - match &self.kind { + pub fn ttl(&self) -> Result { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::TcpStream(sock) => sock.ttl().map_err(net_error_into_wasi_err), InodeSocketKind::UdpSocket(sock) => sock.ttl().map_err(net_error_into_wasi_err), InodeSocketKind::PreSocket { .. } => Err(Errno::Io), @@ -677,8 +787,9 @@ impl InodeSocket { } } - pub fn set_multicast_ttl_v4(&mut self, ttl: u32) -> Result<(), Errno> { - match &mut self.kind { + pub fn set_multicast_ttl_v4(&self, ttl: u32) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::UdpSocket(sock) => sock .set_multicast_ttl_v4(ttl) .map_err(net_error_into_wasi_err), @@ -688,8 +799,9 @@ impl InodeSocket { } } - pub fn multicast_ttl_v4(&self) -> Result { - match &self.kind { + pub fn multicast_ttl_v4(&self) -> Result { + let inner = self.inner.read().unwrap(); + match &inner.kind { InodeSocketKind::UdpSocket(sock) => { sock.multicast_ttl_v4().map_err(net_error_into_wasi_err) } @@ -699,8 +811,13 @@ impl InodeSocket { } } - pub fn join_multicast_v4(&mut self, multiaddr: Ipv4Addr, iface: Ipv4Addr) -> Result<(), Errno> { - match &mut self.kind { + pub async fn join_multicast_v4( + &self, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::UdpSocket(sock) => sock .join_multicast_v4(multiaddr, iface) .map_err(net_error_into_wasi_err), @@ -710,12 +827,13 @@ impl InodeSocket { } } - pub fn leave_multicast_v4( - &mut self, + pub async fn leave_multicast_v4( + &self, multiaddr: Ipv4Addr, iface: Ipv4Addr, - ) -> Result<(), Errno> { - match &mut self.kind { + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::UdpSocket(sock) => sock .leave_multicast_v4(multiaddr, iface) .map_err(net_error_into_wasi_err), @@ -725,8 +843,13 @@ impl InodeSocket { } } - pub fn join_multicast_v6(&mut self, multiaddr: Ipv6Addr, iface: u32) -> Result<(), Errno> { - match &mut self.kind { + pub async fn join_multicast_v6( + &self, + multiaddr: Ipv6Addr, + iface: u32, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::UdpSocket(sock) => sock .join_multicast_v6(multiaddr, iface) .map_err(net_error_into_wasi_err), @@ -736,8 +859,13 @@ impl InodeSocket { } } - pub fn leave_multicast_v6(&mut self, multiaddr: Ipv6Addr, iface: u32) -> Result<(), Errno> { - match &mut self.kind { + pub async fn leave_multicast_v6( + &self, + multiaddr: Ipv6Addr, + iface: u32, + ) -> Result<(), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { InodeSocketKind::UdpSocket(sock) => sock .leave_multicast_v6(multiaddr, iface) .map_err(net_error_into_wasi_err), @@ -747,20 +875,14 @@ impl InodeSocket { } } - pub fn send( - &mut self, - memory: &MemoryView, - iov: WasmSlice<__wasi_ciovec_t>, - ) -> Result { - let buf_len: M::Offset = iov - .iter() - .filter_map(|a| a.read().ok()) - .map(|a| a.buf_len) - .sum(); - let buf_len: usize = buf_len.try_into().map_err(|_| Errno::Inval)?; - let mut buf = Vec::with_capacity(buf_len); - write_bytes(&mut buf, memory, iov)?; - match &mut self.kind { + pub async fn send( + &self, + buf: Vec, + ) -> Result { + let buf_len = buf.len(); + let mut inner = self.inner.write().unwrap(); + + let ret = match &mut inner.kind { InodeSocketKind::HttpRequest(sock, ty) => { let sock = sock.get_mut().unwrap(); match ty { @@ -778,109 +900,179 @@ impl InodeSocket { } InodeSocketKind::WebSocket(sock) => sock .send(Bytes::from(buf)) + .await .map(|_| buf_len) .map_err(net_error_into_wasi_err), InodeSocketKind::Raw(sock) => { - sock.send(Bytes::from(buf)).map_err(net_error_into_wasi_err) + sock.send(Bytes::from(buf)).await.map_err(net_error_into_wasi_err) } InodeSocketKind::TcpStream(sock) => { - sock.send(Bytes::from(buf)).map_err(net_error_into_wasi_err) + sock.send(Bytes::from(buf)).await.map_err(net_error_into_wasi_err) } InodeSocketKind::UdpSocket(sock) => { - sock.send(Bytes::from(buf)).map_err(net_error_into_wasi_err) + sock.send(Bytes::from(buf)).await.map_err(net_error_into_wasi_err) } InodeSocketKind::PreSocket { .. } => Err(Errno::Notconn), InodeSocketKind::Closed => Err(Errno::Io), _ => Err(Errno::Notsup), } - .map(|_| buf_len) + .map(|_| buf_len)?; + + if ret > 0 { + inner.silence_write_ready = false; + } + Ok(ret) } - pub fn send_bytes(&mut self, buf: Bytes) -> Result { + pub async fn send_to( + &self, + buf: Vec, + addr: SocketAddr, + ) -> Result { let buf_len = buf.len(); - match &mut self.kind { + let mut inner = self.inner.write().unwrap(); + + let ret = match &mut inner.kind { + InodeSocketKind::Icmp(sock) => sock + .send_to(Bytes::from(buf), addr) + .await + .map_err(net_error_into_wasi_err), + InodeSocketKind::UdpSocket(sock) => sock + .send_to(Bytes::from(buf), addr) + .await + .map_err(net_error_into_wasi_err), + InodeSocketKind::PreSocket { .. } => Err(__WASI_ENOTCONN), + InodeSocketKind::Closed => Err(__WASI_EIO), + _ => Err(__WASI_ENOTSUP), + } + .map(|_| buf_len)?; + + if ret > 0 { + inner.silence_write_ready = false; + } + Ok(ret) + } + + pub fn peek( + &self, + ) -> Result { + let mut inner = self.inner.write().unwrap(); + if let Some(buf) = inner.read_buffer.as_ref() { + if buf.len() > 0 { + return Ok(buf.len()); + } + } + let data = match &mut inner.kind { InodeSocketKind::HttpRequest(sock, ty) => { let sock = sock.get_mut().unwrap(); match ty { - InodeHttpSocketType::Request => { - if sock.request.is_none() { - return Err(Errno::Io); + InodeHttpSocketType::Response => { + if sock.response.is_none() { + return Err(__WASI_EIO); } - let request = sock.request.as_ref().unwrap(); - request - .send(buf.to_vec()) - .map(|_| buf_len) - .map_err(|_| Errno::Io) + let response = sock.response.as_ref().unwrap(); + + use std::sync::mpsc::TryRecvError; + match response.try_recv() { + Ok(a) => Bytes::from(a), + Err(TryRecvError::Disconnected) => { return Err(__WASI_EIO); } + Err(TryRecvError::Empty) => { + return Ok(0); + } + } + } + InodeHttpSocketType::Headers => { + if sock.headers.is_none() { + return Err(__WASI_EIO); + } + let headers = sock.headers.as_ref().unwrap(); + + use std::sync::mpsc::TryRecvError; + let headers = match headers.try_recv() { + Ok(a) => a, + Err(TryRecvError::Disconnected) => { return Err(__WASI_EIO); } + Err(TryRecvError::Empty) => { + return Ok(0); + } + }; + + let headers = format!("{}: {}", headers.0, headers.1); + Bytes::from(headers.as_bytes().to_vec()) } _ => { return Err(Errno::Io); } } } - InodeSocketKind::WebSocket(sock) => sock - .send(buf) - .map(|_| buf_len) - .map_err(net_error_into_wasi_err), - InodeSocketKind::Raw(sock) => sock.send(buf).map_err(net_error_into_wasi_err), - InodeSocketKind::TcpStream(sock) => sock.send(buf).map_err(net_error_into_wasi_err), - InodeSocketKind::UdpSocket(sock) => sock.send(buf).map_err(net_error_into_wasi_err), - InodeSocketKind::PreSocket { .. } => Err(Errno::Notconn), - InodeSocketKind::Closed => Err(Errno::Io), - _ => Err(Errno::Notsup), - } - .map(|_| buf_len) - } - - pub fn send_to( - &mut self, - memory: &MemoryView, - iov: WasmSlice<__wasi_ciovec_t>, - addr: WasmPtr<__wasi_addr_port_t, M>, - ) -> Result { - let (addr_ip, addr_port) = read_ip_port(memory, addr)?; - let addr = SocketAddr::new(addr_ip, addr_port); - let buf_len: M::Offset = iov - .iter() - .filter_map(|a| a.read().ok()) - .map(|a| a.buf_len) - .sum(); - let buf_len: usize = buf_len.try_into().map_err(|_| Errno::Inval)?; - let mut buf = Vec::with_capacity(buf_len); - write_bytes(&mut buf, memory, iov)?; - match &mut self.kind { - InodeSocketKind::Icmp(sock) => sock - .send_to(Bytes::from(buf), addr) - .map_err(net_error_into_wasi_err), - InodeSocketKind::UdpSocket(sock) => sock - .send_to(Bytes::from(buf), addr) - .map_err(net_error_into_wasi_err), - InodeSocketKind::PreSocket { .. } => Err(Errno::Notconn), - InodeSocketKind::Closed => Err(Errno::Io), - _ => Err(Errno::Notsup), + InodeSocketKind::WebSocket(sock) => { + let read = match sock.try_recv().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + }; + read.data + } + InodeSocketKind::Raw(sock) => { + let read = match sock.try_recv().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + }; + read.data + } + InodeSocketKind::TcpStream(sock) => { + let read = match sock.try_recv().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + }; + read.data + } + InodeSocketKind::UdpSocket(sock) => { + let read = match sock.try_recv().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + }; + read.data + } + InodeSocketKind::TcpListener(sock) => { + return sock.peek().map_err(net_error_into_wasi_err); + } + InodeSocketKind::PreSocket { .. } => return Err(__WASI_ENOTCONN), + InodeSocketKind::Closed => return Err(__WASI_EIO), + _ => return Err(__WASI_ENOTSUP), + }; + inner.read_buffer.replace(data); + inner.read_addr.take(); + if let Some(buf) = inner.read_buffer.as_ref() { + Ok(buf.len()) + } else { + Ok(0) } - .map(|_| buf_len) } - pub fn recv( - &mut self, - memory: &MemoryView, - iov: WasmSlice<__wasi_iovec_t>, - ) -> Result { + pub async fn recv( + &self, + max_size: usize, + ) -> Result { + let mut inner = self.inner.write().unwrap(); loop { - if let Some(buf) = self.read_buffer.as_mut() { + let is_tcp = if let InodeSocketKind::TcpStream(..) = &inner.kind { + true + } else { + false + }; + if let Some(buf) = inner.read_buffer.as_mut() { let buf_len = buf.len(); if buf_len > 0 { - let reader = buf.as_ref(); - let read = read_bytes(reader, memory, iov).map(|_| buf_len)?; - if let InodeSocketKind::TcpStream(..) = &self.kind { + let read = buf_len.min(max_size); + let ret = buf.slice(..read); + if is_tcp { buf.advance(read); } else { buf.clear(); } - return Ok(read); + return Ok(ret); } } - let data = match &mut self.kind { + let data = match &mut inner.kind { InodeSocketKind::HttpRequest(sock, ty) => { let sock = sock.get_mut().unwrap(); match ty { @@ -906,67 +1098,118 @@ impl InodeSocket { } } InodeSocketKind::WebSocket(sock) => { - let read = sock.recv().map_err(net_error_into_wasi_err)?; + let read = sock.recv().await.map_err(net_error_into_wasi_err)?; read.data } InodeSocketKind::Raw(sock) => { - let read = sock.recv().map_err(net_error_into_wasi_err)?; + let read = sock.recv().await.map_err(net_error_into_wasi_err)?; read.data } InodeSocketKind::TcpStream(sock) => { - let read = sock.recv().map_err(net_error_into_wasi_err)?; + let read = sock.recv().await.map_err(net_error_into_wasi_err)?; read.data } InodeSocketKind::UdpSocket(sock) => { - let read = sock.recv().map_err(net_error_into_wasi_err)?; + let read = sock.recv().await.map_err(net_error_into_wasi_err)?; read.data } InodeSocketKind::PreSocket { .. } => return Err(Errno::Notconn), InodeSocketKind::Closed => return Err(Errno::Io), _ => return Err(Errno::Notsup), }; - self.read_buffer.replace(data); - self.read_addr.take(); + if data.len() == 0 { + return Err(__WASI_EIO); + } + inner.read_buffer.replace(data); + inner.read_addr.take(); + } + } + + pub async fn peek_from( + &self, + ) -> Result { + let mut inner = self.inner.write().unwrap(); + if let Some(buf) = inner.read_buffer.as_ref() { + if buf.len() > 0 { + return Ok(buf.len()); + } + } + let rcv = match &mut inner.kind { + InodeSocketKind::Icmp(sock) => { + match sock.try_recv_from().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + } + }, + InodeSocketKind::UdpSocket(sock) => { + match sock.try_recv_from().map_err(net_error_into_wasi_err)? { + Some(a) => a, + None => { return Ok(0); } + } + } + InodeSocketKind::PreSocket { .. } => return Err(__WASI_ENOTCONN), + InodeSocketKind::Closed => return Err(__WASI_EIO), + _ => return Err(__WASI_ENOTSUP), + }; + inner.read_buffer.replace(rcv.data); + inner.read_addr.replace(rcv.addr); + if let Some(buf) = inner.read_buffer.as_ref() { + Ok(buf.len()) + } else { + Ok(0) } } - pub fn recv_from( - &mut self, - memory: &MemoryView, - iov: WasmSlice<__wasi_iovec_t>, - addr: WasmPtr<__wasi_addr_port_t, M>, - ) -> Result { + pub async fn recv_from( + &self, + max_size: usize + ) -> Result<(Bytes, SocketAddr), __wasi_errno_t> { + let mut inner = self.inner.write().unwrap(); loop { - if let Some(buf) = self.read_buffer.as_mut() { + let is_tcp = if let InodeSocketKind::TcpStream(..) = &inner.kind { + true + } else { + false + }; + if let Some(buf) = inner.read_buffer.as_mut() { if !buf.is_empty() { - let reader = buf.as_ref(); - let ret = read_bytes(reader, memory, iov)?; - let peer = self + let buf_len = buf.len(); + let read = buf_len.min(max_size); + let ret = buf.slice(..read); + if is_tcp { + buf.advance(read); + } else { + buf.clear(); + } + let peer = inner .read_addr .unwrap_or_else(|| SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)); - write_ip_port(memory, addr, peer.ip(), peer.port())?; - return Ok(ret); + return Ok((ret, peer)); } } - let rcv = match &mut self.kind { - InodeSocketKind::Icmp(sock) => sock.recv_from().map_err(net_error_into_wasi_err)?, + let rcv = match &mut inner.kind { + InodeSocketKind::Icmp(sock) => { + sock.recv_from().await.map_err(net_error_into_wasi_err)? + }, InodeSocketKind::UdpSocket(sock) => { - sock.recv_from().map_err(net_error_into_wasi_err)? - } - InodeSocketKind::PreSocket { .. } => return Err(Errno::Notconn), - InodeSocketKind::Closed => return Err(Errno::Io), - _ => return Err(Errno::Notsup), + sock.recv_from().await.map_err(net_error_into_wasi_err)? + }, + InodeSocketKind::PreSocket { .. } => return Err(__WASI_ENOTCONN), + InodeSocketKind::Closed => return Err(__WASI_EIO), + _ => return Err(__WASI_ENOTSUP), }; - self.read_buffer.replace(rcv.data); - self.read_addr.replace(rcv.addr); + inner.read_buffer.replace(rcv.data); + inner.read_addr.replace(rcv.addr); } } - pub fn shutdown(&mut self, how: std::net::Shutdown) -> Result<(), Errno> { + pub async fn shutdown(&self, how: std::net::Shutdown) -> Result<(), __wasi_errno_t> { use std::net::Shutdown; - match &mut self.kind { + let mut inner = self.inner.write().unwrap(); + inner.silence_write_ready = false; + match &mut inner.kind { InodeSocketKind::TcpStream(sock) => { - sock.shutdown(how).map_err(net_error_into_wasi_err)?; + sock.shutdown(how).await.map_err(net_error_into_wasi_err)?; } InodeSocketKind::HttpRequest(http, ..) => { let http = http.get_mut().unwrap(); @@ -991,106 +1234,115 @@ impl InodeSocket { } Ok(()) } -} -impl Read for InodeSocket { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - loop { - if let Some(read_buf) = self.read_buffer.as_mut() { - let buf_len = read_buf.len(); - if buf_len > 0 { - let mut reader = read_buf.as_ref(); - let read = reader.read(buf)?; - read_buf.advance(read); - return Ok(read); - } - } - let data = match &mut self.kind { - InodeSocketKind::HttpRequest(sock, ty) => { - let sock = sock.get_mut().unwrap(); - match ty { - InodeHttpSocketType::Response => { - if sock.response.is_none() { - return Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "the socket is not connected".to_string(), - )); - } - let response = sock.response.as_ref().unwrap(); - Bytes::from(response.recv().map_err(|_| { - io::Error::new( - io::ErrorKind::BrokenPipe, - "the wasi pipe is not connected".to_string(), - ) - })?) - } - InodeHttpSocketType::Headers => { - if sock.headers.is_none() { - return Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "the socket is not connected".to_string(), - )); - } - let headers = sock.headers.as_ref().unwrap(); - let headers = headers.recv().map_err(|_| { - io::Error::new( - io::ErrorKind::BrokenPipe, - "the wasi pipe is not connected".to_string(), - ) - })?; - let headers = format!("{}: {}", headers.0, headers.1); - Bytes::from(headers.as_bytes().to_vec()) - } - _ => { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "the socket is of an unsupported type".to_string(), - )); - } - } - } - InodeSocketKind::WebSocket(sock) => { - let read = sock.recv().map_err(net_error_into_io_err)?; - read.data - } - InodeSocketKind::Raw(sock) => { - let read = sock.recv().map_err(net_error_into_io_err)?; - read.data - } - InodeSocketKind::TcpStream(sock) => { - let read = sock.recv().map_err(net_error_into_io_err)?; - read.data - } - InodeSocketKind::UdpSocket(sock) => { - let read = sock.recv().map_err(net_error_into_io_err)?; - read.data - } - InodeSocketKind::PreSocket { .. } => { - return Err(io::Error::new( - io::ErrorKind::NotConnected, - "the socket is not connected".to_string(), - )) - } - InodeSocketKind::Closed => { - return Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "the socket has been closed".to_string(), - )) - } + pub fn can_write(&self) -> bool { + if let Ok(mut guard) = self.inner.try_write() { + match &mut guard.kind { + InodeSocketKind::TcpListener(socket) => { + socket.peek().ok().map(|a| a > 0).unwrap_or_default() + }, + InodeSocketKind::TcpStream(..) | + InodeSocketKind::UdpSocket(..) | + InodeSocketKind::Raw(..) | + InodeSocketKind::WebSocket(..) => { + true + }, _ => { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "the socket type is not supported".to_string(), - )) + false } - }; - self.read_buffer.replace(data); - self.read_addr.take(); + } + } else { + false } } + + pub fn poll_read_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll> { + let mut inner = self.inner.write().unwrap(); + match &mut inner.kind { + InodeSocketKind::TcpListener(socket) => { + socket.poll_accept_ready(cx) + }, + InodeSocketKind::TcpStream(socket) => { + socket.poll_read_ready(cx) + } + InodeSocketKind::UdpSocket(socket) => { + socket.poll_read_ready(cx) + } + InodeSocketKind::Raw(socket) => { + socket.poll_read_ready(cx) + } + InodeSocketKind::WebSocket(socket) => { + socket.poll_read_ready(cx) + }, + InodeSocketKind::Icmp(socket) => { + socket.poll_read_ready(cx) + }, + InodeSocketKind::PreSocket{ .. } => { + std::task::Poll::Ready(Err(wasmer_vnet::NetworkError::IOError)) + }, + InodeSocketKind::HttpRequest(..) => { + std::task::Poll::Pending + }, + InodeSocketKind::Closed => { + std::task::Poll::Ready(Err(wasmer_vnet::NetworkError::ConnectionAborted)) + }, + } + } + + pub fn poll_write_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll> { + let mut inner = self.inner.write().unwrap(); + if inner.silence_write_ready { + return std::task::Poll::Pending; + } + let ret = match &mut inner.kind { + InodeSocketKind::TcpListener(_) => { + std::task::Poll::Pending + }, + InodeSocketKind::TcpStream(socket) => { + socket.poll_write_ready(cx) + } + InodeSocketKind::UdpSocket(socket) => { + socket.poll_write_ready(cx) + } + InodeSocketKind::Raw(socket) => { + socket.poll_write_ready(cx) + } + InodeSocketKind::WebSocket(socket) => { + socket.poll_write_ready(cx) + }, + InodeSocketKind::Icmp(socket) => { + socket.poll_write_ready(cx) + }, + InodeSocketKind::PreSocket{ .. } => { + std::task::Poll::Ready(Err(wasmer_vnet::NetworkError::IOError)) + }, + InodeSocketKind::HttpRequest(..) => { + std::task::Poll::Pending + }, + InodeSocketKind::Closed => { + std::task::Poll::Ready(Err(wasmer_vnet::NetworkError::ConnectionAborted)) + }, + }; + if ret.is_ready() { + // TODO - This will suppress the write ready notifications + inner.silence_write_ready = true; + } + ret + } +} + +#[derive(Default)] +struct IndefinitePoll { +} +impl Future +for IndefinitePoll { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll { + std::task::Poll::Pending + } } -impl Drop for InodeSocket { +impl Drop for InodeSocketInner { fn drop(&mut self) { if let InodeSocketKind::HttpRequest(http, ty) = &self.kind { let mut guard = http.lock().unwrap(); diff --git a/lib/wasi/src/state/thread.rs b/lib/wasi/src/state/thread.rs new file mode 100644 index 00000000000..89b60cc4f11 --- /dev/null +++ b/lib/wasi/src/state/thread.rs @@ -0,0 +1,705 @@ +use std::{ + sync::{ + Mutex, + Arc, + Condvar, RwLock, atomic::{AtomicU32, Ordering}, RwLockWriteGuard, RwLockReadGuard + }, + time::Duration, collections::{HashMap, HashSet}, borrow::Cow, ops::{Deref, DerefMut} +}; + +use bytes::{BytesMut, Bytes}; +use tracing::log::trace; +use wasmer_vbus::{BusSpawnedProcess, SignalHandlerAbi}; +use wasmer_wasi_types::{__wasi_signal_t, __wasi_exitcode_t, __WASI_CLOCK_MONOTONIC, __wasi_errno_t, __WASI_ECHILD}; + +use crate::syscalls::platform_clock_time_get; + +/// Represents the ID of a WASI thread +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WasiThreadId(u32); + +impl WasiThreadId { + pub fn raw(&self) -> u32 { + self.0 + } + + pub fn inc(&mut self) -> WasiThreadId { + let ret = self.clone(); + self.0 += 1; + ret + } +} + +impl From for WasiThreadId { + fn from(id: u32) -> Self { + Self(id) + } +} +impl From for u32 { + fn from(t: WasiThreadId) -> u32 { + t.0 as u32 + } +} + +impl std::fmt::Display +for WasiThreadId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Represents a linked list of stack snapshots +#[derive(Debug, Clone)] +struct ThreadSnapshot +{ + call_stack: Bytes, + store_data: Bytes, +} + +/// Represents a linked list of stack snapshots +#[derive(Debug, Clone, Default)] +struct ThreadStack +{ + memory_stack: Vec, + memory_stack_corrected: Vec, + snapshots: HashMap, + next: Option>, +} + +/// Represents a running thread which allows a joiner to +/// wait for the thread to exit +#[derive(Debug, Clone)] +pub struct WasiThread +{ + pub(crate) is_main: bool, + pub(crate) pid: WasiProcessId, + pub(crate) id: WasiThreadId, + finished: Arc<(Mutex>, Condvar)>, + pub(crate) signals: Arc<(Mutex>, tokio::sync::broadcast::Sender<()>)>, + stack: Arc>, +} + +impl WasiThread +{ + /// Returns the process ID + pub fn pid(&self) -> WasiProcessId { + self.pid + } + + /// Returns the thread ID + pub fn tid(&self) -> WasiThreadId { + self.id + } + + /// Returns true if this thread is the main thread + pub fn is_main(&self) -> bool { + self.is_main + } + + /// Marks the thread as finished (which will cause anyone that + /// joined on it to wake up) + pub fn terminate(&self, exit_code: u32) { + let mut guard = self.finished.0.lock().unwrap(); + if guard.is_none() { + *guard = Some(exit_code); + } + self.finished.1.notify_all(); + } + + /// Waits until the thread is finished or the timeout is reached + pub fn join(&self, timeout: Duration) -> Option<__wasi_exitcode_t> { + let mut finished = self.finished.0.lock().unwrap(); + if finished.is_some() { + return finished.clone(); + } + loop { + let woken = self.finished.1.wait_timeout(finished, timeout).unwrap(); + if woken.1.timed_out() { + return None; + } + finished = woken.0; + if finished.is_some() { + return finished.clone(); + } + } + } + + /// Attempts to join on the thread + pub fn try_join(&self) -> Option<__wasi_exitcode_t> { + let guard = self.finished.0.lock().unwrap(); + guard.clone() + } + + /// Adds a signal for this thread to process + pub fn signal(&self, signal: __wasi_signal_t) { + let mut guard = self.signals.0.lock().unwrap(); + if guard.contains(&signal) == false { + guard.push(signal); + } + let _ = self.signals.1.send(()); + } + + /// Returns all the signals that are waiting to be processed + pub fn pop_signals(&self) -> Vec<__wasi_signal_t> { + let mut guard = self.signals.0.lock().unwrap(); + guard.drain(..).collect() + } + + pub fn subscribe_signals(&self) -> tokio::sync::broadcast::Receiver<()> { + self.signals.1.subscribe() + } + + /// Adds a stack snapshot and removes dead ones + pub fn add_snapshot(&self, mut memory_stack: &[u8], memory_stack_corrected: &[u8], hash: u128, rewind_stack: &[u8], store_data: &[u8]) { + // Lock the stack + let mut stack = self.stack.lock().unwrap(); + let mut pstack = stack.deref_mut(); + loop { + // First we validate if the stack is no longer valid + let memory_stack_before = pstack.memory_stack.len(); + let memory_stack_after= memory_stack.len(); + if memory_stack_before > memory_stack_after || + ( + pstack.memory_stack.iter().zip(memory_stack.iter()).any(|(a, b)| *a == *b) == false && + pstack.memory_stack_corrected.iter().zip(memory_stack.iter()).any(|(a, b)| *a == *b) == false + ) + { + // The stacks have changed so need to start again at this segment + let mut new_stack = ThreadStack::default(); + new_stack.memory_stack = memory_stack.to_vec(); + new_stack.memory_stack_corrected = memory_stack_corrected.to_vec(); + std::mem::swap(pstack, &mut new_stack); + memory_stack = &memory_stack[memory_stack.len()..]; + + // Output debug info for the dead stack + let mut disown = Some(Box::new(new_stack)); + if disown.is_some() { + tracing::trace!("wasi[{}]::stacks forgotten (memory_stack_before={}, memory_stack_after={})", self.pid, memory_stack_before, memory_stack_after); + } + while let Some(disowned) = disown { + for hash in disowned.snapshots.keys() { + tracing::trace!("wasi[{}]::stack has been forgotten (hash={})", self.pid, hash); + } + disown = disowned.next; + } + } else { + memory_stack = &memory_stack[pstack.memory_stack.len()..]; + } + + // If there is no more memory stack then we are done and can add the call stack + if memory_stack.len() <= 0 { + break; + } + + // Otherwise we need to add a next stack pointer and continue the iterations + if pstack.next.is_none() { + let mut new_stack = ThreadStack::default(); + new_stack.memory_stack = memory_stack.to_vec(); + pstack.next.replace(Box::new(new_stack)); + } + pstack = pstack.next.as_mut().unwrap(); + } + + // Add the call stack + pstack.snapshots.insert(hash, ThreadSnapshot { + call_stack: BytesMut::from(rewind_stack).freeze(), + store_data: BytesMut::from(store_data).freeze(), + }); + } + + /// Gets a snapshot that was previously addedf + pub fn get_snapshot(&self, hash: u128) -> Option<(BytesMut, Bytes, Bytes)> { + let mut memory_stack = BytesMut::new(); + + let stack = self.stack.lock().unwrap(); + let mut pstack = stack.deref(); + loop { + memory_stack.extend(pstack.memory_stack_corrected.iter()); + if let Some(snapshot) = pstack.snapshots.get(&hash) { + return Some((memory_stack, snapshot.call_stack.clone(), snapshot.store_data.clone())); + } + if let Some(next) = pstack.next.as_ref() { + pstack = next.deref(); + } else { + return None; + } + } + } + + // Copy the stacks from another thread + pub fn copy_stack_from(&self, other: &WasiThread) { + let mut stack = { + let stack_guard = other.stack.lock().unwrap(); + stack_guard.clone() + }; + + let mut stack_guard = self.stack.lock().unwrap(); + std::mem::swap(stack_guard.deref_mut(), &mut stack); + } +} + +#[derive(Debug, Clone)] +pub struct WasiThreadHandle { + id: Arc, + thread: WasiThread, + inner: Arc>, +} + +impl WasiThreadHandle { + pub fn id(&self) -> WasiThreadId { + self.id.0.into() + } + + pub fn as_thread(&self) -> WasiThread { + self.thread.clone() + } +} + +impl Drop +for WasiThreadHandle { + fn drop(&mut self) { + // We do this so we track when the last handle goes out of scope + if let Some(id) = Arc::get_mut(&mut self.id) { + let mut inner = self.inner.write().unwrap(); + if let Some(ctrl) = inner.threads.remove(id) { + ctrl.terminate(0); + } + inner.thread_count -= 1; + } + } +} + +impl std::ops::Deref +for WasiThreadHandle { + type Target = WasiThread; + + fn deref(&self) -> &Self::Target { + &self.thread + } +} + +impl std::ops::DerefMut +for WasiThreadHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.thread + } +} + +/// Represents the ID of a sub-process +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WasiProcessId(u32); + +impl WasiProcessId { + pub fn raw(&self) -> u32 { + self.0 + } +} + +impl From for WasiProcessId { + fn from(id: i32) -> Self { + Self(id as u32) + } +} +impl Into for WasiProcessId { + fn into(self) -> i32 { + self.0 as i32 + } +} + +impl From for WasiProcessId { + fn from(id: u32) -> Self { + Self(id) + } +} +impl Into for WasiProcessId { + fn into(self) -> u32 { + self.0 as u32 + } +} + +impl std::fmt::Display +for WasiProcessId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug)] +pub struct WasiSignalInterval +{ + /// Signal that will be raised + pub signal: u8, + /// Time between the signals + pub interval: Duration, + /// Flag that indicates if the signal should repeat + pub repeat: bool, + /// Last time that a signal was triggered + pub last_signal: u128, +} + +#[derive(Debug)] +pub struct WasiProcessInner +{ + /// The threads that make up this process + pub threads: HashMap, + /// Number of threads running for this process + pub thread_count: u32, + /// Seed used to generate thread ID's + pub thread_seed: WasiThreadId, + /// All the thread local variables + pub thread_local: HashMap<(WasiThreadId, u32), u64>, + /// User data associated with thread local data + pub thread_local_user_data: HashMap, + /// Seed used to generate thread locals + pub thread_local_seed: u32, + /// Signals that will be triggered at specific intervals + pub signal_intervals: HashMap, + /// Represents all the process spun up as a bus process + pub bus_processes: HashMap>, + /// Indicates if the bus process can be reused + pub bus_process_reuse: HashMap, WasiProcessId>, +} + +/// Represents a process running within the compute state +#[derive(Debug, Clone)] +pub struct WasiProcess +{ + /// Unique ID of this process + pub(crate) pid: WasiProcessId, + /// ID of the parent process + pub(crate) ppid: WasiProcessId, + /// The inner protected region of the process + pub(crate) inner: Arc>, + /// Reference back to the compute engine + pub(crate) compute: WasiControlPlane, + /// Reference to the exit code for the main thread + pub(crate) finished: Arc<(Mutex>, Condvar)>, + /// List of all the children spawned from this thread + pub(crate) children: Arc>>, + /// Number of threads waiting for children to exit + pub(crate) waiting: Arc, +} + +pub(crate) struct WasiProcessWait { + waiting: Arc, +} + +impl WasiProcessWait { + pub fn new(process: &WasiProcess) -> Self { + process.waiting.fetch_add(1, Ordering::AcqRel); + Self { + waiting: process.waiting.clone() + } + } +} + +impl Drop +for WasiProcessWait { + fn drop(&mut self) { + self.waiting.fetch_sub(1, Ordering::AcqRel); + } +} + +impl WasiProcess +{ + /// Gets the process ID of this process + pub fn pid(&self) -> WasiProcessId { + self.pid + } + + /// Gets the process ID of the parent process + pub fn ppid(&self) -> WasiProcessId { + self.ppid + } + + /// Gains write access to the process internals + pub fn write(&self) -> RwLockWriteGuard { + self.inner.write().unwrap() + } + + /// Gains read access to the process internals + pub fn read(&self) -> RwLockReadGuard { + self.inner.read().unwrap() + } + + /// Creates a a thread and returns it + pub fn new_thread(&self) -> WasiThreadHandle { + let mut inner = self.inner.write().unwrap(); + let id = inner.thread_seed.inc(); + + let mut is_main = false; + let finished = if inner.thread_count <= 0 { + is_main = true; + self.finished.clone() + } else { + Arc::new((Mutex::new(None), Condvar::default())) + }; + + let (tx_signals, _) = tokio::sync::broadcast::channel(1); + let ctrl = WasiThread { + pid: self.pid(), + id, + is_main, + finished, + signals: Arc::new((Mutex::new(Vec::new()), tx_signals)), + stack: Arc::new(Mutex::new(ThreadStack::default())) + }; + inner.threads.insert(id, ctrl.clone()); + inner.thread_count += 1; + + WasiThreadHandle { + id: Arc::new(id), + thread: ctrl, + inner: self.inner.clone(), + } + } + + /// Gets a reference to a particular thread + pub fn get_thread(&self, tid: &WasiThreadId) -> Option { + let inner = self.inner.read().unwrap(); + inner.threads.get(tid).map(|a| a.clone()) + } + + /// Signals a particular thread in the process + pub fn signal_thread(&self, tid: &WasiThreadId, signal: __wasi_signal_t) { + let inner = self.inner.read().unwrap(); + if let Some(thread) = inner.threads.get(tid) { + thread.signal(signal); + } else { + trace!("wasi[{}]::lost-signal(tid={}, sig={})", self.pid(), tid.0, signal); + } + } + + /// Signals all the threads in this process + pub fn signal_process(&self, signal: __wasi_signal_t) { + if self.waiting.load(Ordering::Acquire) > 0 { + let children = self.children.read().unwrap(); + for pid in children.iter() { + if let Some(process) = self.compute.get_process(*pid) { + process.signal_process(signal); + } + } + return; + } + let inner = self.inner.read().unwrap(); + for thread in inner.threads.values() { + thread.signal(signal); + } + } + + /// Signals one of the threads every interval + pub fn signal_interval(&self, signal: __wasi_signal_t, interval: Option, repeat: bool) { + let mut inner = self.inner.write().unwrap(); + + let interval = match interval { + None => { + inner.signal_intervals.remove(&signal); + return; + }, + Some(a) => a, + }; + + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + inner.signal_intervals.insert(signal, + WasiSignalInterval { + signal, + interval, + last_signal: now, + repeat + } + ); + } + + /// Returns the number of active threads for this process + pub fn active_threads(&self) -> u32 { + let inner = self.inner.read().unwrap(); + inner.thread_count + } + + /// Waits until the process is finished or the timeout is reached + pub fn join(&self, timeout: Duration) -> Option<__wasi_exitcode_t> { + let _guard = WasiProcessWait::new(self); + let mut finished = self.finished.0.lock().unwrap(); + if finished.is_some() { + return finished.clone(); + } + loop { + let woken = self.finished.1.wait_timeout(finished, timeout).unwrap(); + if woken.1.timed_out() { + return None; + } + finished = woken.0; + if finished.is_some() { + return finished.clone(); + } + } + } + + /// Waits for all the children to be finished + pub fn join_children(&mut self, timeout: Duration) -> Option<__wasi_exitcode_t> { + let _guard = WasiProcessWait::new(self); + let mut exit_code = 0; + let children: Vec<_> = { + let children = self.children.read().unwrap(); + children.clone() + }; + for pid in children { + if let Some(process) = self.compute.get_process(pid) { + match process.join(timeout) { + Some(a) => { + let mut children = self.children.write().unwrap(); + children.retain(|a| *a != pid); + exit_code = a; + }, + None => { + return None; + } + } + } + } + Some(exit_code) + } + + /// Waits for all the children to be finished + pub fn join_any_child(&mut self, timeout: Duration) -> Result, __wasi_errno_t> { + let _guard = WasiProcessWait::new(self); + let children: Vec<_> = { + let children = self.children.read().unwrap(); + children.clone() + }; + if children.is_empty() { + return Err(__WASI_ECHILD); + } + for pid in children { + if let Some(process) = self.compute.get_process(pid) { + if let Some(exit_code) = process.join(timeout) { + let pid = process.pid(); + let mut children = self.children.write().unwrap(); + children.retain(|a| *a != pid); + return Ok(Some((pid, exit_code))) + } + } + } + Ok(None) + } + + /// Attempts to join on the process + pub fn try_join(&self) -> Option<__wasi_exitcode_t> { + let guard = self.finished.0.lock().unwrap(); + guard.clone() + } + + /// Terminate the process and all its threads + pub fn terminate(&self, exit_code: u32) { + let guard = self.inner.read().unwrap(); + for thread in guard.threads.values() { + thread.terminate(exit_code) + } + } + + /// Gains access to the compute control plane + pub fn control_plane(&self) -> &WasiControlPlane { + &self.compute + } +} + +impl SignalHandlerAbi +for WasiProcess { + fn signal(&self, sig: __wasi_signal_t) { + self.signal_process(sig) + } +} + +#[derive(Debug, Clone)] +pub struct WasiControlPlane +{ + /// The processes running on this machine + pub(crate) processes: Arc>>, + /// Seed used to generate process ID's + pub(crate) process_seed: Arc, + /// Allows for a PID to be reserved + pub(crate) reserved: Arc>>, +} + +impl Default +for WasiControlPlane +{ + fn default() -> Self { + Self { + processes: Default::default(), + process_seed: Arc::new(AtomicU32::new(0)), + reserved: Default::default(), + } + } +} + +impl WasiControlPlane +{ + /// Reserves a PID and returns it + pub fn reserve_pid(&self) -> WasiProcessId { + let mut pid: WasiProcessId; + loop { + pid = self.process_seed.fetch_add(1, Ordering::AcqRel).into(); + + { + let mut guard = self.reserved.lock().unwrap(); + if guard.contains(&pid) { + continue; + } + guard.insert(pid); + } + + { + let guard = self.processes.read().unwrap(); + if guard.contains_key(&pid) == false { + break; + } + } + + { + let mut guard = self.reserved.lock().unwrap(); + guard.remove(&pid); + } + } + pid + } + + /// Creates a new process + pub fn new_process(&self) -> WasiProcess { + let pid = self.reserve_pid(); + let ret = WasiProcess { + pid, + ppid: 0u32.into(), + compute: self.clone(), + inner: Arc::new(RwLock::new(WasiProcessInner { + threads: Default::default(), + thread_count: Default::default(), + thread_seed: Default::default(), + thread_local: Default::default(), + thread_local_user_data: Default::default(), + thread_local_seed: Default::default(), + signal_intervals: Default::default(), + bus_processes: Default::default(), + bus_process_reuse: Default::default() + })), + children: Arc::new(RwLock::new(Default::default())), + finished: Arc::new((Mutex::new(None), Condvar::default())), + waiting: Arc::new(AtomicU32::new(0)) + }; + { + let mut guard = self.processes.write().unwrap(); + guard.insert(pid, ret.clone()); + } + { + let mut guard = self.reserved.lock().unwrap(); + guard.remove(&pid); + } + ret + } + + /// Gets a reference to a running process + pub fn get_process(&self, pid: WasiProcessId) -> Option { + let guard = self.processes.read().unwrap(); + guard.get(&pid).map(|a| a.clone()) + } +} diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 08b448409bd..d99c01b7e38 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,72 +1,82 @@ /// types for use in the WASI filesystem #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; -#[cfg(all(unix, feature = "sys-poll"))] +#[cfg(all(unix, feature = "sys-poll", not(feature="os")))] use std::convert::TryInto; -use std::time::Duration; -use wasmer_vbus::BusError; -use wasmer_wasi_types::wasi::{BusErrno, Errno}; +use std::{ + collections::VecDeque, + io::{self, Read, Seek, Write}, + sync::{Arc, Mutex}, + time::Duration, +}; +use wasmer_vbus::VirtualBusError; #[cfg(feature = "host-fs")] pub use wasmer_vfs::host_fs::{Stderr, Stdin, Stdout}; -#[cfg(feature = "mem-fs")] +#[cfg(all(feature = "mem-fs", not(feature = "host-fs")))] pub use wasmer_vfs::mem_fs::{Stderr, Stdin, Stdout}; +#[cfg(all(not(feature = "mem-fs"), not(feature = "host-fs")))] +pub use crate::{ + fs::NullFile as Stderr, + fs::NullFile as Stdin, + fs::NullFile as Stdout, +}; use wasmer_vfs::{FsError, VirtualFile}; use wasmer_vnet::NetworkError; pub fn fs_error_from_wasi_err(err: Errno) -> FsError { match err { - Errno::Badf => FsError::InvalidFd, - Errno::Exist => FsError::AlreadyExists, - Errno::Io => FsError::IOError, - Errno::Addrinuse => FsError::AddressInUse, - Errno::Addrnotavail => FsError::AddressNotAvailable, - Errno::Pipe => FsError::BrokenPipe, - Errno::Connaborted => FsError::ConnectionAborted, - Errno::Connrefused => FsError::ConnectionRefused, - Errno::Connreset => FsError::ConnectionReset, - Errno::Intr => FsError::Interrupted, - Errno::Inval => FsError::InvalidInput, - Errno::Notconn => FsError::NotConnected, - Errno::Nodev => FsError::NoDevice, - Errno::Noent => FsError::EntityNotFound, - Errno::Perm => FsError::PermissionDenied, - Errno::Timedout => FsError::TimedOut, - Errno::Proto => FsError::UnexpectedEof, - Errno::Again => FsError::WouldBlock, - Errno::Nospc => FsError::WriteZero, - Errno::Notempty => FsError::DirectoryNotEmpty, + __WASI_EBADF => FsError::InvalidFd, + __WASI_EEXIST => FsError::AlreadyExists, + __WASI_EIO => FsError::IOError, + __WASI_EADDRINUSE => FsError::AddressInUse, + __WASI_EADDRNOTAVAIL => FsError::AddressNotAvailable, + __WASI_EPIPE => FsError::BrokenPipe, + __WASI_ECONNABORTED => FsError::ConnectionAborted, + __WASI_ECONNREFUSED => FsError::ConnectionRefused, + __WASI_ECONNRESET => FsError::ConnectionReset, + __WASI_EINTR => FsError::Interrupted, + __WASI_EINVAL => FsError::InvalidInput, + __WASI_ENOTCONN => FsError::NotConnected, + __WASI_ENODEV => FsError::NoDevice, + __WASI_ENOENT => FsError::EntryNotFound, + __WASI_EPERM => FsError::PermissionDenied, + __WASI_ETIMEDOUT => FsError::TimedOut, + __WASI_EPROTO => FsError::UnexpectedEof, + __WASI_EAGAIN => FsError::WouldBlock, + __WASI_ENOSPC => FsError::WriteZero, + __WASI_ENOTEMPTY => FsError::DirectoryNotEmpty, _ => FsError::UnknownError, } } pub fn fs_error_into_wasi_err(fs_error: FsError) -> Errno { match fs_error { - FsError::AlreadyExists => Errno::Exist, - FsError::AddressInUse => Errno::Addrinuse, - FsError::AddressNotAvailable => Errno::Addrnotavail, - FsError::BaseNotDirectory => Errno::Notdir, - FsError::BrokenPipe => Errno::Pipe, - FsError::ConnectionAborted => Errno::Connaborted, - FsError::ConnectionRefused => Errno::Connrefused, - FsError::ConnectionReset => Errno::Connreset, - FsError::Interrupted => Errno::Intr, - FsError::InvalidData => Errno::Io, - FsError::InvalidFd => Errno::Badf, - FsError::InvalidInput => Errno::Inval, - FsError::IOError => Errno::Io, - FsError::NoDevice => Errno::Nodev, - FsError::NotAFile => Errno::Inval, - FsError::NotConnected => Errno::Notconn, - FsError::EntityNotFound => Errno::Noent, - FsError::PermissionDenied => Errno::Perm, - FsError::TimedOut => Errno::Timedout, - FsError::UnexpectedEof => Errno::Proto, - FsError::WouldBlock => Errno::Again, - FsError::WriteZero => Errno::Nospc, - FsError::DirectoryNotEmpty => Errno::Notempty, - FsError::Lock | FsError::UnknownError => Errno::Io, + FsError::AlreadyExists => __WASI_EEXIST, + FsError::AddressInUse => __WASI_EADDRINUSE, + FsError::AddressNotAvailable => __WASI_EADDRNOTAVAIL, + FsError::BaseNotDirectory => __WASI_ENOTDIR, + FsError::BrokenPipe => __WASI_EPIPE, + FsError::ConnectionAborted => __WASI_ECONNABORTED, + FsError::ConnectionRefused => __WASI_ECONNREFUSED, + FsError::ConnectionReset => __WASI_ECONNRESET, + FsError::Interrupted => __WASI_EINTR, + FsError::InvalidData => __WASI_EIO, + FsError::InvalidFd => __WASI_EBADF, + FsError::InvalidInput => __WASI_EINVAL, + FsError::IOError => __WASI_EIO, + FsError::NoDevice => __WASI_ENODEV, + FsError::NotAFile => __WASI_EINVAL, + FsError::NotConnected => __WASI_ENOTCONN, + FsError::EntryNotFound => __WASI_ENOENT, + FsError::PermissionDenied => __WASI_EPERM, + FsError::TimedOut => __WASI_ETIMEDOUT, + FsError::UnexpectedEof => __WASI_EPROTO, + FsError::WouldBlock => __WASI_EAGAIN, + FsError::WriteZero => __WASI_ENOSPC, + FsError::DirectoryNotEmpty => __WASI_ENOTEMPTY, + FsError::Lock | FsError::UnknownError => __WASI_EIO, } } @@ -97,57 +107,73 @@ pub fn net_error_into_wasi_err(net_error: NetworkError) -> Errno { } } -pub fn bus_error_into_wasi_err(bus_error: BusError) -> BusErrno { - use BusError::*; +pub fn bus_error_into_wasi_err(bus_error: VirtualBusError) -> __bus_errno_t { + use VirtualBusError::*; match bus_error { - Serialization => BusErrno::Ser, - Deserialization => BusErrno::Des, - InvalidWapm => BusErrno::Wapm, - FetchFailed => BusErrno::Fetch, - CompileError => BusErrno::Compile, - InvalidABI => BusErrno::Abi, - Aborted => BusErrno::Aborted, - BadHandle => BusErrno::Badhandle, - InvalidTopic => BusErrno::Topic, - BadCallback => BusErrno::Badcb, - Unsupported => BusErrno::Unsupported, - BadRequest => BusErrno::Badrequest, - AccessDenied => BusErrno::Denied, - InternalError => BusErrno::Internal, - MemoryAllocationFailed => BusErrno::Alloc, - InvokeFailed => BusErrno::Invoke, - AlreadyConsumed => BusErrno::Consumed, - MemoryAccessViolation => BusErrno::Memviolation, - UnknownError => BusErrno::Unknown, + Serialization => __BUS_ESER, + Deserialization => __BUS_EDES, + NotFound => __BUS_EWAPM, + InvalidWapm => __BUS_EWAPM, + FetchFailed => __BUS_EFETCH, + CompileError => __BUS_ECOMPILE, + InvalidABI => __BUS_EABI, + Aborted => __BUS_EABORTED, + BadHandle => __BUS_EBADHANDLE, + InvalidTopic => __BUS_ETOPIC, + BadCallback => __BUS_EBADCB, + Unsupported => __BUS_EUNSUPPORTED, + BadRequest => __BUS_EBADREQUEST, + AccessDenied => __BUS_EDENIED, + InternalError => __BUS_EINTERNAL, + MemoryAllocationFailed => __BUS_EALLOC, + InvokeFailed => __BUS_EINVOKE, + AlreadyConsumed => __BUS_ECONSUMED, + MemoryAccessViolation => __BUS_EMEMVIOLATION, + UnknownError => __BUS_EUNKNOWN, } } -pub fn wasi_error_into_bus_err(bus_error: BusErrno) -> BusError { - use BusError::*; +pub fn wasi_error_into_bus_err(bus_error: __bus_errno_t) -> VirtualBusError { + use VirtualBusError::*; match bus_error { - BusErrno::Success => UnknownError, - BusErrno::Ser => Serialization, - BusErrno::Des => Deserialization, - BusErrno::Wapm => InvalidWapm, - BusErrno::Fetch => FetchFailed, - BusErrno::Compile => CompileError, - BusErrno::Abi => InvalidABI, - BusErrno::Aborted => Aborted, - BusErrno::Badhandle => BadHandle, - BusErrno::Topic => InvalidTopic, - BusErrno::Badcb => BadCallback, - BusErrno::Unsupported => Unsupported, - BusErrno::Badrequest => BadRequest, - BusErrno::Denied => AccessDenied, - BusErrno::Internal => InternalError, - BusErrno::Alloc => MemoryAllocationFailed, - BusErrno::Invoke => InvokeFailed, - BusErrno::Consumed => AlreadyConsumed, - BusErrno::Memviolation => MemoryAccessViolation, - BusErrno::Unknown => UnknownError, + __BUS_ESER => Serialization, + __BUS_EDES => Deserialization, + __BUS_EWAPM => InvalidWapm, + __BUS_EFETCH => FetchFailed, + __BUS_ECOMPILE => CompileError, + __BUS_EABI => InvalidABI, + __BUS_EABORTED => Aborted, + __BUS_EBADHANDLE => BadHandle, + __BUS_ETOPIC => InvalidTopic, + __BUS_EBADCB => BadCallback, + __BUS_EUNSUPPORTED => Unsupported, + __BUS_EBADREQUEST => BadRequest, + __BUS_EDENIED => AccessDenied, + __BUS_EINTERNAL => InternalError, + __BUS_EALLOC => MemoryAllocationFailed, + __BUS_EINVOKE => InvokeFailed, + __BUS_ECONSUMED => AlreadyConsumed, + __BUS_EMEMVIOLATION => MemoryAccessViolation, + __BUS_EUNKNOWN | _ => UnknownError, } } +#[allow(dead_code)] +pub(crate) fn bus_read_rights() -> __wasi_rights_t { + __WASI_RIGHT_FD_FDSTAT_SET_FLAGS + | __WASI_RIGHT_FD_FILESTAT_GET + | __WASI_RIGHT_FD_READ + | __WASI_RIGHT_POLL_FD_READWRITE +} + +#[allow(dead_code)] +pub(crate) fn bus_write_rights() -> __wasi_rights_t { + __WASI_RIGHT_FD_FDSTAT_SET_FLAGS + | __WASI_RIGHT_FD_FILESTAT_GET + | __WASI_RIGHT_FD_WRITE + | __WASI_RIGHT_POLL_FD_READWRITE +} + #[derive(Debug, Clone)] #[allow(clippy::enum_variant_names)] pub enum PollEvent { @@ -214,7 +240,7 @@ pub fn iterate_poll_events(pes: PollEventSet) -> PollEventIter { PollEventIter { pes, i: 0 } } -#[cfg(all(unix, feature = "sys-poll"))] +#[cfg(all(unix, feature = "sys-poll", not(feature="os")))] fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 { let mut out = 0; for i in 0..16 { @@ -231,7 +257,7 @@ fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 { out } -#[cfg(all(unix, feature = "sys-poll"))] +#[cfg(all(unix, feature = "sys-poll", not(feature="os")))] fn platform_poll_events_to_pollevent_set(mut num: i16) -> PollEventSet { let mut peb = PollEventBuilder::new(); for i in 0..16 { @@ -264,7 +290,7 @@ impl PollEventBuilder { } } -#[cfg(all(unix, feature = "sys-poll"))] +#[cfg(all(unix, feature = "sys-poll", not(feature="os")))] pub(crate) fn poll( selfs: &[&(dyn VirtualFile + Send + Sync + 'static)], events: &[PollEventSet], @@ -304,55 +330,73 @@ pub(crate) fn poll( Ok(result.try_into().unwrap()) } -#[cfg(any(not(unix), not(feature = "sys-poll")))] +#[cfg(any(not(unix), not(feature = "sys-poll"), feature="os"))] pub(crate) fn poll( - files: &[&(dyn VirtualFile + Send + Sync + 'static)], + files: &[super::InodeValFilePollGuard], events: &[PollEventSet], seen_events: &mut [PollEventSet], timeout: Duration, ) -> Result { + if !(files.len() == events.len() && events.len() == seen_events.len()) { tracing::debug!("the slice length of 'files', 'events' and 'seen_events' must be the same (files={}, events={}, seen_events={})", files.len(), events.len(), seen_events.len()); return Err(FsError::InvalidInput); } let mut ret = 0; - for n in 0..files.len() { + for (n, file) in files.iter().enumerate() { let mut builder = PollEventBuilder::new(); - let file = files[n]; - let can_read = file.bytes_available_read()?.map(|_| true).unwrap_or(false); - let can_write = file - .bytes_available_write()? - .map(|s| s > 0) - .unwrap_or(false); - let is_closed = file.is_open() == false; + let mut can_read = None; + let mut can_write = None; + let mut is_closed = None; + /* tracing::debug!( "poll_evt can_read={} can_write={} is_closed={}", can_read, can_write, is_closed ); + */ for event in iterate_poll_events(events[n]) { match event { - PollEvent::PollIn if can_read => { - builder = builder.add(PollEvent::PollIn); - } - PollEvent::PollOut if can_write => { - builder = builder.add(PollEvent::PollOut); - } - PollEvent::PollHangUp if is_closed => { - builder = builder.add(PollEvent::PollHangUp); + PollEvent::PollIn => { + if can_read.is_none() { + can_read = Some( + file.bytes_available_read()? + .map(|s| s > 0) + .unwrap_or(false)); + } + if can_read.unwrap_or_default() { + tracing::debug!("poll_evt can_read=true file={:?}", file); + builder = builder.add(PollEvent::PollIn); + } } - PollEvent::PollInvalid if is_closed => { - builder = builder.add(PollEvent::PollInvalid); + PollEvent::PollOut => { + if can_write.is_none() { + can_write = Some(file + .bytes_available_write()? + .map(|s| s > 0) + .unwrap_or(false)); + } + if can_write.unwrap_or_default() { + tracing::debug!("poll_evt can_write=true file={:?}", file); + builder = builder.add(PollEvent::PollOut); + } } - PollEvent::PollError if is_closed => { - builder = builder.add(PollEvent::PollError); + PollEvent::PollHangUp | + PollEvent::PollInvalid | + PollEvent::PollError => { + if is_closed.is_none() { + is_closed = Some(file.is_open() == false); + } + if is_closed.unwrap_or_default() { + tracing::debug!("poll_evt is_closed=true file={:?}", file); + builder = builder.add(event); + } } - _ => {} } } let revents = builder.build(); diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs index 176e94a5d6b..322e61897a5 100644 --- a/lib/wasi/src/syscalls/legacy/snapshot0.rs +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -1,6 +1,6 @@ use crate::syscalls; -use crate::syscalls::types; -use crate::{mem_error_to_wasi, Memory32, MemorySize, WasiEnv, WasiError, WasiThread}; +use crate::syscalls::types::{self, snapshot0}; +use crate::{mem_error_to_wasi, Memory32, MemorySize, WasiEnv, WasiError}; use wasmer::{AsStoreMut, FunctionEnvMut, WasmPtr}; use wasmer_wasi_types::wasi::{ Errno, Event, Fd, Filesize, Filestat, Filetype, Snapshot0Filestat, Snapshot0Subscription, @@ -32,7 +32,7 @@ pub fn fd_filestat_get( // Set up complete, make the call with the pointer that will write to the // struct and some unrelated memory after the struct. - let result = syscalls::fd_filestat_get::(ctx.as_mut(), fd, new_buf); + let result = syscalls::fd_filestat_get_internal::(&mut ctx, fd, new_buf); // reborrow memory let env = ctx.data(); @@ -136,23 +136,43 @@ pub fn poll_oneoff( // in this case the new type is smaller than the old type, so it all fits into memory, // we just need to readjust and copy it - // we start by adjusting `in_` into a format that the new code can understand - let env = ctx.data(); - let memory = env.memory_view(&ctx); let nsubscriptions_offset: u32 = nsubscriptions; - let in_origs = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions_offset)); - let in_origs = wasi_try_mem_ok!(in_origs.read_to_vec()); - - // get a pointer to the smaller new type - let in_new_type_ptr: WasmPtr = in_.cast(); - - for (in_sub_new, orig) in - wasi_try_mem_ok!(in_new_type_ptr.slice(&memory, nsubscriptions_offset)) - .iter() - .zip(in_origs.iter()) - { - wasi_try_mem_ok!(in_sub_new.write(Subscription::from(*orig))); - } + let in_new_type_ptr = { + // we start by adjusting `in_` into a format that the new code can understand + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let in_origs = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions_offset)); + let in_origs = wasi_try_mem_ok!(in_origs.read_to_vec()); + + // get a pointer to the smaller new type + let in_new_type_ptr: WasmPtr = in_.cast(); + + for (in_sub_new, orig) in + wasi_try_mem_ok!(in_new_type_ptr.slice(&memory, nsubscriptions_offset)) + .iter() + .zip(in_origs.iter()) + { + wasi_try_mem_ok!(in_sub_new.write(types::__wasi_subscription_t { + userdata: orig.userdata, + type_: orig.type_, + u: if orig.type_ == types::__WASI_EVENTTYPE_CLOCK { + types::__wasi_subscription_u { + clock: types::__wasi_subscription_clock_t { + clock_id: unsafe { orig.u.clock.clock_id }, + timeout: unsafe { orig.u.clock.timeout }, + precision: unsafe { orig.u.clock.precision }, + flags: unsafe { orig.u.clock.flags }, + }, + } + } else { + types::__wasi_subscription_u { + fd_readwrite: unsafe { orig.u.fd_readwrite }, + } + }, + })); + } + in_new_type_ptr + }; // make the call let result = syscalls::poll_oneoff::( @@ -161,11 +181,13 @@ pub fn poll_oneoff( out_, nsubscriptions, nevents, - ); + )?; // replace the old values of in, in case the calling code reuses the memory let env = ctx.data(); let memory = env.memory_view(&ctx); + let in_origs = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions_offset)); + let in_origs = wasi_try_mem_ok!(in_origs.read_to_vec()); for (in_sub, orig) in wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions_offset)) .iter() @@ -174,5 +196,5 @@ pub fn poll_oneoff( wasi_try_mem_ok!(in_sub.write(orig)); } - result + Ok(result) } diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index f92a1625e33..ee27e56f245 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -12,32 +12,32 @@ pub mod types { target_vendor = "apple" ))] pub mod unix; -#[cfg(any(target_arch = "wasm32"))] -pub mod wasm32; +#[cfg(any(target_family = "wasm"))] +pub mod wasm; #[cfg(any(target_os = "windows"))] pub mod windows; pub mod legacy; -//pub mod wasi; -#[cfg(feature = "wasix")] -pub mod wasix32; -#[cfg(feature = "wasix")] -pub mod wasix64; - -use self::types::{ - wasi::{ - Addressfamily, Advice, Bid, BusDataFormat, BusErrno, BusHandles, Cid, Clockid, Dircookie, - Dirent, Errno, Event, EventEnum, EventFdReadwrite, Eventrwflags, Eventtype, Fd as WasiFd, - Fdflags, Fdstat, Filesize, Filestat, Filetype, Fstflags, Linkcount, OptionFd, Pid, Prestat, - Rights, Snapshot0Clockid, Sockoption, Sockstatus, Socktype, StdioMode as WasiStdioMode, - Streamsecurity, Subscription, SubscriptionEnum, SubscriptionFsReadwrite, Tid, Timestamp, - Tty, Whence, - }, - *, + +use self::types::*; +#[cfg(feature = "os")] +use crate::bin_factory::spawn_exec_module; +use crate::runtime::SpawnType; +use crate::state::{WasiProcessWait, read_ip_port, write_ip_port}; +use crate::state::{ + bus_error_into_wasi_err, + wasi_error_into_bus_err, + InodeHttpSocketType, + WasiThreadContext, + WasiThreadId, + WasiProcessId, + WasiFutex, + WasiBusCall, + WasiParkingLot, + WasiDummyWaker }; -use crate::state::{bus_error_into_wasi_err, wasi_error_into_bus_err, InodeHttpSocketType}; use crate::utils::map_io_err; -use crate::WasiBusProcessId; +use crate::{WasiEnvInner, import_object_for_all_wasi_versions, WasiFunctionEnv, current_caller_id, DEFAULT_STACK_SIZE, WasiVFork, WasiRuntimeImplementation, VirtualTaskManager, WasiThread}; use crate::{ mem_error_to_wasi, state::{ @@ -45,27 +45,40 @@ use crate::{ virtual_file_type_to_wasi_file_type, Inode, InodeSocket, InodeSocketKind, InodeVal, Kind, PollEvent, PollEventBuilder, WasiBidirectionalPipePair, WasiState, MAX_SYMLINKS, }, - Fd, WasiEnv, WasiError, WasiThread, WasiThreadId, + WasiEnv, WasiError, }; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; +use cooked_waker::IntoWaker; +use sha2::Sha256; +use wasmer::vm::VMMemory; use std::borrow::{Borrow, Cow}; +use std::cell::RefCell; +use std::collections::{HashSet, HashMap}; +use std::collections::hash_map::Entry; use std::convert::{Infallible, TryInto}; use std::io::{self, Read, Seek, Write}; use std::mem::transmute; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::num::NonZeroU64; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::AtomicU64; +use std::path::Path; +use std::pin::Pin; +use std::sync::atomic::{AtomicU64, AtomicU32, AtomicBool}; use std::sync::{atomic::Ordering, Mutex}; -use std::sync::{mpsc, Arc}; +use std::sync::{mpsc, Arc, Condvar}; +use std::task::{Poll, Context}; +use std::thread::LocalKey; use std::time::Duration; use tracing::{debug, error, trace, warn}; use wasmer::{ - AsStoreMut, Extern, FunctionEnv, FunctionEnvMut, Instance, Memory, Memory32, Memory64, - MemorySize, MemoryView, Module, RuntimeError, Value, WasmPtr, WasmSlice, + AsStoreMut, FunctionEnvMut, Memory, Memory32, Memory64, MemorySize, RuntimeError, Value, + WasmPtr, WasmSlice, FunctionEnv, Instance, Module, Extern, MemoryView, TypedFunction, Store, Pages, Global, AsStoreRef, + MemoryAccessError, OnCalledAction, MemoryError, Function, StoreSnapshot }; -use wasmer_vbus::{FileDescriptor, StdioMode}; -use wasmer_vfs::{FsError, VirtualFile}; +use wasmer_vbus::{FileDescriptor, StdioMode, BusDataFormat, BusInvocationEvent, BusSpawnedProcess, VirtualBusError, SignalHandlerAbi, SpawnOptionsConfig}; +use wasmer_vfs::{FsError, VirtualFile, FileSystem}; use wasmer_vnet::{SocketHttpRequest, StreamSecurity}; +use wasmer_types::LinearMemory; #[cfg(any( target_os = "freebsd", @@ -78,8 +91,8 @@ pub use unix::*; #[cfg(any(target_os = "windows"))] pub use windows::*; -#[cfg(any(target_arch = "wasm32"))] -pub use wasm32::*; +#[cfg(any(target_family = "wasm"))] +pub use wasm::*; fn to_offset(offset: usize) -> Result { let ret: M::Offset = offset.try_into().map_err(|_| Errno::Inval)?; @@ -137,7 +150,7 @@ pub(crate) fn read_bytes( let to_read = from_offset::(iov_inner.buf_len)?; raw_bytes.resize(to_read, 0); let has_read = reader.read(&mut raw_bytes).map_err(map_io_err)?; - + let buf = WasmPtr::::new(iov_inner.buf) .slice(memory, iov_inner.buf_len) .map_err(mem_error_to_wasi)?; @@ -150,14 +163,36 @@ pub(crate) fn read_bytes( Ok(bytes_read) } -fn __sock_actor( +/// checks that `rights_check_set` is a subset of `rights_set` +fn has_rights(rights_set: __wasi_rights_t, rights_check_set: __wasi_rights_t) -> bool { + rights_set | rights_check_set == rights_set +} + +/// Writes data to the stderr +pub fn stderr_write( + ctx: &FunctionEnvMut<'_, WasiEnv>, + buf: &[u8] +) -> Result<(), __wasi_errno_t> { + let env = ctx.data(); + let (memory, state, inodes) = env.get_memory_and_wasi_state_and_inodes_mut(ctx, 0); + + let mut stderr = inodes + .stderr_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err)?; + + stderr.write_all(buf).map_err(map_io_err) +} + +fn __sock_actor( ctx: &FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, rights: Rights, actor: F, ) -> Result where - F: FnOnce(&crate::state::InodeSocket) -> Result, + T: 'static, + F: FnOnce(crate::state::InodeSocket) -> Fut + 'static, + Fut: std::future::Future> { let env = ctx.data(); let (_, state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); @@ -171,10 +206,19 @@ where let inode_idx = fd_entry.inode; let inode = &inodes.arena[inode_idx]; + let tasks = env.tasks.clone(); let mut guard = inode.read(); - let deref = guard.deref(); - match deref { - Kind::Socket { socket } => actor(socket)?, + match guard.deref() { + Kind::Socket { socket } => { + // Clone the socket and release the lock + let socket = socket.clone(); + drop(guard); + + // Block on the work and process process + __asyncify(tasks, &env.thread, None, async move { + actor(socket).await + })? + }, _ => { return Err(Errno::Notsock); } @@ -184,72 +228,167 @@ where Ok(ret) } -fn __sock_actor_mut( +fn __asyncify( + tasks: Arc, + thread: &WasiThread, + timeout: Option, + work: Fut, +) -> Result +where + T: 'static, + Fut: std::future::Future> + 'static +{ + let mut signaler = thread.signals.1.subscribe(); + + // Create the timeout + let timeout = { + let tasks_inner= tasks.clone(); + async move { + if let Some(timeout) = timeout { + tasks_inner.sleep_now(current_caller_id(), timeout.as_millis()).await + } else { + InfiniteSleep::default().await + } + } + }; + + // Block on the work and process process + let tasks_inner= tasks.clone(); + let (tx_ret, mut rx_ret) = tokio::sync::mpsc::unbounded_channel(); + tasks.block_on( + Box::pin(async move { + tokio::select! { + // The main work we are doing + ret = work => { + let _ = tx_ret.send(ret); + }, + // If a signaller is triggered then we interrupt the main process + _ = signaler.recv() => { + let _ = tx_ret.send(Err(__WASI_EINTR)); + }, + // Optional timeout + _ = timeout => { + let _ = tx_ret.send(Err(__WASI_ETIMEDOUT)); + }, + // Periodically wake every 10 milliseconds for synchronously IO + // (but only if someone is currently registered for it) + _ = async move { + loop { + tasks_inner.wait_for_root_waker().await; + tasks_inner.wake_root_wakers(); + } + } => { } + } + + }) + ); + rx_ret + .try_recv() + .unwrap_or(Err(__WASI_EINTR)) +} + +#[derive(Default)] +struct InfiniteSleep { } +impl std::future::Future for InfiniteSleep { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Poll::Pending + } +} + +fn __sock_actor_mut( ctx: &FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, rights: Rights, actor: F, ) -> Result where - F: FnOnce(&mut crate::state::InodeSocket) -> Result, + T: 'static, + F: FnOnce(crate::state::InodeSocket) -> Fut + 'static, + Fut: std::future::Future> { let env = ctx.data(); let (_, state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = state.fs.get_fd(sock)?; - let ret = { - if !rights.is_empty() && !fd_entry.rights.contains(rights) { - return Err(Errno::Access); - } + if !rights.is_empty() && !fd_entry.rights.contains(rights) { + return Err(Errno::Access); + } - let inode_idx = fd_entry.inode; - let inode = &inodes.arena[inode_idx]; + let inode_idx = fd_entry.inode; + let inode = &inodes.arena[inode_idx]; - let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { - Kind::Socket { socket } => actor(socket)?, - _ => { - return Err(Errno::Notsock); - } - } - }; + let tasks = env.tasks.clone(); + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::Socket { socket } => { + // Clone the socket and release the lock + let socket = socket.clone(); + drop(guard); - Ok(ret) + __asyncify(tasks, &env.thread, None, async move { + actor(socket).await + }) + }, + _ => { + return Err(__WASI_ENOTSOCK); + } + } } -fn __sock_upgrade( +fn __sock_upgrade( ctx: &FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - rights: Rights, + sock: __wasi_fd_t, + rights: __wasi_rights_t, actor: F, -) -> Result<(), Errno> +) -> Result<(), __wasi_errno_t> where - F: FnOnce(&mut crate::state::InodeSocket) -> Result, Errno>, + F: FnOnce(crate::state::InodeSocket) -> Fut + 'static, + Fut: std::future::Future, __wasi_errno_t>> + { let env = ctx.data(); let (_, state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = state.fs.get_fd(sock)?; - if !rights.is_empty() && !fd_entry.rights.contains(rights) { - return Err(Errno::Access); + if rights != 0 && !has_rights(fd_entry.rights, rights) { + tracing::warn!("wasi[{}:{}]::sock_upgrade(fd={}, rights={}) - failed - no access rights to upgrade", ctx.data().pid(), ctx.data().tid(), sock, rights); + return Err(__WASI_EACCES); } let inode_idx = fd_entry.inode; let inode = &inodes.arena[inode_idx]; + let tasks = env.tasks.clone(); let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Socket { socket } => { - let new_socket = actor(socket)?; + let socket = socket.clone(); + drop(guard); + + let new_socket = { + // Block on the work and process process + __asyncify(tasks, &env.thread, None, async move { + actor(socket).await + })? + }; if let Some(mut new_socket) = new_socket { - std::mem::swap(socket, &mut new_socket); + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::Socket { socket } => { + std::mem::swap(socket, &mut new_socket); + }, + _ => { + tracing::warn!("wasi[{}:{}]::sock_upgrade(fd={}, rights={}) - failed - not a socket", ctx.data().pid(), ctx.data().tid(), sock, rights); + return Err(__WASI_ENOTSOCK); + } + } } } _ => { - return Err(Errno::Notsock); + tracing::warn!("wasi[{}:{}]::sock_upgrade(fd={}, rights={}) - failed - not a socket", ctx.data().pid(), ctx.data().tid(), sock, rights); + return Err(__WASI_ENOTSOCK); } } @@ -287,12 +426,9 @@ fn write_buffer_array( Errno::Success } -fn get_current_time_in_nanos() -> Result { - let now = std::time::SystemTime::now(); - let duration = now - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .map_err(|_| Errno::Io)?; - Ok(duration.as_nanos() as Timestamp) +fn get_current_time_in_nanos() -> Result<__wasi_timestamp_t, __wasi_errno_t> { + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + Ok(now as __wasi_timestamp_t) } /// ### `args_get()` @@ -308,12 +444,13 @@ pub fn args_get( mut ctx: FunctionEnvMut<'_, WasiEnv>, argv: WasmPtr, M>, argv_buf: WasmPtr, -) -> Errno { - debug!("wasi::args_get"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::args_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state) = env.get_memory_and_wasi_state(&ctx, 0); - let result = write_buffer_array(&memory, &state.args, argv, argv_buf); + let state_args: Vec> = state.args.iter().map(|a| a.as_bytes().to_vec()).collect(); + let result = write_buffer_array(&memory, &*state_args, argv, argv_buf); debug!( "=> args:\n{}", @@ -321,7 +458,7 @@ pub fn args_get( .args .iter() .enumerate() - .map(|(i, v)| format!("{:>20}: {}", i, ::std::str::from_utf8(v).unwrap())) + .map(|(i, v)| format!("{:>20}: {}", i, v)) .collect::>() .join("\n") ); @@ -340,8 +477,8 @@ pub fn args_sizes_get( mut ctx: FunctionEnvMut<'_, WasiEnv>, argc: WasmPtr, argv_buf_size: WasmPtr, -) -> Errno { - debug!("wasi::args_sizes_get"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::args_sizes_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state) = env.get_memory_and_wasi_state(&ctx, 0); @@ -370,10 +507,10 @@ pub fn args_sizes_get( /// The resolution of the clock in nanoseconds pub fn clock_res_get( mut ctx: FunctionEnvMut<'_, WasiEnv>, - clock_id: Clockid, - resolution: WasmPtr, -) -> Errno { - trace!("wasi::clock_res_get"); + clock_id: __wasi_clockid_t, + resolution: WasmPtr<__wasi_timestamp_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::clock_res_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); @@ -398,32 +535,73 @@ pub fn clock_res_get( /// The value of the clock in nanoseconds pub fn clock_time_get( ctx: FunctionEnvMut<'_, WasiEnv>, - clock_id: Clockid, - precision: Timestamp, - time: WasmPtr, -) -> Errno { - debug!( - "wasi::clock_time_get clock_id: {}, precision: {}", - clock_id as u8, precision + clock_id: __wasi_clockid_t, + precision: __wasi_timestamp_t, + time: WasmPtr<__wasi_timestamp_t, M>, +) -> __wasi_errno_t { + /* + trace!( + "wasi[{}:{}]::clock_time_get clock_id: {}, precision: {}", + ctx.data().pid(), clock_id, precision ); + */ let env = ctx.data(); let memory = env.memory_view(&ctx); - let t_out = wasi_try!(platform_clock_time_get( - Snapshot0Clockid::from(clock_id), - precision - )); - wasi_try_mem!(time.write(&memory, t_out as Timestamp)); + let mut t_out = wasi_try!(platform_clock_time_get(clock_id, precision)); + + { + let guard = env.state.clock_offset.lock().unwrap(); + if let Some(offset) = guard.get(&clock_id) { + t_out += *offset; + } + }; + + wasi_try_mem!(time.write(&memory, t_out as __wasi_timestamp_t)); - let result = Errno::Success; + let result = __WASI_ESUCCESS; + /* trace!( "time: {} => {}", - wasi_try_mem!(time.deref(&memory).read()), + t_out as __wasi_timestamp_t, result ); + */ result } +/// ### `clock_time_set()` +/// Set the time of the specified clock +/// Inputs: +/// - `__wasi_clockid_t clock_id` +/// The ID of the clock to query +/// - `__wasi_timestamp_t *time` +/// The value of the clock in nanoseconds +pub fn clock_time_set( + ctx: FunctionEnvMut<'_, WasiEnv>, + clock_id: __wasi_clockid_t, + time: __wasi_timestamp_t, +) -> __wasi_errno_t { + trace!( + "wasi::clock_time_set clock_id: {}, time: {}", + clock_id, time + ); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + let precision = 1 as __wasi_timestamp_t; + let t_now = wasi_try!(platform_clock_time_get(clock_id, precision)); + let t_now = t_now as i64; + + let t_target = time as i64; + let t_offset = t_target - t_now; + + let mut guard = env.state.clock_offset.lock().unwrap(); + guard.insert(clock_id, t_offset); + + __WASI_ESUCCESS +} + /// ### `environ_get()` /// Read environment variable data. /// The sizes of the buffers should match that returned by [`environ_sizes_get()`](#environ_sizes_get). @@ -436,8 +614,8 @@ pub fn environ_get( ctx: FunctionEnvMut<'_, WasiEnv>, environ: WasmPtr, M>, environ_buf: WasmPtr, -) -> Errno { - debug!( +) -> __wasi_errno_t { + trace!( "wasi::environ_get. Environ: {:?}, environ_buf: {:?}", environ, environ_buf ); @@ -459,8 +637,8 @@ pub fn environ_sizes_get( ctx: FunctionEnvMut<'_, WasiEnv>, environ_count: WasmPtr, environ_buf_size: WasmPtr, -) -> Errno { - trace!("wasi::environ_sizes_get"); +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::environ_sizes_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state) = env.get_memory_and_wasi_state(&ctx, 0); @@ -496,12 +674,12 @@ pub fn environ_sizes_get( /// The advice to give pub fn fd_advise( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - offset: Filesize, - len: Filesize, - advice: Advice, -) -> Errno { - debug!("wasi::fd_advise: fd={}", fd); + fd: __wasi_fd_t, + offset: __wasi_filesize_t, + len: __wasi_filesize_t, + advice: __wasi_advice_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_advise: fd={}", ctx.data().pid(), ctx.data().tid(), fd); // this is used for our own benefit, so just returning success is a valid // implementation for now @@ -519,11 +697,11 @@ pub fn fd_advise( /// The length from the offset marking the end of the allocation pub fn fd_allocate( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - offset: Filesize, - len: Filesize, -) -> Errno { - debug!("wasi::fd_allocate"); + fd: __wasi_fd_t, + offset: __wasi_filesize_t, + len: __wasi_filesize_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_allocate", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -535,10 +713,10 @@ pub fn fd_allocate( let new_size = wasi_try!(offset.checked_add(len).ok_or(Errno::Inval)); { let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); wasi_try!(handle.set_len(new_size).map_err(fs_error_into_wasi_err)); } else { return Errno::Badf; @@ -570,13 +748,12 @@ pub fn fd_allocate( /// If `fd` is a directory /// - `Errno::Badf` /// If `fd` is invalid or not open -pub fn fd_close(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { - debug!("wasi::fd_close: fd={}", fd); +pub fn fd_close(ctx: FunctionEnvMut<'_, WasiEnv>, fd: __wasi_fd_t) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_close: fd={}", ctx.data().pid(), ctx.data().tid(), fd); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); - let fd_entry = wasi_try!(state.fs.get_fd(fd)); - + let fd_entry = wasi_try!(state.fs.get_fd(fd)); wasi_try!(state.fs.close_fd(inodes.deref(), fd)); Errno::Success @@ -587,8 +764,8 @@ pub fn fd_close(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { /// Inputs: /// - `Fd fd` /// The file descriptor to sync -pub fn fd_datasync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { - debug!("wasi::fd_datasync"); +pub fn fd_datasync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: __wasi_fd_t) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_datasync", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -617,7 +794,7 @@ pub fn fd_fdstat_get( buf_ptr: WasmPtr, ) -> Errno { debug!( - "wasi::fd_fdstat_get: fd={}, buf_ptr={}", + "wasi[{}:{}]::fd_fdstat_get: fd={}, buf_ptr={}", ctx.data().pid(), ctx.data().tid(), fd, buf_ptr.offset() ); @@ -639,15 +816,33 @@ pub fn fd_fdstat_get( /// The file descriptor to apply the new flags to /// - `Fdflags flags` /// The flags to apply to `fd` -pub fn fd_fdstat_set_flags(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, flags: Fdflags) -> Errno { - debug!("wasi::fd_fdstat_set_flags"); +pub fn fd_fdstat_set_flags( + ctx: FunctionEnvMut<'_, WasiEnv>, + fd: __wasi_fd_t, + flags: __wasi_fdflags_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_fdstat_set_flags (fd={}, flags={})", ctx.data().pid(), ctx.data().tid(), fd, flags); let env = ctx.data(); - let (_, mut state) = env.get_memory_and_wasi_state(&ctx, 0); + let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); + let fd_entry = wasi_try!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let inode = fd_entry.inode; - if !fd_entry.rights.contains(Rights::FD_FDSTAT_SET_FLAGS) { - return Errno::Access; + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FDSTAT_SET_FLAGS) { + debug!("wasi[{}:{}]::fd_fdstat_set_flags (fd={}, flags={}) - access denied", ctx.data().pid(), ctx.data().tid(), fd, flags); + return __WASI_EACCES; + } + + { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { + Kind::Socket { socket } => { + let nonblocking = (flags & __WASI_FDFLAG_NONBLOCK) != 0; + debug!("wasi[{}:{}]::socket(fd={}) nonblocking={}", ctx.data().pid(), ctx.data().tid(), fd, nonblocking); + socket.set_nonblocking(nonblocking); + }, + _ => { } + } } fd_entry.flags = flags; @@ -665,11 +860,11 @@ pub fn fd_fdstat_set_flags(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, flags: /// The inheriting rights to apply to `fd` pub fn fd_fdstat_set_rights( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, -) -> Errno { - debug!("wasi::fd_fdstat_set_rights"); + fd: __wasi_fd_t, + fs_rights_base: __wasi_rights_t, + fs_rights_inheriting: __wasi_rights_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_fdstat_set_rights", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (_, mut state) = env.get_memory_and_wasi_state(&ctx, 0); let mut fd_map = state.fs.fd_map.write().unwrap(); @@ -697,11 +892,27 @@ pub fn fd_fdstat_set_rights( /// - `Filestat *buf` /// Where the metadata from `fd` will be written pub fn fd_filestat_get( - ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - buf: WasmPtr, -) -> Errno { - debug!("wasi::fd_filestat_get"); + mut ctx: FunctionEnvMut<'_, WasiEnv>, + fd: __wasi_fd_t, + buf: WasmPtr<__wasi_filestat_t, M>, +) -> __wasi_errno_t { + fd_filestat_get_internal(&mut ctx, fd, buf) +} + +/// ### `fd_filestat_get()` +/// Get the metadata of an open file +/// Input: +/// - `__wasi_fd_t fd` +/// The open file descriptor whose metadata will be read +/// Output: +/// - `__wasi_filestat_t *buf` +/// Where the metadata from `fd` will be written +pub(crate) fn fd_filestat_get_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: __wasi_fd_t, + buf: WasmPtr<__wasi_filestat_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_filestat_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -726,10 +937,10 @@ pub fn fd_filestat_get( /// New size that `fd` will be set to pub fn fd_filestat_set_size( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - st_size: Filesize, -) -> Errno { - debug!("wasi::fd_filestat_set_size"); + fd: __wasi_fd_t, + st_size: __wasi_filesize_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_filestat_set_size", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -741,10 +952,10 @@ pub fn fd_filestat_set_size( { let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); wasi_try!(handle.set_len(st_size).map_err(fs_error_into_wasi_err)); } else { return Errno::Badf; @@ -776,12 +987,12 @@ pub fn fd_filestat_set_size( /// Bit-vector for controlling which times get set pub fn fd_filestat_set_times( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - debug!("wasi::fd_filestat_set_times"); + fd: __wasi_fd_t, + st_atim: __wasi_timestamp_t, + st_mtim: __wasi_timestamp_t, + fst_flags: __wasi_fstflags_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_filestat_set_times", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -842,28 +1053,24 @@ pub fn fd_pread( iovs_len: M::Offset, offset: Filesize, nread: WasmPtr, -) -> Result { - trace!("wasi::fd_pread: fd={}, offset={}", fd, offset); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::fd_pread: fd={}, offset={}", ctx.data().pid(), ctx.data().tid(), fd, offset); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); - let iovs = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); let nread_ref = nread.deref(&memory); let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); let bytes_read = match fd { __WASI_STDIN_FILENO => { - let mut guard = wasi_try_ok!( + let mut stdin = wasi_try_ok!( inodes .stdin_mut(&state.fs.fd_map) .map_err(fs_error_into_wasi_err), env ); - if let Some(ref mut stdin) = guard.deref_mut() { - wasi_try_ok!(read_bytes(stdin, &memory, iovs), env) - } else { - return Ok(Errno::Badf); - } + wasi_try_ok!(read_bytes(stdin.deref_mut(), &memory, iovs_arr), env) } __WASI_STDOUT_FILENO => return Ok(Errno::Inval), __WASI_STDERR_FILENO => return Ok(Errno::Inval), @@ -878,31 +1085,71 @@ pub fn fd_pread( return Ok(Errno::Access); } let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(h) = handle { + let mut h = h.write().unwrap(); wasi_try_ok!( h.seek(std::io::SeekFrom::Start(offset as u64)) .map_err(map_io_err), env ); - wasi_try_ok!(read_bytes(h, &memory, iovs), env) + wasi_try_ok!(read_bytes(h.deref_mut(), &memory, iovs_arr), env) } else { return Ok(Errno::Inval); } } Kind::Socket { socket } => { - wasi_try_ok!(socket.recv(&memory, iovs), env) + let mut memory = env.memory_view(&ctx); + + let mut max_size = 0usize; + for iovs in iovs_arr.iter() { + let iovs = wasi_try_mem_ok!(iovs.read()); + let buf_len: usize = wasi_try_ok!(iovs.buf_len.try_into().map_err(|_| __WASI_EOVERFLOW)); + max_size += buf_len; + } + + let socket = socket.clone(); + let data = wasi_try_ok!( + __asyncify( + env.tasks.clone(), + &env.thread, + None, + async move { + socket.recv(max_size).await + } + ) + ); + + let data_len = data.len(); + let mut reader = &data[..]; + let bytes_read = wasi_try_ok!( + read_bytes(reader, &memory, iovs_arr).map(|_| data_len) + ); + bytes_read } Kind::Pipe { pipe } => { - wasi_try_ok!(pipe.recv(&memory, iovs), env) + let mut a; + loop { + a = wasi_try_ok!(match pipe.recv(&memory, iovs_arr, Duration::from_millis(5)) { + Err(err) if err == __WASI_ETIMEDOUT => { + env.yield_now()?; + continue; + }, + a => a + }, env); + break; + } + a } Kind::EventNotifications { .. } => return Ok(Errno::Inval), Kind::Dir { .. } | Kind::Root { .. } => return Ok(Errno::Isdir), Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_pread"), Kind::Buffer { buffer } => { - wasi_try_ok!(read_bytes(&buffer[(offset as usize)..], &memory, iovs), env) + wasi_try_ok!( + read_bytes(&buffer[(offset as usize)..], &memory, iovs_arr), + env + ) } } } @@ -910,8 +1157,7 @@ pub fn fd_pread( let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| Errno::Overflow)); wasi_try_mem_ok!(nread_ref.write(bytes_read)); - debug!("Success: {} bytes read", bytes_read); - Ok(Errno::Success) + Ok(__WASI_ESUCCESS) } /// ### `fd_prestat_get()` @@ -924,22 +1170,21 @@ pub fn fd_pread( /// Where the metadata will be written pub fn fd_prestat_get( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - buf: WasmPtr, -) -> Errno { - trace!("wasi::fd_prestat_get: fd={}", fd); + fd: __wasi_fd_t, + buf: WasmPtr<__wasi_prestat_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::fd_prestat_get: fd={}", ctx.data().pid(), ctx.data().tid(), fd); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let prestat_ptr = buf.deref(&memory); - wasi_try_mem!( - prestat_ptr.write(wasi_try!(state.fs.prestat_fd(inodes.deref(), fd).map_err( - |code| { + wasi_try_mem!(prestat_ptr.write(wasi_try!( + state.fs.prestat_fd(inodes.deref(), fd) + .map_err(|code| { debug!("fd_prestat_get failed (fd={}) - errno={}", fd, code); code - } - ))) - ); + }) + ))); Errno::Success } @@ -951,7 +1196,8 @@ pub fn fd_prestat_dir_name( path_len: M::Offset, ) -> Errno { trace!( - "wasi::fd_prestat_dir_name: fd={}, path_len={}", + "wasi[{}:{}]::fd_prestat_dir_name: fd={}, path_len={}", + ctx.data().pid(), ctx.data().tid(), fd, path_len ); @@ -964,10 +1210,9 @@ pub fn fd_prestat_dir_name( // check inode-val.is_preopened? - trace!("=> inode: {:?}", inode_val); + //trace!("=> inode: {:?}", inode_val); let guard = inode_val.read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { .. } | Kind::Root { .. } => { let path_len: u64 = path_len.into(); if (inode_val.name.len() as u64) <= path_len { @@ -975,7 +1220,7 @@ pub fn fd_prestat_dir_name( .subslice(0..inode_val.name.len() as u64) .write_slice(inode_val.name.as_bytes())); - trace!("=> result: \"{}\"", inode_val.name); + //trace!("=> result: \"{}\"", inode_val.name); Errno::Success } else { @@ -1012,8 +1257,8 @@ pub fn fd_pwrite( iovs_len: M::Offset, offset: Filesize, nwritten: WasmPtr, -) -> Result { - trace!("wasi::fd_pwrite"); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::fd_pwrite", ctx.data().pid(), ctx.data().tid()); // TODO: refactor, this is just copied from `fd_write`... let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); @@ -1024,30 +1269,22 @@ pub fn fd_pwrite( let bytes_written = match fd { __WASI_STDIN_FILENO => return Ok(Errno::Inval), __WASI_STDOUT_FILENO => { - let mut guard = wasi_try_ok!( + let mut stdout = wasi_try_ok!( inodes .stdout_mut(&state.fs.fd_map) .map_err(fs_error_into_wasi_err), env ); - if let Some(ref mut stdout) = guard.deref_mut() { - wasi_try_ok!(write_bytes(stdout, &memory, iovs_arr), env) - } else { - return Ok(Errno::Badf); - } + wasi_try_ok!(write_bytes(stdout.deref_mut(), &memory, iovs_arr), env) } __WASI_STDERR_FILENO => { - let mut guard = wasi_try_ok!( + let mut stderr = wasi_try_ok!( inodes .stderr_mut(&state.fs.fd_map) .map_err(fs_error_into_wasi_err), env ); - if let Some(ref mut stderr) = guard.deref_mut() { - wasi_try_ok!(write_bytes(stderr, &memory, iovs_arr), env) - } else { - return Ok(Errno::Badf); - } + wasi_try_ok!(write_bytes(stderr.deref_mut(), &memory, iovs_arr), env) } _ => { if !fd_entry.rights.contains(Rights::FD_WRITE | Rights::FD_SEEK) { @@ -1058,23 +1295,42 @@ pub fn fd_pwrite( let inode = &inodes.arena[inode_idx]; let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); wasi_try_ok!( handle .seek(std::io::SeekFrom::Start(offset as u64)) .map_err(map_io_err), env ); - wasi_try_ok!(write_bytes(handle, &memory, iovs_arr), env) + wasi_try_ok!(write_bytes(handle.deref_mut(), &memory, iovs_arr), env) } else { return Ok(Errno::Inval); } } Kind::Socket { socket } => { - wasi_try_ok!(socket.send(&memory, iovs_arr), env) + let buf_len: M::Offset = iovs_arr + .iter() + .filter_map(|a| a.read().ok()) + .map(|a| a.buf_len) + .sum(); + let buf_len: usize = wasi_try_ok!(buf_len.try_into().map_err(|_| __WASI_EINVAL)); + let mut buf = Vec::with_capacity(buf_len); + wasi_try_ok!(write_bytes(&mut buf, &memory, iovs_arr)); + + let socket = socket.clone(); + wasi_try_ok!( + __asyncify( + env.tasks.clone(), + &env.thread, + None, + async move { + socket.send(buf).await + } + ) + ) } Kind::Pipe { pipe } => { wasi_try_ok!(pipe.send(&memory, iovs_arr), env) @@ -1116,144 +1372,208 @@ pub fn fd_pwrite( /// Number of bytes read /// pub fn fd_read( - ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, + mut ctx: FunctionEnvMut<'_, WasiEnv>, + fd: __wasi_fd_t, iovs: WasmPtr<__wasi_iovec_t, M>, iovs_len: M::Offset, nread: WasmPtr, -) -> Result { - trace!("wasi::fd_read: fd={}", fd); - let env = ctx.data(); - let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::fd_read: fd={}", ctx.data().pid(), ctx.data().tid(), fd); + + ctx.data().clone().process_signals(&mut ctx)?; + + let mut env = ctx.data(); + let state = env.state.clone(); + let inodes = state.inodes.clone(); //let iovs_len = if iovs_len > M::Offset::from(1u32) { M::Offset::from(1u32) } else { iovs_len }; - let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); - let nread_ref = nread.deref(&memory); + let is_stdio = match fd { + __WASI_STDIN_FILENO => true, + __WASI_STDOUT_FILENO => return Ok(__WASI_EINVAL), + __WASI_STDERR_FILENO => return Ok(__WASI_EINVAL), + _ => false, + }; + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); - let bytes_read = match fd { - __WASI_STDIN_FILENO => { - let mut guard = wasi_try_ok!( - inodes - .stdin_mut(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ); - if let Some(ref mut stdin) = guard.deref_mut() { - wasi_try_ok!(read_bytes(stdin, &memory, iovs_arr), env) - } else { - return Ok(Errno::Badf); - } - } - __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return Ok(Errno::Inval), - _ => { - if !fd_entry.rights.contains(Rights::FD_READ) { + let bytes_read = { + let inodes = inodes.read().unwrap(); + if is_stdio == false { + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { // TODO: figure out the error to return when lacking rights return Ok(Errno::Access); } + } - let is_non_blocking = fd_entry.flags.contains(Fdflags::NONBLOCK); - let offset = fd_entry.offset as usize; - let inode_idx = fd_entry.inode; - let inode = &inodes.arena[inode_idx]; + let is_non_blocking = fd_entry.flags & __WASI_FDFLAG_NONBLOCK != 0; + let offset = fd_entry.offset.load(Ordering::Acquire) as usize; + let inode_idx = fd_entry.inode; + let inode = &inodes.arena[inode_idx]; - let bytes_read = { - let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { - Kind::File { handle, .. } => { - if let Some(handle) = handle { + let bytes_read = { + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); + if is_stdio == false { wasi_try_ok!( handle .seek(std::io::SeekFrom::Start(offset as u64)) .map_err(map_io_err), env ); - wasi_try_ok!(read_bytes(handle, &memory, iovs_arr), env) - } else { - return Ok(Errno::Inval); } + let mut memory = env.memory_view(&ctx); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + + // Wait for bytes to arrive - then read them + while handle.bytes_available_read().unwrap_or(None).unwrap_or(1) <= 0 { + env.clone().sleep(&mut ctx, Duration::from_millis(5))?; + env = ctx.data(); + } + + let memory = env.memory_view(&ctx); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + wasi_try_ok!(read_bytes(handle.deref_mut(), &memory, iovs_arr), env) + } else { + return Ok(__WASI_EINVAL); } - Kind::Socket { socket } => { - wasi_try_ok!(socket.recv(&memory, iovs_arr), env) - } - Kind::Pipe { pipe } => { - wasi_try_ok!(pipe.recv(&memory, iovs_arr), env) + } + Kind::Socket { socket } => { + let mut memory = env.memory_view(&ctx); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + + let mut max_size = 0usize; + for iovs in iovs_arr.iter() { + let iovs = wasi_try_mem_ok!(iovs.read()); + let buf_len: usize = wasi_try_ok!(iovs.buf_len.try_into().map_err(|_| __WASI_EOVERFLOW)); + max_size += buf_len; } - Kind::Dir { .. } | Kind::Root { .. } => { - // TODO: verify - return Ok(Errno::Isdir); + + let socket = socket.clone(); + let data = wasi_try_ok!( + __asyncify( + env.tasks.clone(), + &env.thread, + None, + async move { + socket.recv(max_size).await + } + ) + ); + + let data_len = data.len(); + let mut reader = &data[..]; + let bytes_read = wasi_try_ok!( + read_bytes(reader, &memory, iovs_arr + ).map(|_| data_len)); + bytes_read + } + Kind::Pipe { pipe } => { + let mut a; + loop { + let mut memory = env.memory_view(&ctx); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + a = wasi_try_ok!(match pipe.recv(&memory, iovs_arr, Duration::from_millis(50)) { + Err(err) if err == __WASI_ETIMEDOUT => { + env.clone().process_signals(&mut ctx)?; + env = ctx.data(); + continue; + }, + a => a + }, env); + break; } - Kind::EventNotifications { - counter, - is_semaphore, - wakers, - } => { - let counter = Arc::clone(counter); - let is_semaphore: bool = *is_semaphore; - let wakers = Arc::clone(wakers); - drop(guard); - drop(inodes); + a + } + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: verify + return Ok(__WASI_EISDIR); + } + Kind::EventNotifications { + counter, + is_semaphore, + wakers, + .. + } => { + let counter = Arc::clone(counter); + let is_semaphore: bool = *is_semaphore; + let wakers = Arc::clone(wakers); + drop(guard); + drop(inodes); - let (tx, rx) = mpsc::channel(); - { - let mut guard = wakers.lock().unwrap(); - guard.push_front(tx); - } + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + { + let mut guard = wakers.lock().unwrap(); + guard.push_front(tx); + } - let ret; - loop { - let val = counter.load(Ordering::Acquire); - if val > 0 { - let new_val = if is_semaphore { val - 1 } else { 0 }; - if counter - .compare_exchange( - val, - new_val, - Ordering::AcqRel, - Ordering::Acquire, - ) - .is_ok() - { - let reader = val.to_ne_bytes(); - ret = wasi_try_ok!( - read_bytes(&reader[..], &memory, iovs_arr), - env - ); - break; - } else { - continue; - } + let ret; + loop { + let val = counter.load(Ordering::Acquire); + if val > 0 { + let new_val = if is_semaphore { val - 1 } else { 0 }; + if counter + .compare_exchange( + val, + new_val, + Ordering::AcqRel, + Ordering::Acquire, + ) + .is_ok() + { + let mut memory = env.memory_view(&ctx); + let reader = val.to_ne_bytes(); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + ret = wasi_try_ok!( + read_bytes(&reader[..], &memory, iovs_arr), + env + ); + break; + } else { + continue; } + } - // If its none blocking then exit - if is_non_blocking { - return Ok(Errno::Again); - } + // If its none blocking then exit + if is_non_blocking { + return Ok(__WASI_EAGAIN); + } - // Yield for a fixed period of time and then check again - env.yield_now()?; - if rx.recv_timeout(Duration::from_millis(5)).is_err() { - env.sleep(Duration::from_millis(5))?; - } + // Yield for a fixed period of time and then check again + env.yield_now()?; + if rx.try_recv().is_err() { + env.clone().sleep(&mut ctx, Duration::from_millis(5))?; } - ret - } - Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), - Kind::Buffer { buffer } => { - wasi_try_ok!(read_bytes(&buffer[offset..], &memory, iovs_arr), env) + env = ctx.data(); } + ret } - }; + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), + Kind::Buffer { buffer } => { + let mut memory = env.memory_view(&ctx); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); + wasi_try_ok!(read_bytes(&buffer[offset..], &memory, iovs_arr), env) + } + } + }; + if is_stdio == false { // reborrow let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); - fd_entry.offset += bytes_read as u64; - - bytes_read + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset.fetch_add(bytes_read as u64, Ordering::AcqRel); } + + bytes_read }; - let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| Errno::Overflow)); + + let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| __WASI_EOVERFLOW)); + trace!("wasi[{}:{}]::fd_read: bytes_read={}", ctx.data().pid(), ctx.data().tid(), bytes_read); + + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let nread_ref = nread.deref(&memory); wasi_try_mem_ok!(nread_ref.write(bytes_read)); Ok(Errno::Success) @@ -1281,8 +1601,8 @@ pub fn fd_readdir( buf_len: M::Offset, cookie: Dircookie, bufused: WasmPtr, -) -> Errno { - trace!("wasi::fd_readdir"); +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::fd_readdir", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); // TODO: figure out how this is supposed to work; @@ -1296,8 +1616,7 @@ pub fn fd_readdir( let entries: Vec<(String, Filetype, u64)> = { let guard = inodes.arena[working_dir.inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { path, entries, .. } => { debug!("Reading dir {:?}", path); // TODO: refactor this code @@ -1408,23 +1727,42 @@ pub fn fd_readdir( /// File descriptor to copy /// - `Fd to` /// Location to copy file descriptor to -pub fn fd_renumber(ctx: FunctionEnvMut<'_, WasiEnv>, from: WasiFd, to: WasiFd) -> Errno { - debug!("wasi::fd_renumber: from={}, to={}", from, to); +pub fn fd_renumber( + ctx: FunctionEnvMut<'_, WasiEnv>, + from: __wasi_fd_t, + to: __wasi_fd_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_renumber(from={}, to={})", ctx.data().pid(), ctx.data().tid(), from, to); + + if from == to { + return __WASI_ESUCCESS; + } + let env = ctx.data(); - let (_, mut state) = env.get_memory_and_wasi_state(&ctx, 0); + let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let mut fd_map = state.fs.fd_map.write().unwrap(); let fd_entry = wasi_try!(fd_map.get_mut(&from).ok_or(Errno::Badf)); + if from != to { + fd_entry.ref_cnt.fetch_add(1, Ordering::Acquire); + } let new_fd_entry = Fd { // TODO: verify this is correct + ref_cnt: fd_entry.ref_cnt.clone(), + offset: fd_entry.offset.clone(), rights: fd_entry.rights_inheriting, ..*fd_entry }; + if let Some(fd_entry) = fd_map.get(&to).map(|a| a.clone()) { + if fd_entry.ref_cnt.fetch_sub(1, Ordering::AcqRel) == 1 { + wasi_try!(state.fs.close_fd_ext(inodes.deref(), &mut fd_map, to)); + } + } fd_map.insert(to, new_fd_entry); - fd_map.remove(&from); - Errno::Success + + __WASI_ESUCCESS } /// ### `fd_dup()` @@ -1437,10 +1775,10 @@ pub fn fd_renumber(ctx: FunctionEnvMut<'_, WasiEnv>, from: WasiFd, to: WasiFd) - /// The new file handle that is a duplicate of the original pub fn fd_dup( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - ret_fd: WasmPtr, -) -> Errno { - debug!("wasi::fd_dup"); + fd: __wasi_fd_t, + ret_fd: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_dup", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, state) = env.get_memory_and_wasi_state(&ctx, 0); @@ -1456,10 +1794,10 @@ pub fn fd_dup( pub fn fd_event( ctx: FunctionEnvMut<'_, WasiEnv>, initial_val: u64, - flags: EventFdFlags, - ret_fd: WasmPtr, -) -> Errno { - debug!("wasi::fd_event"); + flags: __wasi_eventfdflags, + ret_fd: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_event", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -1468,19 +1806,19 @@ pub fn fd_event( counter: Arc::new(AtomicU64::new(initial_val)), is_semaphore: flags & EVENT_FD_FLAGS_SEMAPHORE != 0, wakers: Default::default(), + immediate: Arc::new(AtomicBool::new(false)) }; let inode = state.fs.create_inode_with_default_stat( inodes.deref_mut(), kind, false, - "event".to_string(), + "event".into(), ); - let rights = Rights::FD_READ | Rights::FD_WRITE | Rights::POLL_FD_READWRITE; - let fd = wasi_try!(state - .fs - .create_fd(rights, rights, Fdflags::empty(), 0, inode)); + let rights = __WASI_RIGHT_FD_READ | __WASI_RIGHT_FD_WRITE | __WASI_RIGHT_POLL_FD_READWRITE | __WASI_RIGHT_FD_FDSTAT_SET_FLAGS; + let fd = wasi_try!(state.fs.create_fd(rights, rights, 0, 0, inode)); + debug!("wasi[{}:{}]::fd_event - event notifications created (fd={})", ctx.data().pid(), ctx.data().tid(), fd); wasi_try_mem!(ret_fd.write(&memory, fd)); Errno::Success @@ -1500,12 +1838,12 @@ pub fn fd_event( /// The new offset relative to the start of the file pub fn fd_seek( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - offset: FileDelta, - whence: Whence, - newoffset: WasmPtr, -) -> Result { - trace!("wasi::fd_seek: fd={}, offset={}", fd, offset); + fd: __wasi_fd_t, + offset: __wasi_filedelta_t, + whence: __wasi_whence_t, + newoffset: WasmPtr<__wasi_filesize_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::fd_seek: fd={}, offset={}", ctx.data().pid(), ctx.data().tid(), fd, offset); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let new_offset_ref = newoffset.deref(&memory); @@ -1516,28 +1854,35 @@ pub fn fd_seek( } // TODO: handle case if fd is a dir? - match whence { - Whence::Cur => { + let new_offset = match whence { + __WASI_WHENCE_CUR => { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); - fd_entry.offset = (fd_entry.offset as i64 + offset) as u64 + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + if offset > 0 { + fd_entry.offset.fetch_add(offset as u64, Ordering::AcqRel) + } else if offset < 0 { + fd_entry.offset.fetch_sub(offset.abs() as u64, Ordering::AcqRel) + } else { + fd_entry.offset.load(Ordering::Acquire) + } } Whence::End => { use std::io::SeekFrom; let inode_idx = fd_entry.inode; let mut guard = inodes.arena[inode_idx].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); let end = wasi_try_ok!(handle.seek(SeekFrom::End(0)).map_err(map_io_err), env); // TODO: handle case if fd_entry.offset uses 64 bits of a u64 + drop(handle); drop(guard); let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); - fd_entry.offset = (end as i64 + offset) as u64; + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset.store((end as i64 + offset) as u64, Ordering::Release); } else { return Ok(Errno::Inval); } @@ -1559,17 +1904,19 @@ pub fn fd_seek( return Ok(Errno::Inval); } } + fd_entry.offset.load(Ordering::Acquire) } Whence::Set => { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); - fd_entry.offset = offset as u64 + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset.store(offset as u64, Ordering::Release); + offset as u64 } - _ => return Ok(Errno::Inval), - } + _ => return Ok(__WASI_EINVAL), + }; // reborrow let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); - wasi_try_mem_ok!(new_offset_ref.write(fd_entry.offset)); + wasi_try_mem_ok!(new_offset_ref.write(new_offset)); Ok(Errno::Success) } @@ -1581,10 +1928,10 @@ pub fn fd_seek( /// The file descriptor to sync /// Errors: /// TODO: figure out which errors this should return -/// - `Errno::Perm` -/// - `Errno::Notcapable` -pub fn fd_sync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { - debug!("wasi::fd_sync"); +/// - `__WASI_EPERM` +/// - `__WASI_ENOTCAPABLE` +pub fn fd_sync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: __wasi_fd_t) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_sync", ctx.data().pid(), ctx.data().tid()); debug!("=> fd={}", fd); let env = ctx.data(); let (_, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); @@ -1596,11 +1943,11 @@ pub fn fd_sync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { // TODO: implement this for more than files { - let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + let mut guard = inodes.arena[inode].read(); + match guard.deref() { Kind::File { handle, .. } => { if let Some(h) = handle { + let mut h = h.read().unwrap(); wasi_try!(h.sync_to_disk().map_err(fs_error_into_wasi_err)); } else { return Errno::Inval; @@ -1628,10 +1975,10 @@ pub fn fd_sync(ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Errno { /// The offset of `fd` relative to the start of the file pub fn fd_tell( ctx: FunctionEnvMut<'_, WasiEnv>, - fd: WasiFd, - offset: WasmPtr, -) -> Errno { - debug!("wasi::fd_tell"); + fd: __wasi_fd_t, + offset: WasmPtr<__wasi_filesize_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::fd_tell", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state) = env.get_memory_and_wasi_state(&ctx, 0); let offset_ref = offset.deref(&memory); @@ -1642,7 +1989,7 @@ pub fn fd_tell( return Errno::Access; } - wasi_try_mem!(offset_ref.write(fd_entry.offset)); + wasi_try_mem!(offset_ref.write(fd_entry.offset.load(Ordering::Acquire))); Errno::Success } @@ -1667,117 +2014,127 @@ pub fn fd_write( iovs: WasmPtr<__wasi_ciovec_t, M>, iovs_len: M::Offset, nwritten: WasmPtr, -) -> Result { - trace!("wasi::fd_write: fd={}", fd); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::fd_write: fd={}", ctx.data().pid(), ctx.data().tid(), fd); let env = ctx.data(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); let nwritten_ref = nwritten.deref(&memory); let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); - let bytes_written = match fd { - __WASI_STDIN_FILENO => return Ok(Errno::Inval), - __WASI_STDOUT_FILENO => { - let mut guard = wasi_try_ok!( - inodes - .stdout_mut(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ); - if let Some(ref mut stdout) = guard.deref_mut() { - wasi_try_ok!(write_bytes(stdout, &memory, iovs_arr), env) - } else { - return Ok(Errno::Badf); - } - } - __WASI_STDERR_FILENO => { - let mut guard = wasi_try_ok!( - inodes - .stderr_mut(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ); - if let Some(ref mut stderr) = guard.deref_mut() { - wasi_try_ok!(write_bytes(stderr, &memory, iovs_arr), env) - } else { - return Ok(Errno::Badf); + + let is_stdio = match fd { + __WASI_STDIN_FILENO => return Ok(__WASI_EINVAL), + __WASI_STDOUT_FILENO => true, + __WASI_STDERR_FILENO => true, + _ => false, + }; + + let bytes_written ={ + if is_stdio == false { + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { + return Ok(__WASI_EACCES); } } - _ => { - if !fd_entry.rights.contains(Rights::FD_WRITE) { - return Ok(Errno::Access); - } - let offset = fd_entry.offset as usize; - let inode_idx = fd_entry.inode; - let inode = &inodes.arena[inode_idx]; + let offset = fd_entry.offset.load(Ordering::Acquire) as usize; + let inode_idx = fd_entry.inode; + let inode = &inodes.arena[inode_idx]; - let bytes_written = { - let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { - Kind::File { handle, .. } => { - if let Some(handle) = handle { + let bytes_written = { + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); + if is_stdio == false { wasi_try_ok!( handle .seek(std::io::SeekFrom::Start(offset as u64)) .map_err(map_io_err), env ); - wasi_try_ok!(write_bytes(handle, &memory, iovs_arr), env) - } else { - return Ok(Errno::Inval); } + wasi_try_ok!(write_bytes(handle.deref_mut(), &memory, iovs_arr), env) + } else { + return Ok(__WASI_EINVAL); } - Kind::Socket { socket } => { - wasi_try_ok!(socket.send(&memory, iovs_arr), env) - } - Kind::Pipe { pipe } => { - wasi_try_ok!(pipe.send(&memory, iovs_arr), env) - } - Kind::Dir { .. } | Kind::Root { .. } => { - // TODO: verify - return Ok(Errno::Isdir); + } + Kind::Socket { socket } => { + let buf_len: M::Offset = iovs_arr + .iter() + .filter_map(|a| a.read().ok()) + .map(|a| a.buf_len) + .sum(); + let buf_len: usize = wasi_try_ok!(buf_len.try_into().map_err(|_| __WASI_EINVAL)); + let mut buf = Vec::with_capacity(buf_len); + wasi_try_ok!(write_bytes(&mut buf, &memory, iovs_arr)); + + let socket = socket.clone(); + wasi_try_ok!( + __asyncify( + env.tasks.clone(), + &env.thread, + None, + async move { + socket.send(buf).await + } + ) + ) + } + Kind::Pipe { pipe } => { + wasi_try_ok!(pipe.send(&memory, iovs_arr), env) + } + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: verify + return Ok(__WASI_EISDIR); + } + Kind::EventNotifications { + counter, wakers, immediate, .. + } => { + let mut val = 0u64.to_ne_bytes(); + let written = + wasi_try_ok!(write_bytes(&mut val[..], &memory, iovs_arr)); + if written != val.len() { + return Ok(__WASI_EINVAL); } - Kind::EventNotifications { - counter, wakers, .. - } => { - let mut val = 0u64.to_ne_bytes(); - let written = wasi_try_ok!(write_bytes(&mut val[..], &memory, iovs_arr)); - if written != val.len() { - return Ok(Errno::Inval); - } - let val = u64::from_ne_bytes(val); + let val = u64::from_ne_bytes(val); - counter.fetch_add(val, Ordering::AcqRel); - { - let mut guard = wakers.lock().unwrap(); - while let Some(wake) = guard.pop_back() { - if wake.send(()).is_ok() { - break; - } - } + counter.fetch_add(val, Ordering::AcqRel); + { + let mut guard = wakers.lock().unwrap(); + immediate.store(true, Ordering::Release); + while let Some(wake) = guard.pop_back() { + let _ = wake.send(()); } - - written - } - Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_write"), - Kind::Buffer { buffer } => { - wasi_try_ok!(write_bytes(&mut buffer[offset..], &memory, iovs_arr), env) } + + written } - }; + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_write"), + Kind::Buffer { buffer } => { + wasi_try_ok!( + write_bytes(&mut buffer[offset..], &memory, iovs_arr), + env + ) + } + } + }; - // reborrow + // reborrow and update the size + if is_stdio == false { { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); - fd_entry.offset += bytes_written as u64; + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset.fetch_add(bytes_written as u64, Ordering::AcqRel); } - wasi_try_ok!(state.fs.filestat_resync_size(inodes.deref(), fd), env); - bytes_written + // we set teh size but we don't return any errors if it fails as + // pipes and sockets will not do anything with this + let _ = state.fs.filestat_resync_size(inodes.deref(), fd); } + + bytes_written }; let bytes_written: M::Offset = @@ -1796,10 +2153,10 @@ pub fn fd_write( /// Second file handle that represents the other end of the pipe pub fn fd_pipe( ctx: FunctionEnvMut<'_, WasiEnv>, - ro_fd1: WasmPtr, - ro_fd2: WasmPtr, -) -> Errno { - trace!("wasi::fd_pipe"); + ro_fd1: WasmPtr<__wasi_fd_t, M>, + ro_fd2: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::fd_pipe", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -1812,22 +2169,19 @@ pub fn fd_pipe( inodes.deref_mut(), Kind::Pipe { pipe: pipe1 }, false, - "pipe".to_string(), + "pipe".into(), ); let inode2 = state.fs.create_inode_with_default_stat( inodes.deref_mut(), Kind::Pipe { pipe: pipe2 }, false, - "pipe".to_string(), + "pipe".into(), ); - let rights = Rights::all_socket(); - let fd1 = wasi_try!(state - .fs - .create_fd(rights, rights, Fdflags::empty(), 0, inode1)); - let fd2 = wasi_try!(state - .fs - .create_fd(rights, rights, Fdflags::empty(), 0, inode2)); + let rights = super::state::all_socket_rights(); + let fd1 = wasi_try!(state.fs.create_fd(rights, rights, 0, 0, inode1)); + let fd2 = wasi_try!(state.fs.create_fd(rights, rights, 0, 0, inode2)); + trace!("wasi[{}:{}]::fd_pipe (fd1={}, fd2={})", ctx.data().pid(), ctx.data().tid(), fd1, fd2); wasi_try_mem!(ro_fd1.write(&memory, fd1)); wasi_try_mem!(ro_fd2.write(&memory, fd2)); @@ -1853,8 +2207,8 @@ pub fn path_create_directory( fd: WasiFd, path: WasmPtr, path_len: M::Offset, -) -> Errno { - debug!("wasi::path_create_directory"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_create_directory", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -1868,8 +2222,14 @@ pub fn path_create_directory( if !working_dir.rights.contains(Rights::PATH_CREATE_DIRECTORY) { return Errno::Access; } - let path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; debug!("=> fd: {}, path: {}", fd, &path_string); + + // Convert relative paths into absolute paths + if path_string.starts_with("./") { + path_string = ctx.data().state.fs.relative_path_to_absolute(path_string); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_string); + } let path = std::path::PathBuf::from(&path_string); let path_vec = wasi_try!(path @@ -1891,8 +2251,7 @@ pub fn path_create_directory( for comp in &path_vec { debug!("Creating dir {}", comp); let mut guard = inodes.arena[cur_dir_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { ref mut entries, path, @@ -1983,13 +2342,19 @@ pub fn path_filestat_get( flags: LookupFlags, path: WasmPtr, path_len: M::Offset, - buf: WasmPtr, -) -> Errno { - debug!("wasi::path_filestat_get (fd={})", fd); + buf: WasmPtr<__wasi_filestat_t, M>, +) -> __wasi_errno_t { let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); - let path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; + debug!("wasi[{}:{}]::path_filestat_get (fd={}, path={})", ctx.data().pid(), ctx.data().tid(), fd, path_string); + + // Convert relative paths into absolute paths + if path_string.starts_with("./") { + path_string = ctx.data().state.fs.relative_path_to_absolute(path_string); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_string); + } let stat = wasi_try!(path_filestat_get_internal( &memory, @@ -2032,8 +2397,7 @@ pub fn path_filestat_get_internal( if !root_dir.rights.contains(Rights::PATH_FILESTAT_GET) { return Err(Errno::Access); } - debug!("=> base_fd: {}, path: {}", fd, path_string); - + let file_inode = state.fs.get_inode_at_path( inodes, fd, @@ -2071,11 +2435,11 @@ pub fn path_filestat_set_times( flags: LookupFlags, path: WasmPtr, path_len: M::Offset, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - debug!("wasi::path_filestat_set_times"); + st_atim: __wasi_timestamp_t, + st_mtim: __wasi_timestamp_t, + fst_flags: __wasi_fstflags_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_filestat_set_times", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); @@ -2089,9 +2453,15 @@ pub fn path_filestat_set_times( return Errno::Inval; } - let path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; debug!("=> base_fd: {}, path: {}", fd, &path_string); + // Convert relative paths into absolute paths + if path_string.starts_with("./") { + path_string = ctx.data().state.fs.relative_path_to_absolute(path_string); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_string); + } + let file_inode = wasi_try!(state.fs.get_inode_at_path( inodes.deref_mut(), fd, @@ -2151,15 +2521,15 @@ pub fn path_link( new_fd: WasiFd, new_path: WasmPtr, new_path_len: M::Offset, -) -> Errno { - debug!("wasi::path_link"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_link", ctx.data().pid(), ctx.data().tid()); if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { debug!(" - will follow symlinks when opening path"); } let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); - let old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; - let new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + let mut old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + let mut new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; let source_fd = wasi_try!(state.fs.get_fd(old_fd)); let target_fd = wasi_try!(state.fs.get_fd(new_fd)); debug!( @@ -2173,6 +2543,10 @@ pub fn path_link( return Errno::Access; } + // Convert relative paths into absolute paths + old_path_str = ctx.data().state.fs.relative_path_to_absolute(old_path_str); + new_path_str = ctx.data().state.fs.relative_path_to_absolute(new_path_str); + let source_inode = wasi_try!(state.fs.get_inode_at_path( inodes.deref_mut(), old_fd, @@ -2192,8 +2566,7 @@ pub fn path_link( } { let mut guard = inodes.arena[target_parent_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { entries, .. } => { if entries.contains_key(&new_entry_name) { return Errno::Exist; @@ -2244,13 +2617,13 @@ pub fn path_open( dirflags: LookupFlags, path: WasmPtr, path_len: M::Offset, - o_flags: Oflags, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, - fs_flags: Fdflags, - fd: WasmPtr, -) -> Errno { - debug!("wasi::path_open"); + o_flags: __wasi_oflags_t, + fs_rights_base: __wasi_rights_t, + fs_rights_inheriting: __wasi_rights_t, + fs_flags: __wasi_fdflags_t, + fd: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_open", ctx.data().pid(), ctx.data().tid()); if dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { debug!(" - will follow symlinks when opening path"); } @@ -2277,10 +2650,15 @@ pub fn path_open( if !working_dir.rights.contains(Rights::PATH_OPEN) { return Errno::Access; } + let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; - let path_string = unsafe { get_input_str!(&memory, path, path_len) }; + debug!("=> dirfd: {}, path: {}", dirfd, &path_string); - debug!("=> path_open(): fd: {}, path: {}", dirfd, &path_string); + // Convert relative paths into absolute paths + if path_string.starts_with("./") { + path_string = ctx.data().state.fs.relative_path_to_absolute(path_string); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_string); + } let path_arg = std::path::PathBuf::from(&path_string); let maybe_inode = state.fs.get_inode_at_path( @@ -2352,8 +2730,7 @@ pub fn path_open( let inode = if let Ok(inode) = maybe_inode { // Happy path, we found the file we're trying to open let mut guard = inodes.arena[inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { ref mut handle, path, @@ -2390,10 +2767,25 @@ pub fn path_open( if minimum_rights.truncate { open_flags |= Fd::TRUNCATE; } - - *handle = Some(wasi_try!(open_options - .open(&path) - .map_err(fs_error_into_wasi_err))); + *handle = Some( + Arc::new(std::sync::RwLock::new( + wasi_try!(open_options.open(&path).map_err(fs_error_into_wasi_err)) + )) + ); + + if let Some(handle) = handle { + let handle = handle.read().unwrap(); + if let Some(special_fd) = handle.get_special_fd() { + // We close the file descriptor so that when its closed + // nothing bad happens + let special_fd = wasi_try!(state.fs.clone_fd(special_fd)); + + // some special files will return a constant FD rather than + // actually open the file (/dev/stdin, /dev/stdout, /dev/stderr) + wasi_try_mem!(fd_ref.write(special_fd)); + return __WASI_ESUCCESS; + } + } } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), Kind::Root { .. } => { @@ -2434,8 +2826,7 @@ pub fn path_open( )); let new_file_host_path = { let guard = inodes.arena[parent_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { path, .. } => { let mut new_path = path.clone(); new_path.push(&new_entity_name); @@ -2481,7 +2872,7 @@ pub fn path_open( let new_inode = { let kind = Kind::File { - handle, + handle: handle.map(|a| Arc::new(std::sync::RwLock::new(a))), path: new_file_host_path, fd: None, }; @@ -2509,10 +2900,6 @@ pub fn path_open( } }; - { - debug!("inode {:?} value {:#?} found!", inode, inodes.arena[inode]); - } - // TODO: check and reduce these // TODO: ensure a mutable fd to root can never be opened let out_fd = wasi_try!(state.fs.create_fd( @@ -2524,7 +2911,7 @@ pub fn path_open( )); wasi_try_mem!(fd_ref.write(out_fd)); - debug!("wasi::path_open returning fd {}", out_fd); + debug!("wasi[{}:{}]::path_open returning fd {}", ctx.data().pid(), ctx.data().tid(), out_fd); Errno::Success } @@ -2553,8 +2940,8 @@ pub fn path_readlink( buf: WasmPtr, buf_len: M::Offset, buf_used: WasmPtr, -) -> Errno { - debug!("wasi::path_readlink"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_readlink", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -2562,7 +2949,14 @@ pub fn path_readlink( if !base_dir.rights.contains(Rights::PATH_READLINK) { return Errno::Access; } - let path_str = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_str = unsafe { get_input_str!(&memory, path, path_len) }; + + // Convert relative paths into absolute paths + if path_str.starts_with("./") { + path_str = ctx.data().state.fs.relative_path_to_absolute(path_str); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_str); + } + let inode = wasi_try!(state .fs .get_inode_at_path(inodes.deref_mut(), dir_fd, &path_str, false)); @@ -2579,7 +2973,8 @@ pub fn path_readlink( } let bytes: Vec<_> = bytes.collect(); - let out = wasi_try_mem!(buf.slice(&memory, wasi_try!(to_offset::(bytes.len())))); + let out = + wasi_try_mem!(buf.slice(&memory, wasi_try!(to_offset::(bytes.len())))); wasi_try_mem!(out.write_slice(&bytes)); // should we null terminate this? @@ -2602,12 +2997,18 @@ pub fn path_remove_directory( path_len: M::Offset, ) -> Errno { // TODO check if fd is a dir, ensure it's within sandbox, etc. - debug!("wasi::path_remove_directory"); + debug!("wasi[{}:{}]::path_remove_directory", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); let base_dir = wasi_try!(state.fs.get_fd(fd)); - let path_str = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_str = unsafe { get_input_str!(&memory, path, path_len) }; + + // Convert relative paths into absolute paths + if path_str.starts_with("./") { + path_str = ctx.data().state.fs.relative_path_to_absolute(path_str); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_str); + } let inode = wasi_try!(state .fs @@ -2621,8 +3022,7 @@ pub fn path_remove_directory( let host_path_to_remove = { let guard = inodes.arena[inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { entries, path, .. } => { if !entries.is_empty() || wasi_try!(state.fs_read_dir(path)).count() != 0 { return Errno::Notempty; @@ -2636,8 +3036,7 @@ pub fn path_remove_directory( { let mut guard = inodes.arena[parent_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { ref mut entries, .. } => { @@ -2697,9 +3096,11 @@ pub fn path_rename( ); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); - let source_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + let mut source_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + source_str = ctx.data().state.fs.relative_path_to_absolute(source_str); let source_path = std::path::Path::new(&source_str); - let target_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + let mut target_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + target_str = ctx.data().state.fs.relative_path_to_absolute(target_str); let target_path = std::path::Path::new(&target_str); debug!("=> rename from {} to {}", &source_str, &target_str); @@ -2736,11 +3137,10 @@ pub fn path_rename( wasi_try!(state .fs .get_parent_inode_at_path(inodes.deref_mut(), new_fd, target_path, true)); - let mut need_create = true; + let host_adjusted_target_path = { let guard = inodes.arena[target_parent_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { entries, path, .. } => { if entries.contains_key(&target_entry_name) { need_create = false; @@ -2761,8 +3161,7 @@ pub fn path_rename( let source_entry = { let mut guard = inodes.arena[source_parent_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { entries, .. } => { wasi_try!(entries.remove(&source_entry_name).ok_or(Errno::Noent)) } @@ -2778,8 +3177,7 @@ pub fn path_rename( { let mut guard = inodes.arena[source_entry].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, ref path, .. } => { @@ -2870,12 +3268,14 @@ pub fn path_symlink( fd: WasiFd, new_path: WasmPtr, new_path_len: M::Offset, -) -> Errno { - debug!("wasi::path_symlink"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_symlink", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); - let old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; - let new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + let mut old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + let mut new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + old_path_str = ctx.data().state.fs.relative_path_to_absolute(old_path_str); + new_path_str = ctx.data().state.fs.relative_path_to_absolute(new_path_str); let base_fd = wasi_try!(state.fs.get_fd(fd)); if !base_fd.rights.contains(Rights::PATH_SYMLINK) { return Errno::Access; @@ -2906,8 +3306,7 @@ pub fn path_symlink( // short circuit if anything is wrong, before we create an inode { let guard = inodes.arena[target_parent_inode].read(); - let deref = guard.deref(); - match deref { + match guard.deref() { Kind::Dir { entries, .. } => { if entries.contains_key(&entry_name) { return Errno::Exist; @@ -2944,7 +3343,7 @@ pub fn path_symlink( inodes.deref_mut(), kind, false, - entry_name.clone(), + entry_name.clone().into(), ); { @@ -2974,8 +3373,8 @@ pub fn path_unlink_file( fd: WasiFd, path: WasmPtr, path_len: M::Offset, -) -> Errno { - debug!("wasi::path_unlink_file"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::path_unlink_file", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -2983,9 +3382,15 @@ pub fn path_unlink_file( if !base_dir.rights.contains(Rights::PATH_UNLINK_FILE) { return Errno::Access; } - let path_str = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_str = unsafe { get_input_str!(&memory, path, path_len) }; debug!("Requested file: {}", path_str); + // Convert relative paths into absolute paths + if path_str.starts_with("./") { + path_str = ctx.data().state.fs.relative_path_to_absolute(path_str); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), path_str); + } + let inode = wasi_try!(state .fs .get_inode_at_path(inodes.deref_mut(), fd, &path_str, false)); @@ -2998,8 +3403,7 @@ pub fn path_unlink_file( let removed_inode = { let mut guard = inodes.arena[parent_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::Dir { ref mut entries, .. } => { @@ -3023,17 +3427,18 @@ pub fn path_unlink_file( }; if st_nlink == 0 { { - let mut guard = inodes.arena[removed_inode].write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + let mut guard = inodes.arena[removed_inode].read(); + match guard.deref() { Kind::File { handle, path, .. } => { if let Some(h) = handle { + let mut h = h.write().unwrap(); wasi_try!(h.unlink().map_err(fs_error_into_wasi_err)); } else { // File is closed // problem with the abstraction, we can't call unlink because there's no handle // drop mutable borrow on `path` let path = path.clone(); + drop(guard); wasi_try!(state.fs_remove_file(path)); } } @@ -3083,34 +3488,37 @@ pub fn path_unlink_file( /// - `u32 nevents` /// The number of events seen pub fn poll_oneoff( - ctx: FunctionEnvMut<'_, WasiEnv>, - in_: WasmPtr, - out_: WasmPtr, + mut ctx: FunctionEnvMut<'_, WasiEnv>, + in_: WasmPtr<__wasi_subscription_t, M>, + out_: WasmPtr<__wasi_event_t, M>, nsubscriptions: M::Offset, nevents: WasmPtr, -) -> Result { - trace!("wasi::poll_oneoff"); - trace!(" => nsubscriptions = {}", nsubscriptions); - let env = ctx.data(); - let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); +) -> Result<__wasi_errno_t, WasiError> { - let subscription_array = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions)); - let event_array = wasi_try_mem_ok!(out_.slice(&memory, nsubscriptions)); - let mut events_seen: u32 = 0; - let out_ptr = nevents.deref(&memory); + let pid = ctx.data().pid(); + let tid = ctx.data().tid(); + trace!("wasi[{}:{}]::poll_oneoff (nsubscriptions={})", pid, tid, nsubscriptions); - let mut fd_guards = vec![]; + // These are used when we capture what clocks (timeouts) are being + // subscribed too let mut clock_subs = vec![]; - let mut in_events = vec![]; - let mut time_to_sleep = Duration::from_millis(5); + let mut time_to_sleep = None; + // First we extract all the subscriptions into an array so that they + // can be processed + let env = ctx.data(); + let state = ctx.data().state.deref(); + let memory = env.memory_view(&ctx); + let mut subscriptions = HashMap::new(); + let subscription_array = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions)); for sub in subscription_array.iter() { - let s: Subscription = wasi_try_mem_ok!(sub.read()); - let mut peb = PollEventBuilder::new(); + let s: WasiSubscription = wasi_try_ok!(wasi_try_mem_ok!(sub.read()).try_into()); - let fd = match s.data { - SubscriptionEnum::Read(SubscriptionFsReadwrite { file_descriptor }) => { - match file_descriptor { + let mut peb = PollEventBuilder::new(); + let mut in_events = HashMap::new(); + let fd = match s.event_type { + EventType::Read(__wasi_subscription_fs_readwrite_t { fd }) => { + match fd { __WASI_STDIN_FILENO | __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => (), _ => { let fd_entry = wasi_try_ok!(state.fs.get_fd(file_descriptor), env); @@ -3119,8 +3527,8 @@ pub fn poll_oneoff( } } } - in_events.push(peb.add(PollEvent::PollIn).build()); - Some(file_descriptor) + in_events.insert(peb.add(PollEvent::PollIn).build(), s); + fd } SubscriptionEnum::Write(SubscriptionFsReadwrite { file_descriptor }) => { match file_descriptor { @@ -3132,192 +3540,207 @@ pub fn poll_oneoff( } } } - in_events.push(peb.add(PollEvent::PollOut).build()); - Some(file_descriptor) + in_events.insert(peb.add(PollEvent::PollOut).build(), s); + fd } SubscriptionEnum::Clock(clock_info) => { if matches!(clock_info.clock_id, Clockid::Realtime | Clockid::Monotonic) { // this is a hack // TODO: do this properly - time_to_sleep = Duration::from_nanos(clock_info.timeout); - clock_subs.push((clock_info, s.userdata)); - None + time_to_sleep = Some(Duration::from_nanos(clock_info.timeout)); + clock_subs.push((clock_info, s.user_data)); + continue; } else { unimplemented!("Polling not implemented for clocks yet"); } } }; - if let Some(fd) = fd { - let wasi_file_ref = match fd { - __WASI_STDERR_FILENO => { - wasi_try_ok!( - inodes - .stderr(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ) - } - __WASI_STDIN_FILENO => { - wasi_try_ok!( - inodes - .stdin(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ) - } - __WASI_STDOUT_FILENO => { - wasi_try_ok!( - inodes - .stdout(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err), - env - ) - } - _ => { - let fd_entry = wasi_try_ok!(state.fs.get_fd(fd), env); - let inode = fd_entry.inode; - if !fd_entry.rights.contains(Rights::POLL_FD_READWRITE) { - return Ok(Errno::Access); - } + let entry = subscriptions + .entry(fd) + .or_insert_with(|| HashMap::::default()); + entry.extend(in_events.into_iter()); + } + drop(env); - { - let guard = inodes.arena[inode].read(); - let deref = guard.deref(); - match deref { - Kind::File { handle, .. } => { - if let Some(h) = handle { - crate::state::InodeValFileReadGuard { guard } + // If there is a timeout we need to use the runtime to measure this + // otherwise we just process all the events and wait on them indefinately + if let Some(time_to_sleep) = time_to_sleep.as_ref() { + tracing::trace!("wasi[{}:{}]::poll_oneoff wait_for_timeout={}", pid, tid, time_to_sleep.as_millis()); + } + let time_to_sleep = time_to_sleep; + + let mut events_seen: u32 = 0; + + // Build the async function we will block on + let state = ctx.data().state.clone(); + let (triggered_events_tx, mut triggered_events_rx) = std::sync::mpsc::channel(); + let tasks = ctx.data().tasks.clone(); + let work = { + let tasks = tasks.clone(); + let triggered_events_tx = triggered_events_tx.clone(); + async move { + // We start by building a list of files we are going to poll + // and open a read lock on them all + let inodes = state.inodes.clone(); + let inodes = inodes.read().unwrap(); + let mut fd_guards = vec![]; + + #[allow(clippy::significant_drop_in_scrutinee)] + let fds = { + for (fd, in_events) in subscriptions { + let wasi_file_ref = match fd { + __WASI_STDERR_FILENO => { + wasi_try_ok!( + inodes + .stderr(&state.fs.fd_map) + .map(|g| g.into_poll_guard(fd, in_events, tasks.clone())) + .map_err(fs_error_into_wasi_err) + ) + } + __WASI_STDIN_FILENO => { + wasi_try_ok!( + inodes + .stdin(&state.fs.fd_map) + .map(|g| g.into_poll_guard(fd, in_events, tasks.clone())) + .map_err(fs_error_into_wasi_err) + ) + } + __WASI_STDOUT_FILENO => { + wasi_try_ok!( + inodes + .stdout(&state.fs.fd_map) + .map(|g| g.into_poll_guard(fd, in_events, tasks.clone())) + .map_err(fs_error_into_wasi_err) + ) + } + _ => { + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); + let inode = fd_entry.inode; + if !has_rights(fd_entry.rights, __WASI_RIGHT_POLL_FD_READWRITE) { + return Ok(__WASI_EACCES); + } + + { + let guard = inodes.arena[inode].read(); + if let Some(guard) = crate::state::InodeValFilePollGuard::new(fd, guard.deref(), in_events, tasks.clone()) { + guard } else { return Ok(Errno::Badf); } } - Kind::Socket { .. } - | Kind::Pipe { .. } - | Kind::EventNotifications { .. } => { - return Ok(Errno::Badf); - } - Kind::Dir { .. } - | Kind::Root { .. } - | Kind::Buffer { .. } - | Kind::Symlink { .. } => { - unimplemented!("polling read on non-files not yet supported") - } } - } + }; + tracing::trace!("wasi[{}:{}]::poll_oneoff wait_for_fd={} type={:?}", pid, tid, fd, wasi_file_ref); + fd_guards.push(wasi_file_ref); } + + fd_guards }; - fd_guards.push(wasi_file_ref); - } - } + + // Build all the async calls we need for all the files + let mut polls = Vec::new(); + for guard in fds + { + // Combine all the events together + let mut peb = PollEventBuilder::new(); + for (in_events, _) in guard.subscriptions.iter() { + for in_event in iterate_poll_events(*in_events) { + peb = peb.add(in_event); + } + } + let peb = peb.build(); + + let triggered_events_tx = triggered_events_tx.clone(); + let poll = Box::pin(async move { + let mut flags = 0; + let mut bytes_available = 0; + + // Wait for it to trigger (or throw an error) then + // once it has triggered an event will be returned + // that we can give to the caller + let evts = guard.wait().await; + for evt in evts { + tracing::trace!("wasi[{}:{}]::poll_oneoff (fd_triggered={}, type={})", pid, tid, guard.fd, evt.type_); + triggered_events_tx + .send(evt) + .unwrap(); + } + }); + polls.push(poll); + } - #[allow(clippy::significant_drop_in_scrutinee)] - let fds = { - let mut f = vec![]; - for fd in fd_guards.iter() { - f.push(wasi_try_ok!(fd.as_ref().ok_or(Errno::Badf)).deref()); + // We have to drop the lock on inodes otherwise it will freeze up the + // IO subsystem + drop(inodes); + + // This is the part that actually does the waiting + if polls.is_empty() == false { + futures::future::select_all(polls.into_iter()).await; + } else { + InfiniteSleep::default().await; + } + Ok(__WASI_ESUCCESS) } - f }; - let mut seen_events = vec![Default::default(); in_events.len()]; - - let start = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; - let mut triggered = 0; - while triggered == 0 { - let now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; - let delta = match now.checked_sub(start) { - Some(a) => Duration::from_nanos(a as u64), - None => Duration::ZERO, - }; - match poll( - fds.as_slice(), - in_events.as_slice(), - seen_events.as_mut_slice(), - Duration::from_millis(1), - ) { - Ok(0) => { - env.yield_now()?; - } - Ok(a) => { - triggered = a; - } - Err(FsError::WouldBlock) => { - env.sleep(Duration::from_millis(1))?; - } - Err(err) => { - return Ok(fs_error_into_wasi_err(err)); - } - }; - if delta > time_to_sleep { - break; + // Block on the work and process process + let env = ctx.data(); + let mut ret = __asyncify( + env.tasks.clone(), + &env.thread, + time_to_sleep, + async move { + work.await } - } + ); - for (i, seen_event) in seen_events.into_iter().enumerate() { - let mut flags = Eventrwflags::empty(); - let mut error = Errno::Again; - let mut bytes_available = 0; - let event_iter = iterate_poll_events(seen_event); - for event in event_iter { - match event { - PollEvent::PollError => error = Errno::Io, - PollEvent::PollHangUp => flags = Eventrwflags::FD_READWRITE_HANGUP, - PollEvent::PollInvalid => error = Errno::Inval, - PollEvent::PollIn => { - bytes_available = wasi_try_ok!( - fds[i] - .bytes_available_read() - .map_err(fs_error_into_wasi_err), - env - ) - .unwrap_or(0usize); - error = Errno::Success; - } - PollEvent::PollOut => { - bytes_available = wasi_try_ok!( - fds[i] - .bytes_available_write() - .map_err(fs_error_into_wasi_err), - env - ) - .unwrap_or(0usize); - error = Errno::Success; + // If its a timeout then return an event for it + if let Err(__WASI_ETIMEDOUT) = ret { + tracing::trace!("wasi[{}:{}]::poll_oneoff triggered_timeout", pid, tid); + + // The timeout has triggerred so lets add that event + for (clock_info, userdata) in clock_subs { + triggered_events_tx.send( + __wasi_event_t { + userdata, + error: __WASI_ESUCCESS, + type_: __WASI_EVENTTYPE_CLOCK, + u: unsafe { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + } + }, } - } + ).unwrap(); } - let event = Event { - userdata: wasi_try_mem_ok!(subscription_array.index(i as u64).read()).userdata, - error, - data: match wasi_try_mem_ok!(subscription_array.index(i as u64).read()).data { - SubscriptionEnum::Read(d) => EventEnum::FdRead(EventFdReadwrite { - nbytes: bytes_available as u64, - flags, - }), - SubscriptionEnum::Write(d) => EventEnum::FdWrite(EventFdReadwrite { - nbytes: bytes_available as u64, - flags, - }), - SubscriptionEnum::Clock(_) => EventEnum::Clock, - }, - }; + ret = Ok(__WASI_ESUCCESS); + } + + // If its a signal then process them + if let Err(__WASI_EINTR) = ret { + let env = ctx.data().clone(); + env.process_signals(&mut ctx)?; + ret = Ok(__WASI_ESUCCESS); + } + let ret = wasi_try_ok!(ret); + + // Process all the events that were triggered + let mut env = ctx.data(); + let memory = env.memory_view(&ctx); + let event_array = wasi_try_mem_ok!(out_.slice(&memory, nsubscriptions)); + while let Ok(event) = triggered_events_rx.try_recv() { wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); events_seen += 1; } - if triggered == 0 { - for (clock_info, userdata) in clock_subs { - let event = Event { - userdata, - error: Errno::Success, - data: EventEnum::Clock, - }; - wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); - events_seen += 1; - } - } - let events_seen: M::Offset = wasi_try_ok!(events_seen.try_into().map_err(|_| Errno::Overflow)); + let events_seen: M::Offset = wasi_try_ok!(events_seen.try_into().map_err(|_| __WASI_EOVERFLOW)); + let out_ptr = nevents.deref(&memory); wasi_try_mem_ok!(out_ptr.write(events_seen)); - Ok(Errno::Success) + tracing::trace!("wasi[{}:{}]::poll_oneoff ret={} seen={}", pid, tid, ret, events_seen); + Ok(ret) } /// ### `proc_exit()` @@ -3327,47 +3750,759 @@ pub fn poll_oneoff( /// Inputs: /// - `__wasi_exitcode_t` /// Exit code to return to the operating system -pub fn proc_exit( - ctx: FunctionEnvMut<'_, WasiEnv>, +pub fn proc_exit( + mut ctx: FunctionEnvMut<'_, WasiEnv>, code: __wasi_exitcode_t, ) -> Result<(), WasiError> { - debug!("wasi::proc_exit, {}", code); + debug!("wasi[{}:{}]::proc_exit (code={})", ctx.data().pid(), ctx.data().tid(), code); + + // Set the exit code for this process + ctx.data().thread.terminate(code as u32); + + // If we are in a vfork we need to return to the point we left off + if let Some(mut vfork) = ctx.data_mut().vfork.take() + { + // Restore the WasiEnv to the point when we vforked + std::mem::swap(&mut vfork.env.inner, &mut ctx.data_mut().inner); + std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); + let mut wasi_env = *vfork.env; + wasi_env.owned_handles.push(vfork.handle); + + // We still need to create the process that exited so that + // the exit code can be used by the parent process + let pid = wasi_env.process.pid(); + let mut memory_stack = vfork.memory_stack; + let rewind_stack = vfork.rewind_stack; + let store_data = vfork.store_data; + + // If the return value offset is within the memory stack then we need + // to update it here rather than in the real memory + let pid_offset: u64 = vfork.pid_offset.into(); + if pid_offset >= wasi_env.stack_start && pid_offset < wasi_env.stack_base + { + // Make sure its within the "active" part of the memory stack + let offset = wasi_env.stack_base - pid_offset; + if offset as usize > memory_stack.len() { + warn!("wasi[{}:{}]::vfork failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", ctx.data().pid(), ctx.data().tid(), offset, memory_stack.len()); + return Err(WasiError::Exit(__WASI_EFAULT as u32)); + } + + // Update the memory stack with the new PID + let val_bytes = pid.raw().to_ne_bytes(); + let pstart = memory_stack.len() - offset as usize; + let pend = pstart + val_bytes.len(); + let pbytes = &mut memory_stack[pstart..pend]; + pbytes.clone_from_slice(&val_bytes); + } else { + warn!("wasi[{}:{}]::vfork failed - the return value (pid) is not being returned on the stack - which is not supported", ctx.data().pid(), ctx.data().tid()); + return Err(WasiError::Exit(__WASI_EFAULT as u32)); + } + + // Jump back to the vfork point and current on execution + unwind::(ctx, move |mut ctx, _, _| + { + // Now rewind the previous stack and carry on from where we did the vfork + match rewind::(ctx, memory_stack.freeze(), rewind_stack.freeze(), store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("fork failed - could not rewind the stack - errno={}", err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + } + })?; + return Ok(()); + } + + // Otherwise just exit Err(WasiError::Exit(code)) } +/// ### `thread_signal()` +/// Send a signal to a particular thread in the current process. +/// Note: This is similar to `signal` in POSIX. +/// Inputs: +/// - `__wasi_signal_t` +/// Signal to be raised for this process +#[cfg(feature = "os")] +pub fn thread_signal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + tid: __wasi_tid_t, + sig: __wasi_signal_t +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::thread_signal(tid={}, sig={})", ctx.data().pid(), ctx.data().tid(), tid, sig); + { + let tid: WasiThreadId = tid.into(); + ctx.data().process.signal_thread(&tid, sig); + } + + let env = ctx.data(); + env.clone().yield_now_with_signals(&mut ctx)?; + + Ok(__WASI_ESUCCESS) +} + +#[cfg(not(feature = "os"))] +pub fn thread_signal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + tid: __wasi_tid_t, + sig: __wasi_signal_t +) -> Result<__wasi_errno_t, WasiError> { + warn!("wasi[{}:{}]::thread_signal(tid={}, sig={}) are not supported without the 'os' feature", ctx.data().pid(), ctx.data().tid(), tid, sig); + Ok(__WASI_ENOTSUP) +} + /// ### `proc_raise()` /// Send a signal to the process of the calling thread. /// Note: This is similar to `raise` in POSIX. /// Inputs: /// - `Signal` /// Signal to be raised for this process -pub fn proc_raise(ctx: FunctionEnvMut<'_, WasiEnv>, sig: Signal) -> Errno { - debug!("wasi::proc_raise"); - unimplemented!("wasi::proc_raise") +pub fn proc_raise( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sig: __wasi_signal_t +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::proc_raise (sig={})", ctx.data().pid(), ctx.data().tid(), sig); + let env = ctx.data(); + env.process.signal_process(sig); + env.clone().yield_now_with_signals(&mut ctx)?; + Ok(__WASI_ESUCCESS) +} + +/// ### `proc_raise()` +/// Send a signal to the process of the calling thread. +/// Note: This is similar to `raise` in POSIX. +/// Inputs: +/// - `__wasi_signal_t` +/// Signal to be raised for this process +pub fn proc_raise_interval( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sig: __wasi_signal_t, + interval: __wasi_timestamp_t, + repeat: __wasi_bool_t, +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::proc_raise_interval (sig={})", ctx.data().pid(), ctx.data().tid(), sig); + let env = ctx.data(); + let interval = match interval { + 0 => None, + a => Some(Duration::from_millis(a)) + }; + let repeat = match repeat { + __WASI_BOOL_TRUE => true, + _ => false + }; + env.process.signal_interval(sig, interval, repeat); + env.clone().yield_now_with_signals(&mut ctx)?; + + Ok(__WASI_ESUCCESS) } /// ### `sched_yield()` /// Yields execution of the thread -pub fn sched_yield(ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { - trace!("wasi::sched_yield"); +pub fn sched_yield( + mut ctx: FunctionEnvMut<'_, WasiEnv> +) -> Result<__wasi_errno_t, WasiError> { + //trace!("wasi[{}:{}]::sched_yield", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); - env.yield_now()?; - Ok(Errno::Success) + env.clone().yield_now_with_signals(&mut ctx)?; + Ok(__WASI_ESUCCESS) } -/// ### `random_get()` -/// Fill buffer with high-quality random data. This function may be slow and block -/// Inputs: -/// - `void *buf` -/// A pointer to a buffer where the random bytes will be written -/// - `size_t buf_len` -/// The number of bytes that will be written -pub fn random_get( - ctx: FunctionEnvMut<'_, WasiEnv>, +fn get_stack_base( + mut ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> u64 { + ctx.data().stack_base +} + +fn get_stack_start( + mut ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> u64 { + ctx.data().stack_start +} + +fn get_memory_stack_pointer( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result +{ + // Get the current value of the stack pointer (which we will use + // to save all of the stack) + let stack_base = get_stack_base(ctx); + let stack_pointer = if let Some(stack_pointer) = ctx.data().inner().stack_pointer.clone() { + match stack_pointer.get(ctx) { + Value::I32(a) => a as u64, + Value::I64(a) => a as u64, + _ => stack_base + } + } else { + return Err(format!("failed to save stack: not exported __stack_pointer global")); + }; + Ok(stack_pointer) +} + +fn get_memory_stack_offset( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result +{ + let stack_base = get_stack_base(ctx); + let stack_pointer = get_memory_stack_pointer(ctx)?; + Ok(stack_base - stack_pointer) +} + +fn set_memory_stack_offset( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + offset: u64, +) -> Result<(), String> +{ + // Sets the stack pointer + let stack_base = get_stack_base(ctx); + let stack_pointer = stack_base - offset; + if let Some(stack_pointer_ptr) = ctx.data().inner().stack_pointer.clone() { + match stack_pointer_ptr.get(ctx) { + Value::I32(_) => { + stack_pointer_ptr.set(ctx, Value::I32(stack_pointer as i32)); + }, + Value::I64(_) => { + stack_pointer_ptr.set(ctx, Value::I64(stack_pointer as i64)); + }, + _ => { + return Err(format!("failed to save stack: __stack_pointer global is of an unknown type")); + } + } + } else { + return Err(format!("failed to save stack: not exported __stack_pointer global")); + } + Ok(()) +} + +#[allow(dead_code)] +fn get_memory_stack( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result { + // Get the current value of the stack pointer (which we will use + // to save all of the stack) + let stack_base = get_stack_base(ctx); + let stack_pointer = if let Some(stack_pointer) = ctx.data().inner().stack_pointer.clone() { + match stack_pointer.get(ctx) { + Value::I32(a) => a as u64, + Value::I64(a) => a as u64, + _ => stack_base + } + } else { + return Err(format!("failed to save stack: not exported __stack_pointer global")); + }; + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let stack_offset = env.stack_base - stack_pointer; + + // Read the memory stack into a vector + let memory_stack_ptr = WasmPtr::::new(stack_pointer.try_into().map_err(|_| { + format!("failed to save stack: stack pointer overflow") + })?); + + memory_stack_ptr.slice(&memory, stack_offset.try_into().map_err(|_| { + format!("failed to save stack: stack pointer overflow") + })?) + .and_then(|memory_stack| { + memory_stack.read_to_bytes() + }) + .map_err(|err| { + format!("failed to read stack: {}", err) + }) +} + +#[allow(dead_code)] +fn set_memory_stack( + mut ctx: &mut FunctionEnvMut<'_, WasiEnv>, + stack: Bytes +) -> Result<(), String> { + // First we restore the memory stack + let stack_base = get_stack_base(ctx); + let stack_offset = stack.len() as u64; + let stack_pointer = stack_base - stack_offset; + let stack_ptr = WasmPtr::::new(stack_pointer.try_into().map_err(|_| { + format!("failed to restore stack: stack pointer overflow") + })?); + + let env = ctx.data(); + let memory = env.memory_view(&ctx); + stack_ptr.slice(&memory, stack_offset.try_into().map_err(|_| { + format!("failed to restore stack: stack pointer overflow") + })?) + .and_then(|memory_stack| { + memory_stack.write_slice(&stack[..]) + }) + .map_err(|err| { + format!("failed to write stack: {}", err) + })?; + + // Set the stack pointer itself and return + set_memory_stack_offset(ctx, stack_offset)?; + Ok(()) +} + +#[must_use = "you must return the result immediately so the stack can unwind"] +fn unwind( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + callback: F, +) -> Result<__wasi_errno_t, WasiError> +where F: FnOnce(FunctionEnvMut<'_, WasiEnv>, BytesMut, BytesMut) -> OnCalledAction + Send + Sync + 'static, +{ + // Get the current stack pointer (this will be used to determine the + // upper limit of stack space remaining to unwind into) + let memory_stack = match get_memory_stack::(&mut ctx) { + Ok(a) => a, + Err(err) => { + warn!("unable to get the memory stack - {}", err); + return Err(WasiError::Exit(__WASI_EFAULT as __wasi_exitcode_t)); + } + }; + + // Perform a check to see if we have enough room + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + // Write the addresses to the start of the stack space + let unwind_pointer: u64 = wasi_try_ok!(env.stack_start.try_into().map_err(|_| __WASI_EOVERFLOW)); + let unwind_data_start = unwind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); + let unwind_data = __wasi_asyncify_t:: { + start: wasi_try_ok!(unwind_data_start.try_into().map_err(|_| __WASI_EOVERFLOW)), + end: wasi_try_ok!(env.stack_base.try_into().map_err(|_| __WASI_EOVERFLOW)), + }; + let unwind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new( + wasi_try_ok!(unwind_pointer.try_into().map_err(|_| __WASI_EOVERFLOW)) + ); + wasi_try_mem_ok!( + unwind_data_ptr.write(&memory, unwind_data) + ); + + // Invoke the callback that will prepare to unwind + // We need to start unwinding the stack + let asyncify_data = wasi_try_ok!(unwind_pointer.try_into().map_err(|_| __WASI_EOVERFLOW)); + if let Some(asyncify_start_unwind) = env.inner().asyncify_start_unwind.clone() { + asyncify_start_unwind.call(&mut ctx, asyncify_data); + } else { + warn!("failed to unwind the stack because the asyncify_start_rewind export is missing"); + return Err(WasiError::Exit(128)); + } + + // Set callback that will be invoked when this process finishes + let env = ctx.data(); + let unwind_stack_begin: u64 = unwind_data.start.into(); + let unwind_space = env.stack_base - env.stack_start; + let func = ctx.as_ref(); + trace!("wasi[{}:{}]::unwinding (memory_stack_size={} unwind_space={})", ctx.data().pid(), ctx.data().tid(), memory_stack.len(), unwind_space); + ctx.as_store_mut().on_called(move |mut store| { + let mut ctx = func.into_mut(&mut store); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + let unwind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new( + unwind_pointer.try_into().map_err(|_| __WASI_EOVERFLOW).unwrap() + ); + let unwind_data_result = unwind_data_ptr.read(&memory).unwrap(); + let unwind_stack_finish: u64 = unwind_data_result.start.into(); + let unwind_size = unwind_stack_finish - unwind_stack_begin; + trace!("wasi[{}:{}]::unwound (memory_stack_size={} unwind_size={})", ctx.data().pid(), ctx.data().tid(), memory_stack.len(), unwind_size); + + // Read the memory stack into a vector + let unwind_stack_ptr = WasmPtr::::new(unwind_stack_begin.try_into().map_err(|_| { + format!("failed to save stack: stack pointer overflow") + })?); + let unwind_stack = unwind_stack_ptr.slice(&memory, unwind_size.try_into().map_err(|_| { + format!("failed to save stack: stack pointer overflow") + })?) + .and_then(|memory_stack| { + memory_stack.read_to_bytes() + }) + .map_err(|err| { + format!("failed to read stack: {}", err) + })?; + + // Notify asyncify that we are no longer unwinding + if let Some(asyncify_stop_unwind) = env.inner().asyncify_stop_unwind.clone() { + asyncify_stop_unwind.call(&mut ctx); + } else { + warn!("failed to unwind the stack because the asyncify_start_rewind export is missing"); + return Ok(OnCalledAction::Finish); + } + + Ok( + callback(ctx, memory_stack, unwind_stack) + ) + }); + + // We need to exit the function so that it can unwind and then invoke the callback + Ok(__WASI_ESUCCESS) +} + +#[must_use = "the action must be passed to the call loop"] +fn rewind( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + memory_stack: Bytes, + rewind_stack: Bytes, + store_data: Bytes, +) -> __wasi_errno_t +{ + trace!("wasi[{}:{}]::rewinding (memory_stack_size={}, rewind_size={}, store_data={})", ctx.data().pid(), ctx.data().tid(), memory_stack.len(), rewind_stack.len(), store_data.len()); + + // Store the memory stack so that it can be restored later + super::REWIND.with(|cell| cell.replace(Some(memory_stack))); + + // Deserialize the store data back into a snapshot + let store_snapshot = match StoreSnapshot::deserialize(&store_data[..]) { + Ok(a) => a, + Err(err) => { + warn!("snapshot restore failed - the store snapshot could not be deserialized"); + return __WASI_EFAULT as __wasi_errno_t; + } + }; + ctx.as_store_mut().restore_snapshot(&store_snapshot); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + // Write the addresses to the start of the stack space + let rewind_pointer: u64 = wasi_try!(env.stack_start.try_into().map_err(|_| __WASI_EOVERFLOW)); + let rewind_data_start = rewind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); + let rewind_data_end = rewind_data_start + (rewind_stack.len() as u64); + if rewind_data_end > env.stack_base { + warn!("attempting to rewind a stack bigger than the allocated stack space ({} > {})", rewind_data_end, env.stack_base); + return __WASI_EOVERFLOW; + } + let rewind_data = __wasi_asyncify_t:: { + start: wasi_try!(rewind_data_end.try_into().map_err(|_| __WASI_EOVERFLOW)), + end: wasi_try!(env.stack_base.try_into().map_err(|_| __WASI_EOVERFLOW)), + }; + let rewind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new( + wasi_try!(rewind_pointer.try_into().map_err(|_| __WASI_EOVERFLOW)) + ); + wasi_try_mem!( + rewind_data_ptr.write(&memory, rewind_data) + ); + + // Copy the data to the address + let rewind_stack_ptr = WasmPtr::::new(wasi_try!(rewind_data_start.try_into().map_err(|_| __WASI_EOVERFLOW))); + wasi_try_mem!(rewind_stack_ptr.slice(&memory, wasi_try!(rewind_stack.len().try_into().map_err(|_| __WASI_EOVERFLOW))) + .and_then(|stack| { + stack.write_slice(&rewind_stack[..]) + })); + + // Invoke the callback that will prepare to rewind + let asyncify_data = wasi_try!(rewind_pointer.try_into().map_err(|_| __WASI_EOVERFLOW)); + if let Some(asyncify_start_rewind) = env.inner().asyncify_start_rewind.clone() { + asyncify_start_rewind.call(&mut ctx, asyncify_data); + } else { + warn!("failed to rewind the stack because the asyncify_start_rewind export is missing"); + return __WASI_EFAULT; + } + + __WASI_ESUCCESS +} + +fn handle_rewind( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> bool { + // If the stack has been restored + if let Some(memory_stack) = super::REWIND.with(|cell| cell.borrow_mut().take()) + { + // Notify asyncify that we are no longer rewinding + let env = ctx.data(); + if let Some(asyncify_stop_rewind) = env.inner().asyncify_stop_rewind.clone() { + asyncify_stop_rewind.call(ctx); + } + + // Restore the memory stack + set_memory_stack::(ctx, memory_stack); + true + } else { + false + } +} + +/// ### `stack_checkpoint()` +/// Creates a snapshot of the current stack which allows it to be restored +/// later using its stack hash. +pub fn stack_checkpoint( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + snapshot_ptr: WasmPtr<__wasi_stack_snaphost_t, M>, + ret_val: WasmPtr<__wasi_longsize_t, M>, +) -> Result<__wasi_errno_t, WasiError> +{ + // If we were just restored then we need to return the value instead + if handle_rewind::(&mut ctx) { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let ret_val = wasi_try_mem_ok!( + ret_val.read(&memory) + ); + trace!("wasi[{}:{}]::stack_checkpoint - restored - (ret={})", ctx.data().pid(), ctx.data().tid(), ret_val); + return Ok(__WASI_ESUCCESS); + } + trace!("wasi[{}:{}]::stack_checkpoint - capturing", ctx.data().pid(), ctx.data().tid()); + + // Set the return value that we will give back to + // indicate we are a normal function call that has not yet + // been restored + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!( + ret_val.write(&memory, 0) + ); + + // Pass some offsets to the unwind function + let ret_offset = ret_val.offset(); + let snapshot_offset = snapshot_ptr.offset(); + let secret = env.state().secret.clone(); + + // We clear the target memory location before we grab the stack so that + // it correctly hashes + if let Err(err) = snapshot_ptr.write(&memory, + __wasi_stack_snaphost_t { + hash: 0, + user: 0, + }) + { + warn!("wasi[{}:{}]::failed to write to stack snapshot return variable - {}", env.pid(), env.tid(), err); + } + + // Perform the unwind action + unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| + { + // Grab all the globals and serialize them + let env = ctx.data(); + let store_data = ctx.as_store_ref().save_snapshot().serialize(); + let store_data = Bytes::from(store_data); + let mut memory_stack_corrected = memory_stack.clone(); + + // We compute the hash again for two reasons... integrity so if there + // is a long jump that goes to the wrong place it will fail gracefully. + // and security so that the stack can not be used to attempt to break + // out of the sandbox + let hash = { + use sha2::{Sha256, Digest}; + let mut hasher = Sha256::new(); + hasher.update(&secret[..]); + hasher.update(&memory_stack[..]); + hasher.update(&rewind_stack[..]); + hasher.update(&store_data[..]); + let hash: [u8; 16] = hasher.finalize()[..16].try_into().unwrap(); + u128::from_le_bytes(hash) + }; + + // Build a stack snapshot + let snapshot = __wasi_stack_snaphost_t { + hash, + user: ret_offset.into(), + }; + + // Get a reference directly to the bytes of snapshot + let val_bytes = unsafe { + let p = &snapshot; + ::std::slice::from_raw_parts( + (p as *const __wasi_stack_snaphost_t) as *const u8, + ::std::mem::size_of::<__wasi_stack_snaphost_t>(), + ) + }; + + // The snapshot may itself reside on the stack (which means we + // need to update the memory stack rather than write to the memory + // as otherwise the rewind will wipe out the structure) + // This correct memory stack is stored as well for validation purposes + let mut memory_stack_corrected = memory_stack.clone(); + { + let snapshot_offset: u64 = snapshot_offset.into(); + if snapshot_offset >= env.stack_start && snapshot_offset < env.stack_base + { + // Make sure its within the "active" part of the memory stack + // (note - the area being written to might not go past the memory pointer) + let offset = env.stack_base - snapshot_offset; + if (offset as usize) < memory_stack_corrected.len() { + let left = memory_stack_corrected.len() - (offset as usize); + let end = offset + (val_bytes.len().min(left) as u64); + if end as usize <= memory_stack_corrected.len() { + let pstart = memory_stack_corrected.len() - offset as usize; + let pend = pstart + val_bytes.len(); + let pbytes = &mut memory_stack_corrected[pstart..pend]; + pbytes.clone_from_slice(&val_bytes); + } + } + } + } + + /// Add a snapshot to the stack + ctx.data().thread.add_snapshot( + &memory_stack[..], + &memory_stack_corrected[..], + hash, + &rewind_stack[..], + &store_data[..] + ); + trace!("wasi[{}:{}]::stack_recorded (hash={}, user={})", ctx.data().pid(), ctx.data().tid(), snapshot.hash, snapshot.user); + + // Save the stack snapshot + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let snapshot_ptr: WasmPtr<__wasi_stack_snaphost_t, M> = WasmPtr::new(snapshot_offset); + if let Err(err) = snapshot_ptr.write(&memory, snapshot) { + warn!("wasi[{}:{}]::failed checkpoint - could not save stack snapshot - {}", env.pid(), env.tid(), err); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } + + // Rewind the stack and carry on + let pid = ctx.data().pid(); + let tid = ctx.data().tid(); + match rewind::(ctx, memory_stack_corrected.freeze(), rewind_stack.freeze(), store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("wasi[{}:{}]::failed checkpoint - could not rewind the stack - errno={}", pid, tid, err); + OnCalledAction::Trap(Box::new(WasiError::Exit(err as u32))) + } + } + }) +} + +/// ### `stack_restore()` +/// Restores the current stack to a previous stack described by its +/// stack hash. +/// +/// ## Parameters +/// +/// * `snapshot_ptr` - Contains a previously made snapshot +pub fn stack_restore( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + snapshot_ptr: WasmPtr<__wasi_stack_snaphost_t, M>, + mut val: __wasi_longsize_t, +) -> Result<(), WasiError> { + // Read the snapshot from the stack + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let snapshot = match snapshot_ptr.read(&memory) { + Ok(a) => { + trace!("wasi[{}:{}]::stack_restore (with_ret={}, hash={}, user={})", ctx.data().pid(), ctx.data().tid(), val, a.hash, a.user); + a + }, + Err(err) => { + warn!("wasi[{}:{}]::stack_restore - failed to read stack snapshot - {}", ctx.data().pid(), ctx.data().tid(), err); + return Err(WasiError::Exit(128)); + } + }; + + // Perform the unwind action + unwind::(ctx, move |mut ctx, _, _| + { + // Let the stack (or fail trying!) + let env = ctx.data(); + if let Some((mut memory_stack, rewind_stack, store_data)) = env.thread.get_snapshot(snapshot.hash) + { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + // If the return value offset is within the memory stack then we need + // to update it here rather than in the real memory + let ret_val_offset = snapshot.user; + if ret_val_offset >= env.stack_start && ret_val_offset < env.stack_base + { + // Make sure its within the "active" part of the memory stack + let val_bytes = val.to_ne_bytes(); + let offset = env.stack_base - ret_val_offset; + let end = offset + (val_bytes.len() as u64); + if end as usize > memory_stack.len() { + warn!("wasi[{}:{}]::snapshot stack restore failed - the return value is outside of the active part of the memory stack ({} vs {}) - {} - {}", env.pid(), env.tid(), offset, memory_stack.len(), ret_val_offset, end); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } else { + // Update the memory stack with the new return value + let pstart = memory_stack.len() - offset as usize; + let pend = pstart + val_bytes.len(); + let pbytes = &mut memory_stack[pstart..pend]; + pbytes.clone_from_slice(&val_bytes); + } + } else { + let err = snapshot.user + .try_into() + .map_err(|_| __WASI_EOVERFLOW) + .map(|a| WasmPtr::<__wasi_longsize_t, M>::new(a)) + .map(|a| a.write(&memory, val) + .map(|_| __WASI_ESUCCESS) + .unwrap_or(__WASI_EFAULT)) + .unwrap_or_else(|a| a); + if err != __WASI_ESUCCESS { + warn!("wasi[{}:{}]::snapshot stack restore failed - the return value can not be written too - {}", env.pid(), env.tid(), err); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } + } + + // Rewind the stack - after this point we must immediately return + // so that the execution can end here and continue elsewhere. + let pid = ctx.data().pid(); + let tid = ctx.data().tid(); + match rewind::(ctx, memory_stack.freeze(), rewind_stack, store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("wasi[{}:{}]::failed to rewind the stack - errno={}", pid, tid, err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + } + } else { + warn!("wasi[{}:{}]::snapshot stack restore failed - the snapshot can not be found and hence restored (hash={})", env.pid(), env.tid(), snapshot.hash); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + }); + + // Return so the stack can be unwound (which will then + // be rewound again but with a different location) + Ok(()) +} + +/// ### `proc_signal()` +/// Sends a signal to a child process +/// +/// ## Parameters +/// +/// * `pid` - Handle of the child process to wait on +/// * `sig` - Signal to send the child process +#[cfg(feature = "os")] +pub fn proc_signal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + pid: __wasi_pid_t, + sig: __wasi_signal_t, +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::proc_signal(pid={}, sig={})", ctx.data().pid(), ctx.data().tid(), pid, sig); + + let process = { + let pid: WasiProcessId = pid.into(); + ctx.data().process.compute.get_process(pid) + }; + if let Some(process) = process { + process.signal_process(sig); + } + + let env = ctx.data(); + env.clone().yield_now_with_signals(&mut ctx)?; + + Ok(__WASI_ESUCCESS) +} + +#[cfg(not(feature = "os"))] +pub fn proc_signal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + pid: __wasi_pid_t, + sig: __wasi_signal_t, +) -> Result<__wasi_errno_t, WasiError> { + warn!("wasi[{}:{}]::proc_signal(pid={}, sig={}) is not supported without 'os' feature", ctx.data().pid(), ctx.data().tid(), pid, sig); + Ok(__WASI_ENOTSUP) +} + +/// ### `random_get()` +/// Fill buffer with high-quality random data. This function may be slow and block +/// Inputs: +/// - `void *buf` +/// A pointer to a buffer where the random bytes will be written +/// - `size_t buf_len` +/// The number of bytes that will be written +pub fn random_get( + ctx: FunctionEnvMut<'_, WasiEnv>, buf: WasmPtr, buf_len: M::Offset, -) -> Errno { - trace!("wasi::random_get buf_len: {}", buf_len); +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::random_get(buf_len={})", ctx.data().pid(), ctx.data().tid(), buf_len); let env = ctx.data(); let memory = env.memory_view(&ctx); let buf_len64: u64 = buf_len.into(); @@ -3387,9 +4522,9 @@ pub fn random_get( /// Retrieves the current state of the TTY pub fn tty_get( ctx: FunctionEnvMut<'_, WasiEnv>, - tty_state: WasmPtr, -) -> Errno { - debug!("wasi::tty_stdin"); + tty_state: WasmPtr<__wasi_tty_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::tty_get", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let state = env.runtime.tty_get(); @@ -3415,23 +4550,47 @@ pub fn tty_get( /// Updates the properties of the rect pub fn tty_set( ctx: FunctionEnvMut<'_, WasiEnv>, - tty_state: WasmPtr, -) -> Errno { - debug!("wasi::tty_set"); - + tty_state: WasmPtr<__wasi_tty_t, M>, +) -> __wasi_errno_t { let env = ctx.data(); let memory = env.memory_view(&ctx); let state = wasi_try_mem!(tty_state.read(&memory)); + let echo = match state.echo { + __WASI_BOOL_FALSE => false, + __WASI_BOOL_TRUE => true, + _ => return __WASI_EINVAL, + }; + let line_buffered = match state.line_buffered { + __WASI_BOOL_FALSE => false, + __WASI_BOOL_TRUE => true, + _ => return __WASI_EINVAL, + }; + let line_feeds = true; + debug!("wasi[{}:{}]::tty_set(echo={}, line_buffered={}, line_feeds={})", ctx.data().pid(), ctx.data().tid(), echo, line_buffered, line_feeds); + let state = super::runtime::WasiTtyState { cols: state.cols, rows: state.rows, width: state.width, height: state.height, - stdin_tty: state.stdin_tty, - stdout_tty: state.stdout_tty, - stderr_tty: state.stderr_tty, - echo: state.echo, - line_buffered: state.line_buffered, + stdin_tty: match state.stdin_tty { + __WASI_BOOL_FALSE => false, + __WASI_BOOL_TRUE => true, + _ => return __WASI_EINVAL, + }, + stdout_tty: match state.stdout_tty { + __WASI_BOOL_FALSE => false, + __WASI_BOOL_TRUE => true, + _ => return __WASI_EINVAL, + }, + stderr_tty: match state.stderr_tty { + __WASI_BOOL_FALSE => false, + __WASI_BOOL_TRUE => true, + _ => return __WASI_EINVAL, + }, + echo, + line_buffered, + line_feeds }; env.runtime.tty_set(state); @@ -3447,14 +4606,15 @@ pub fn getcwd( ctx: FunctionEnvMut<'_, WasiEnv>, path: WasmPtr, path_len: WasmPtr, -) -> Errno { - debug!("wasi::getcwd"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::getcwd", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, mut state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); let (_, cur_dir) = wasi_try!(state .fs .get_current_dir(inodes.deref_mut(), crate::VIRTUAL_ROOT_FD,)); + trace!("wasi[{}:{}]::getcwd(current_dir={})", ctx.data().pid(), ctx.data().tid(), cur_dir); let max_path_len = wasi_try_mem!(path_len.read(&memory)); let path_slice = wasi_try_mem!(path.slice(&memory, max_path_len)); @@ -3488,16 +4648,129 @@ pub fn chdir( ctx: FunctionEnvMut<'_, WasiEnv>, path: WasmPtr, path_len: M::Offset, -) -> Errno { - debug!("wasi::chdir"); +) -> __wasi_errno_t { let env = ctx.data(); let (memory, mut state) = env.get_memory_and_wasi_state(&ctx, 0); let path = unsafe { get_input_str!(&memory, path, path_len) }; + debug!("wasi[{}:{}]::chdir [{}]", ctx.data().pid(), ctx.data().tid(), path); + + // Check if the directory exists + if state.fs.root_fs.read_dir(Path::new(path.as_str())).is_err() { + return __WASI_ENOENT; + } state.fs.set_current_dir(path.as_str()); Errno::Success } +/// ### `callback_spawn()` +/// Sets the callback to invoke upon spawning of new threads +/// +/// ### Parameters +/// +/// * `name` - Name of the function that will be invoked +pub fn callback_thread( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, +) -> Result<(), MemoryAccessError> { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let name = unsafe { name.read_utf8_string(&memory, name_len)? }; + debug!("wasi[{}:{}]::callback_spawn (name={})", ctx.data().pid(), ctx.data().tid(), name); + + let funct = env.inner().exports + .get_typed_function(&ctx, &name).ok(); + + ctx.data_mut().inner_mut().thread_spawn = funct; + Ok(()) +} + +/// ### `callback_signal()` +/// Sets the callback to invoke signals +/// +/// ### Parameters +/// +/// * `name` - Name of the function that will be invoked +pub fn callback_signal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, +) -> Result<(), WasiError> { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let name = unsafe { + match name.read_utf8_string(&memory, name_len) { + Ok(a) => a, + Err(err) => { + warn!("failed to access memory that holds the name of the signal callback: {}", err); + return Ok(()); + } + } + }; + + let funct = env.inner().exports + .get_typed_function(&ctx, &name).ok(); + trace!("wasi[{}:{}]::callback_signal (name={}, found={})", ctx.data().pid(), ctx.data().tid(), name, funct.is_some()); + + { + let inner = ctx.data_mut().inner_mut(); + inner.signal = funct; + inner.signal_set = true; + } + + let env = ctx.data(); + env.clone().yield_now_with_signals(&mut ctx)?; + + Ok(()) +} + +/// ### `callback_reactor()` +/// Sets the callback to invoke for reactors +/// +/// ### Parameters +/// +/// * `name` - Name of the function that will be invoked +pub fn callback_reactor( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, +) -> Result<(), MemoryAccessError> { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let name = unsafe { name.read_utf8_string(&memory, name_len)? }; + debug!("wasi[{}:{}]::callback_reactor (name={})", ctx.data().pid(), ctx.data().tid(), name); + + let funct = env.inner().exports + .get_typed_function(&ctx, &name).ok(); + + ctx.data_mut().inner_mut().react = funct; + Ok(()) +} + +/// ### `callback_thread_local_destroy()` +/// Sets the callback to invoke for the destruction of thread local variables +/// +/// ### Parameters +/// +/// * `name` - Name of the function that will be invoked +pub fn callback_thread_local_destroy( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, +) -> Result<(), MemoryAccessError> { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let name = unsafe { name.read_utf8_string(&memory, name_len)? }; + debug!("wasi[{}:{}]::callback_thread_local_destroy (name={})", ctx.data().pid(), ctx.data().tid(), name); + + let funct = env.inner().exports + .get_typed_function(&ctx, &name).ok(); + + ctx.data_mut().inner_mut().thread_local_destroy = funct; + Ok(()) +} + /// ### `thread_spawn()` /// Creates a new thread by spawning that shares the same /// memory address space, file handles and main event loops. @@ -3517,200 +4790,1343 @@ pub fn chdir( /// Returns the thread index of the newly created thread /// (indices always start from zero) pub fn thread_spawn( - ctx: FunctionEnvMut<'_, WasiEnv>, - method: WasmPtr, - method_len: M::Offset, + mut ctx: FunctionEnvMut<'_, WasiEnv>, user_data: u64, - reactor: Bool, - ret_tid: WasmPtr, -) -> Errno { - debug!("wasi::thread_spawn"); + stack_base: u64, + stack_start: u64, + reactor: __wasi_bool_t, + ret_tid: WasmPtr<__wasi_tid_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::thread_spawn (reactor={}, thread_id={}, stack_base={}, caller_id={})", ctx.data().pid(), ctx.data().tid(), reactor, ctx.data().thread.tid().raw(), stack_base, current_caller_id().raw()); + + // Now we use the environment and memory references let env = ctx.data(); let memory = env.memory_view(&ctx); - let method = unsafe { get_input_str!(&memory, method, method_len) }; + let runtime = env.runtime.clone(); + let tasks = env.tasks.clone(); + + // Create the handle that represents this thread + let mut thread_handle = env.process.new_thread(); + let thread_id: __wasi_tid_t = thread_handle.id().into(); + + // We need a copy of the process memory and a packaged store in order to + // launch threads and reactors + let thread_memory = wasi_try!( + ctx.data() + .memory() + .try_clone(&ctx) + .ok_or_else(|| { + error!("thread failed - the memory could not be cloned"); + __WASI_ENOTCAPABLE + }) + ); + #[cfg(feature = "compiler")] + let engine = ctx.as_store_ref().engine().clone(); + + // Build a new store that will be passed to the thread + #[cfg(feature = "compiler")] + let mut store = Store::new(engine); + #[cfg(not(feature = "compiler"))] + let mut store = Store::default(); + + // This function takes in memory and a store and creates a context that + // can be used to call back into the process + let create_ctx = { + let state = env.state.clone(); + let wasi_env = env.clone(); + let thread = thread_handle.as_thread(); + move |mut store: Store, module: Module, memory: VMMemory| + { + // We need to reconstruct some things + let module = module.clone(); + let memory = Memory::new_from_existing(&mut store, memory); - // Load the callback function - if method.as_str() != "_thread_start" { - return Errno::Notcapable; - }; - /* - let funct = unsafe { - if env.thread_start_ref().is_none() { - return Errno::Addrnotavail; + // Build the context object and import the memory + let mut ctx = WasiFunctionEnv::new(&mut store, wasi_env.clone()); + { + let env = ctx.data_mut(&mut store); + env.thread = thread.clone(); + env.stack_base = stack_base; + env.stack_start = stack_start; + } + + let mut import_object = import_object_for_all_wasi_versions(&mut store, &ctx.env); + import_object.define("env", "memory", memory.clone()); + + let instance = match Instance::new(&mut store, &module, &import_object) { + Ok(a) => a, + Err(err) => { + error!("thread failed - create instance failed: {}", err); + return Err(__WASI_ENOEXEC as u32); + } + }; + + // Set the current thread ID + ctx.data_mut(&mut store).inner = Some( + WasiEnvInner::new(module, memory, &store, &instance) + ); + trace!("threading: new context created for thread_id = {}", thread.tid().raw()); + Ok(WasiThreadContext { + ctx, + store: RefCell::new(store) + }) } - env.thread_start_ref_unchecked() }; - */ - let reactor = match reactor { - Bool::False => false, - Bool::True => true, - _ => return Errno::Inval, + // This function calls into the module + let call_module = move |ctx: &WasiFunctionEnv, store: &mut Store| + { + // We either call the reactor callback or the thread spawn callback + //trace!("threading: invoking thread callback (reactor={})", reactor); + let spawn = match reactor { + __WASI_BOOL_FALSE => ctx.data(&store).inner().thread_spawn.clone().unwrap(), + __WASI_BOOL_TRUE => ctx.data(&store).inner().react.clone().unwrap(), + _ => { + debug!("thread failed - failed as the reactor type is not value"); + return __WASI_ENOEXEC as u32; + } + }; + + let user_data_low: u32 = (user_data & 0xFFFFFFFF) as u32; + let user_data_high: u32 = (user_data >> 32) as u32; + + let mut ret = __WASI_ESUCCESS; + if let Err(err) = spawn.call(store, user_data_low as i32, user_data_high as i32) { + debug!("thread failed - start: {}", err); + ret = __WASI_ENOEXEC; + } + //trace!("threading: thread callback finished (reactor={}, ret={})", reactor, ret); + + // If we are NOT a reactor then we will only run once and need to clean up + if reactor == __WASI_BOOL_FALSE + { + // Clean up the environment + ctx.cleanup(store); + } + + // Return the result + ret as u32 + }; + + // This next function gets a context for the local thread and then + // calls into the process + let mut execute_module = { + let state = env.state.clone(); + move |store: &mut Option, module: Module, memory: &mut Option| + { + // We capture the thread handle here, it is used to notify + // anyone that is interested when this thread has terminated + let _captured_handle = Box::new(&mut thread_handle); + + // Given that it is not safe to assume this delegate will run on the + // same thread we need to capture a simple process that will create + // context objects on demand and reuse them + let caller_id = current_caller_id(); + + // We loop because read locks are held while functions run which need + // to be relocked in the case of a miss hit. + loop { + let thread = { + let guard = state.threading.read().unwrap(); + guard.thread_ctx.get(&caller_id).map(|a| a.clone()) + }; + if let Some(thread) = thread + { + let mut store = thread.store.borrow_mut(); + let ret = call_module(&thread.ctx, store.deref_mut()); + return ret; + } + + // Otherwise we need to create a new context under a write lock + debug!("encountered a new caller (ref={}) - creating WASM execution context...", caller_id.raw()); + + // We can only create the context once per thread + let memory = match memory.take() { + Some(m) => m, + None => { + debug!("thread failed - memory can only be consumed once per context creation"); + return __WASI_ENOEXEC as u32; + } + }; + let store = match store.take() { + Some(s) => s, + None => { + debug!("thread failed - store can only be consumed once per context creation"); + return __WASI_ENOEXEC as u32; + } + }; + + // Now create the context and hook it up + let mut guard = state.threading.write().unwrap(); + let ctx = match create_ctx(store, module.clone(), memory) { + Ok(c) => c, + Err(err) => { + return err; + } + }; + guard.thread_ctx.insert(caller_id, Arc::new(ctx)); + } + } + }; + + // If we are a reactor then instead of launching the thread now + // we store it in the state machine and only launch it whenever + // work arrives that needs to be processed + match reactor { + __WASI_BOOL_TRUE => { + warn!("thread failed - reactors are not currently supported"); + return __WASI_ENOTCAPABLE; + }, + __WASI_BOOL_FALSE => { + // If the process does not export a thread spawn function then obviously + // we can't spawn a background thread + if env.inner().thread_spawn.is_none() { + warn!("thread failed - the program does not export a _start_thread function"); + return __WASI_ENOTCAPABLE; + } + + // Now spawn a thread + trace!("threading: spawning background thread"); + let thread_module = env.inner().module.clone(); + wasi_try!(tasks + .task_wasm(Box::new(move |store, module, thread_memory| { + let mut thread_memory = thread_memory; + let mut store = Some(store); + execute_module(&mut store, module, &mut thread_memory); + }), + store, + thread_module, + crate::runtime::SpawnType::NewThread(thread_memory) + ) + .map_err(|err| { + let err: __wasi_errno_t = err.into(); + err + }) + ); + }, + _ => { + warn!("thread failed - invalid reactor parameter value"); + return __WASI_ENOTCAPABLE; + } + } + + // Success + let memory = ctx.data().memory_view(&ctx); + wasi_try_mem!(ret_tid.write(&memory, thread_id)); + __WASI_ESUCCESS +} + +/// ### `thread_local_create()` +/// Create a thread local variable +/// If The web assembly process exports function named '_thread_local_destroy' +/// then it will be invoked when the thread goes out of scope and dies. +/// +/// ## Parameters +/// +/// * `user_data` - User data that will be passed to the destructor +/// when the thread variable goes out of scope +pub fn thread_local_create( + ctx: FunctionEnvMut<'_, WasiEnv>, + user_data: u64, + ret_key: WasmPtr<__wasi_tl_key_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::thread_local_create (user_data={})", ctx.data().pid(), ctx.data().tid(), user_data); + let env = ctx.data(); + + let key = { + let mut inner = env.process.write(); + inner.thread_local_seed += 1; + let key = inner.thread_local_seed; + inner.thread_local_user_data.insert(key, user_data); + key + }; + + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_key.write(&memory, key)); + __WASI_ESUCCESS +} + +/// ### `thread_local_destroy()` +/// Destroys a thread local variable +/// +/// ## Parameters +/// +/// * `user_data` - User data that will be passed to the destructor +/// when the thread variable goes out of scope +/// * `key` - Thread key that was previously created +pub fn thread_local_destroy( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + key: __wasi_tl_key_t +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::thread_local_destroy (key={})", ctx.data().pid(), ctx.data().tid(), key); + let process = ctx.data().process.clone(); + let mut inner = process.write(); + if let Some(user_data) = inner.thread_local_user_data.remove(&key) { + if let Some(thread_local_destroy) = ctx.data().inner().thread_local_destroy.as_ref().map(|a| a.clone()) { + inner.thread_local + .iter() + .filter(|((_, k), _)| *k == key) + .for_each(|((_, _), val)| { + let user_data_low: u32 = (user_data & 0xFFFFFFFF) as u32; + let user_data_high: u32 = (user_data >> 32) as u32; + + let val_low: u32 = (val & 0xFFFFFFFF) as u32; + let val_high: u32 = (val >> 32) as u32; + + let _ = thread_local_destroy.call(&mut ctx, user_data_low as i32, user_data_high as i32, val_low as i32, val_high as i32); + }); + } + } + inner.thread_local.retain(|(_, k), _| *k != key); + __WASI_ESUCCESS +} + +/// ### `thread_local_set()` +/// Sets the value of a thread local variable +/// +/// ## Parameters +/// +/// * `key` - Thread key that this local variable will be associated with +/// * `val` - Value to be set for the thread local variable +pub fn thread_local_set( + ctx: FunctionEnvMut<'_, WasiEnv>, + key: __wasi_tl_key_t, + val: __wasi_tl_val_t +) -> __wasi_errno_t { + //trace!("wasi[{}:{}]::thread_local_set (key={}, val={})", ctx.data().pid(), ctx.data().tid(), key, val); + let env = ctx.data(); + + let current_thread = ctx.data().thread.tid(); + let mut inner = env.process.write(); + inner.thread_local.insert((current_thread, key), val); + __WASI_ESUCCESS +} + +/// ### `thread_local_get()` +/// Gets the value of a thread local variable +/// +/// ## Parameters +/// +/// * `key` - Thread key that this local variable that was previous set +pub fn thread_local_get( + ctx: FunctionEnvMut<'_, WasiEnv>, + key: __wasi_tl_key_t, + ret_val: WasmPtr<__wasi_tl_val_t, M>, +) -> __wasi_errno_t { + //trace!("wasi[{}:{}]::thread_local_get (key={})", ctx.data().pid(), ctx.data().tid(), key); + let env = ctx.data(); + + let val = { + let current_thread = ctx.data().thread.tid(); + let guard = env.process.read(); + guard.thread_local.get(&(current_thread, key)).map(|a| a.clone()) + }; + let val = val.unwrap_or_default(); + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_val.write(&memory, val)); + __WASI_ESUCCESS +} + +/// ### `thread_sleep()` +/// Sends the current thread to sleep for a period of time +/// +/// ## Parameters +/// +/// * `duration` - Amount of time that the thread should sleep +pub fn thread_sleep( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + duration: __wasi_timestamp_t, +) -> Result<__wasi_errno_t, WasiError> { + //trace!("wasi[{}:{}]::thread_sleep", ctx.data().pid(), ctx.data().tid()); + + let env = ctx.data(); + let duration = Duration::from_nanos(duration as u64); + env.clone().sleep(&mut ctx, duration)?; + Ok(__WASI_ESUCCESS) +} + +/// ### `thread_id()` +/// Returns the index of the current thread +/// (threads indices are sequencial from zero) +pub fn thread_id( + ctx: FunctionEnvMut<'_, WasiEnv>, + ret_tid: WasmPtr<__wasi_tid_t, M>, +) -> __wasi_errno_t { + //trace!("wasi[{}:{}]::thread_id", ctx.data().pid(), ctx.data().tid()); + + let env = ctx.data(); + let tid: __wasi_tid_t = env.thread.tid().into(); + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_tid.write(&memory, tid)); + Errno::Success +} + +/// ### `thread_join()` +/// Joins this thread with another thread, blocking this +/// one until the other finishes +/// +/// ## Parameters +/// +/// * `tid` - Handle of the thread to wait on +pub fn thread_join( + ctx: FunctionEnvMut<'_, WasiEnv>, + tid: __wasi_tid_t, +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::thread_join(tid={})", ctx.data().pid(), ctx.data().tid(), tid); + + let env = ctx.data(); + let tid: WasiThreadId = tid.into(); + let other_thread = env.process.get_thread(&tid); + if let Some(other_thread) = other_thread { + loop { + env.yield_now()?; + if other_thread.join(Duration::from_millis(50)).is_some() { + break; + } + } + Ok(Errno::Success) + } else { + Ok(Errno::Success) + } +} + +/// ### `thread_parallelism()` +/// Returns the available parallelism which is normally the +/// number of available cores that can run concurrently +pub fn thread_parallelism( + ctx: FunctionEnvMut<'_, WasiEnv>, + ret_parallelism: WasmPtr, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::thread_parallelism", ctx.data().pid(), ctx.data().tid()); + + let env = ctx.data(); + let parallelism = wasi_try!(env.tasks().thread_parallelism().map_err(|err| { + let err: __wasi_errno_t = err.into(); + err + })); + let parallelism: M::Offset = wasi_try!(parallelism.try_into().map_err(|_| Errno::Overflow)); + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_parallelism.write(&memory, parallelism)); + Errno::Success +} + +/// Wait for a futex_wake operation to wake us. +/// Returns with EINVAL if the futex doesn't hold the expected value. +/// Returns false on timeout, and true in all other cases. +/// +/// ## Parameters +/// +/// * `futex` - Memory location that holds the value that will be checked +/// * `expected` - Expected value that should be currently held at the memory location +/// * `timeout` - Timeout should the futex not be triggered in the allocated time +pub fn futex_wait( + ctx: FunctionEnvMut<'_, WasiEnv>, + futex_ptr: WasmPtr, + expected: u32, + timeout: WasmPtr<__wasi_option_timestamp_t, M>, + ret_woken: WasmPtr<__wasi_bool_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi[{}:{}]::futex_wait(offset={})", ctx.data().pid(), ctx.data().tid(), futex_ptr.offset()); + let env = ctx.data(); + let state = env.state.deref(); + + let pointer: u64 = wasi_try_ok!(futex_ptr.offset().try_into().map_err(|_| __WASI_EOVERFLOW)); + + // Register the waiting futex + let futex = { + use std::collections::hash_map::Entry; + let mut guard = state.futexs.lock().unwrap(); + match guard.entry(pointer) { + Entry::Occupied(entry) => { + entry.get().clone() + }, + Entry::Vacant(entry) => { + let futex = WasiFutex { + refcnt: Arc::new(AtomicU32::new(1)), + inner: Arc::new((Mutex::new(()), Condvar::new())) + }; + entry.insert(futex.clone()); + futex + } + } + }; + + // Loop until we either hit a yield error or the futex is woken + let mut yielded = Ok(()); + loop { + let futex_lock = futex.inner.0.lock().unwrap(); + + // If the value of the memory is no longer the expected value + // then terminate from the loop (we do this under a futex lock + // so that its protected) + { + let view = env.memory_view(&ctx); + let val = wasi_try_mem_ok!(futex_ptr.read(&view)); + if val != expected { + break; + } + } + + let result = futex.inner.1.wait_timeout(futex_lock, Duration::from_millis(50)).unwrap(); + if result.1.timed_out() { + yielded = env.yield_now(); + if yielded.is_err() { + break; + } + } else { + break; + } + } + + // Drop the reference count to the futex (and remove it if the refcnt hits zero) + { + let mut guard = state.futexs.lock().unwrap(); + if guard.get(&pointer) + .map(|futex| futex.refcnt.fetch_sub(1, Ordering::AcqRel) == 1) + .unwrap_or(false) + { + guard.remove(&pointer); + } + } + + // We may have a yield error (such as a terminate) + yielded?; + + Ok(__WASI_ESUCCESS) +} + +/// Wake up one thread that's blocked on futex_wait on this futex. +/// Returns true if this actually woke up such a thread, +/// or false if no thread was waiting on this futex. +/// +/// ## Parameters +/// +/// * `futex` - Memory location that holds a futex that others may be waiting on +pub fn futex_wake( + ctx: FunctionEnvMut<'_, WasiEnv>, + futex: WasmPtr, + ret_woken: WasmPtr<__wasi_bool_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::futex_wake(offset={})", ctx.data().pid(), ctx.data().tid(), futex.offset()); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let state = env.state.deref(); + + let pointer: u64 = wasi_try!(futex.offset().try_into().map_err(|_| __WASI_EOVERFLOW)); + let mut woken = false; + + let mut guard = state.futexs.lock().unwrap(); + if let Some(futex) = guard.get(&pointer) { + futex.inner.1.notify_one(); + woken = true; + } else { + trace!("wasi[{}:{}]::futex_wake - nothing waiting!", ctx.data().pid(), ctx.data().tid()); + } + + let woken = match woken { + false => __WASI_BOOL_FALSE, + true => __WASI_BOOL_TRUE, + }; + wasi_try_mem!(ret_woken.write(&memory, woken)); + + __WASI_ESUCCESS +} + +/// Wake up all threads that are waiting on futex_wait on this futex. +/// +/// ## Parameters +/// +/// * `futex` - Memory location that holds a futex that others may be waiting on +pub fn futex_wake_all( + ctx: FunctionEnvMut<'_, WasiEnv>, + futex: WasmPtr, + ret_woken: WasmPtr<__wasi_bool_t, M>, +) -> __wasi_errno_t { + trace!("wasi[{}:{}]::futex_wake_all(offset={})", ctx.data().pid(), ctx.data().tid(), futex.offset()); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let state = env.state.deref(); + + let pointer: u64 = wasi_try!(futex.offset().try_into().map_err(|_| __WASI_EOVERFLOW)); + let mut woken = false; + + let mut guard = state.futexs.lock().unwrap(); + if let Some(futex) = guard.remove(&pointer) { + futex.inner.1.notify_all(); + woken = true; + } + + let woken = match woken { + false => __WASI_BOOL_FALSE, + true => __WASI_BOOL_TRUE, + }; + wasi_try_mem!(ret_woken.write(&memory, woken)); + + __WASI_ESUCCESS +} + +/// ### `getpid()` +/// Returns the handle of the current process +pub fn proc_id( + ctx: FunctionEnvMut<'_, WasiEnv>, + ret_pid: WasmPtr<__wasi_pid_t, M>, +) -> __wasi_errno_t { + let env = ctx.data(); + let pid = env.process.pid(); + debug!("wasi[{}:{}]::getpid", ctx.data().pid(), ctx.data().tid()); + + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_pid.write(&memory, pid.raw() as __wasi_pid_t)); + __WASI_ESUCCESS +} + +/// ### `getppid()` +/// Returns the parent handle of the supplied process +pub fn proc_parent( + ctx: FunctionEnvMut<'_, WasiEnv>, + pid: __wasi_pid_t, + ret_parent: WasmPtr<__wasi_pid_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::getppid", ctx.data().pid(), ctx.data().tid()); + + let env = ctx.data(); + let pid: WasiProcessId = pid.into(); + if pid == env.process.pid() { + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_parent.write(&memory, env.process.ppid().raw() as __wasi_pid_t)); + } else { + let compute = env.process.control_plane(); + if let Some(process) = compute.get_process(pid) { + let memory = env.memory_view(&ctx); + wasi_try_mem!(ret_parent.write(&memory, process.pid().raw() as __wasi_pid_t)); + } else { + return __WASI_EBADF; + } + } + __WASI_ESUCCESS +} + +/// ### `thread_exit()` +/// Terminates the current running thread, if this is the last thread then +/// the process will also exit with the specified exit code. An exit code +/// of 0 indicates successful termination of the thread. The meanings of +/// other values is dependent on the environment. +/// +/// ## Parameters +/// +/// * `rval` - The exit code returned by the process. +pub fn thread_exit( + ctx: FunctionEnvMut<'_, WasiEnv>, + exitcode: __wasi_exitcode_t, +) -> Result<(), WasiError> { + debug!("wasi[{}:{}]::thread_exit", ctx.data().pid(), ctx.data().tid()); + Err(WasiError::Exit(exitcode)) +} + +// Function to prepare the WASI environment +fn _prepare_wasi(wasi_env: &mut WasiEnv, args: Option>) +{ + // Swap out the arguments with the new ones + if let Some(args) = args { + let mut wasi_state = wasi_env.state.fork(); + wasi_state.args = args; + wasi_env.state = Arc::new(wasi_state); + } + + // Close any files after the STDERR that are not preopened + let close_fds = { + let preopen_fds = { + let preopen_fds = wasi_env.state.fs.preopen_fds.read().unwrap(); + preopen_fds.iter().map(|a| *a).collect::>() + }; + let mut fd_map = wasi_env.state.fs.fd_map.read().unwrap(); + fd_map.keys().filter_map(|a| { + match *a { + a if a <= __WASI_STDERR_FILENO => None, + a if preopen_fds.contains(&a) => None, + a => Some(a) + } + }).collect::>() }; - // Create the sub-thread - let mut sub_env = env.clone(); - let mut sub_thread = env.new_thread(); - sub_env.id = sub_thread.id; - - let child = { - let id = sub_thread.id; - wasi_try!(env - .runtime - .thread_spawn(Box::new(move || { - /* - if let Some(funct) = sub_env.thread_start_ref() { - if let Err(err) = funct.call(user_data) { - warn!("thread failed: {}", err); - std::mem::forget(sub_thread); + // Now close all these files + for fd in close_fds { + let inodes = wasi_env.state.inodes.read().unwrap(); + let _ = wasi_env.state.fs.close_fd(inodes.deref(), fd); + } +} + +fn conv_bus_err_to_exit_code(err: VirtualBusError) -> u32 { + match err { + VirtualBusError::AccessDenied => -1i32 as u32, + VirtualBusError::NotFound => -2i32 as u32, + VirtualBusError::Unsupported => -22i32 as u32, + VirtualBusError::BadRequest | _ => -8i32 as u32 + } +} + +/// ### `proc_fork()` +/// Forks the current process into a new subprocess. If the function +/// returns a zero then its the new subprocess. If it returns a positive +/// number then its the current process and the $pid represents the child. +#[cfg(feature = "os")] +pub fn proc_fork( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + mut copy_memory: __wasi_bool_t, + pid_ptr: WasmPtr<__wasi_pid_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + // If we were just restored then we need to return the value instead + let fork_op = if copy_memory == __WASI_BOOL_TRUE { "fork" } else { "vfork" }; + if handle_rewind::(&mut ctx) { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let ret_pid = wasi_try_mem_ok!( + pid_ptr.read(&memory) + ); + if ret_pid == 0 { + trace!("wasi[{}:{}]::proc_{} - entering child", ctx.data().pid(), ctx.data().tid(), fork_op); + } else { + trace!("wasi[{}:{}]::proc_{} - entering parent(child={})", ctx.data().pid(), ctx.data().tid(), fork_op, ret_pid); + } + return Ok(__WASI_ESUCCESS); + } + trace!("wasi[{}:{}]::proc_{} - capturing", ctx.data().pid(), ctx.data().tid(), fork_op); + + // Fork the environment which will copy all the open file handlers + // and associate a new context but otherwise shares things like the + // file system interface. The handle to the forked process is stored + // in the parent process context + let (mut child_env, mut child_handle) = ctx.data().fork(); + let child_pid = child_env.process.pid(); + + // We write a zero to the PID before we capture the stack + // so that this is what will be returned to the child + { + let mut children = ctx.data().process.children.write().unwrap(); + children.push(child_pid); + } + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!( + pid_ptr.write(&memory, 0) + ); + + // Pass some offsets to the unwind function + let pid_offset = pid_ptr.offset(); + + // If we are not copying the memory then we act like a `vfork` + // instead which will pretend to be the new process for a period + // of time until `proc_exec` is called at which point the fork + // actually occurs + if copy_memory == __WASI_BOOL_FALSE + { + // Perform the unwind action + let pid_offset: u64 = pid_offset.into(); + return unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| + { + // Grab all the globals and serialize them + let store_data = ctx.as_store_ref().save_snapshot().serialize(); + let store_data = Bytes::from(store_data); + + // We first fork the environment and replace the current environment + // so that the process can continue to prepare for the real fork as + // if it had actually forked + std::mem::swap(&mut ctx.data_mut().inner, &mut child_env.inner); + std::mem::swap(ctx.data_mut(), &mut child_env); + ctx.data_mut().vfork.replace(WasiVFork { + rewind_stack: rewind_stack.clone(), + memory_stack: memory_stack.clone(), + store_data: store_data.clone(), + env: Box::new(child_env), + handle: child_handle, + pid_offset, + }); + + // Carry on as if the fork had taken place (which basically means + // it prevents to be the new process with the old one suspended) + // Rewind the stack and carry on + match rewind::(ctx, memory_stack.freeze(), rewind_stack.freeze(), store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("{} failed - could not rewind the stack - errno={}", fork_op, err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + } + }); + } + + // Create the thread that will back this forked process + let state = env.state.clone(); + let bin_factory = env.bin_factory.clone(); + + // Perform the unwind action + unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| + { + // Grab all the globals and serialize them + let store_data = ctx.as_store_ref().save_snapshot().serialize(); + let store_data = Bytes::from(store_data); + + // Fork the memory and copy the module (compiled code) + let env = ctx.data(); + let fork_memory: VMMemory = match env + .memory() + .try_clone(&ctx) + .ok_or_else(|| { + error!("wasi[{}:{}]::{} failed - the memory could not be cloned", ctx.data().pid(), ctx.data().tid(), fork_op); + MemoryError::Generic(format!("the memory could not be cloned")) + }) + .and_then(|mut memory| + memory.fork() + ) + { + Ok(memory) => { + memory.into() + }, + Err(err) => { + warn!("wasi[{}:{}]::{} failed - could not fork the memory - {}", ctx.data().pid(), ctx.data().tid(), fork_op, err); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } + }; + let fork_module = env.inner().module.clone(); + + #[cfg(feature = "compiler")] + let engine = ctx.as_store_ref().engine().clone(); + + // Build a new store that will be passed to the thread + #[cfg(feature = "compiler")] + let mut fork_store = Store::new(engine); + #[cfg(not(feature = "compiler"))] + let mut fork_store = Store::default(); + + // Now we use the environment and memory references + let runtime = child_env.runtime.clone(); + let tasks = child_env.tasks.clone(); + let child_memory_stack = memory_stack.clone(); + let child_rewind_stack = rewind_stack.clone(); + + // ------------------------------------------------------- + + // Spawn a new process with this current execution environment + let signaler = Box::new(child_env.process.clone()); + let (exit_code_tx, exit_code_rx) = tokio::sync::mpsc::unbounded_channel(); + { + let store_data = store_data.clone(); + let runtime = runtime.clone(); + let tasks = tasks.clone(); + let tasks_outer = tasks.clone(); + tasks_outer.task_wasm(Box::new(move |mut store, module, memory| + { + // Create the WasiFunctionEnv + let pid = child_env.pid(); + let tid = child_env.tid(); + child_env.runtime = runtime.clone(); + child_env.tasks = tasks.clone(); + let mut ctx = WasiFunctionEnv::new(&mut store, child_env); + + // Let's instantiate the module with the imports. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &ctx.env); + let memory = if let Some(memory) = memory { + let memory = Memory::new_from_existing(&mut store, memory); + import_object.define("env", "memory", memory.clone()); + memory + } else { + error!("wasi[{}:{}]::wasm instantiate failed - no memory supplied", pid, tid); return; + }; + let instance = match Instance::new(&mut store, &module, &import_object) { + Ok(a) => a, + Err(err) => { + error!("wasi[{}:{}]::wasm instantiate error ({})", pid, tid, err); + return; + } + }; + + // Set the current thread ID + ctx.data_mut(&mut store).inner = Some( + WasiEnvInner::new(module, memory, &store, &instance) + ); + + // Rewind the stack and carry on + { + trace!("wasi[{}:{}]::{}: rewinding child", ctx.data(&store).pid(), ctx.data(&store).tid(), fork_op); + let ctx = ctx.env.clone().into_mut(&mut store); + match rewind::(ctx, child_memory_stack.freeze(), child_rewind_stack.freeze(), store_data.clone()) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("wasi[{}:{}]::wasm rewind failed - could not rewind the stack - errno={}", pid, tid, err); + return; + } + }; + } + + // Invoke the start function + let mut ret = __WASI_ESUCCESS; + if ctx.data(&store).thread.is_main() { + trace!("wasi[{}:{}]::{}: re-invoking main", ctx.data(&store).pid(), ctx.data(&store).tid(), fork_op); + let start = ctx.data(&store).inner().start.clone().unwrap(); + start.call(&mut store); + } else { + trace!("wasi[{}:{}]::{}: re-invoking thread_spawn", ctx.data(&store).pid(), ctx.data(&store).tid(), fork_op); + let start = ctx.data(&store).inner().thread_spawn.clone().unwrap(); + start.call(&mut store, 0, 0); + } + + // Clean up the environment + ctx.cleanup((&mut store)); + + // Send the result + let _ = exit_code_tx.send(ret as u32); + drop(exit_code_tx); + drop(child_handle); + } + ), fork_store, fork_module, SpawnType::NewThread(fork_memory)) + .map_err(|err| { + warn!("wasi[{}:{}]::failed to fork as the process could not be spawned - {}", ctx.data().pid(), ctx.data().tid(), err); + err + }) + .ok() + }; + + // Add the process to the environment state + let process = BusSpawnedProcess { + inst: Box::new( + crate::bin_factory::SpawnedProcess { + exit_code: Mutex::new(None), + exit_code_rx: Mutex::new(exit_code_rx), + } + ), + stdin: None, + stdout: None, + stderr: None, + signaler: Some(signaler), + }; + { + trace!("wasi[{}:{}]::spawned sub-process (pid={})", ctx.data().pid(), ctx.data().tid(), child_pid.raw()); + let mut inner = ctx.data().process.write(); + inner.bus_processes.insert(child_pid.into(), Box::new(process)); + } + + // ------------------------------------------------------- + + /* + // This function takes in memory and a store and creates a context that + // can be used to call back into the process + let create_ctx = { + let state = child_env.state.clone(); + let wasi_env = child_env.clone(); + move |mut store: Store, module: Module, memory: VMMemory| + { + // We need to reconstruct some things + let module = module.clone(); + let memory = Memory::new_from_existing(&mut store, memory); + + // Build the context object and import the memory + let mut ctx = WasiFunctionEnv::new(&mut store, wasi_env.clone()); + { + let env = ctx.data_mut(&mut store); + env.stack_base = stack_base; + env.stack_start = stack_start; + } + + let mut import_object = import_object_for_all_wasi_versions(&mut store, &ctx.env); + import_object.define("env", "memory", memory.clone()); + + let instance = match Instance::new(&mut store, &module, &import_object) { + Ok(a) => a, + Err(err) => { + error!("{} failed - create instance failed: {}", fork_op, err); + return Err(__WASI_ENOEXEC as u32); } + }; + + // Set the current thread ID + ctx.data_mut(&mut store).inner = Some( + WasiEnvInner::new(module, memory, &store, &instance) + ); + trace!("{}: new context created for thread_id = {}", fork_op, wasi_env.thread.tid().raw()); + Ok(WasiThreadContext { + ctx, + store: RefCell::new(store) + }) + } + }; + + // This function calls into the module + let call_module = { + let store_data = store_data.clone(); + move |ctx: &WasiFunctionEnv, mut store: &mut Store| + { + // Rewind the stack and carry on + { + trace!("wasi[{}:{}]::{}: rewinding child", ctx.data(store).pid(), fork_op); + let ctx = ctx.env.clone().into_mut(&mut store); + match rewind::(ctx, child_memory_stack.freeze(), child_rewind_stack.freeze(), store_data.clone()) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("{} failed - could not rewind the stack - errno={}", fork_op, err); + return __WASI_ENOEXEC as u32; + } + }; + } + + // Invoke the start function + let mut ret = __WASI_ESUCCESS; + if ctx.data(store).thread.is_main() { + trace!("wasi[{}:{}]::{}: re-invoking main", ctx.data(store).pid(), fork_op); + let start = ctx.data(store).inner().start.clone().unwrap(); + start.call(&mut store); } else { - warn!("failed to start thread: missing callback '__wasix_thread_start'"); - std::mem::forget(sub_thread); - return; + trace!("wasi[{}:{}]::{}: re-invoking thread_spawn", ctx.data(store).pid(), fork_op); + let start = ctx.data(store).inner().thread_spawn.clone().unwrap(); + start.call(&mut store, 0, 0); } - */ - let thread = { - let mut guard = sub_env.state.threading.lock().unwrap(); - let thread = guard.threads.remove(&id); - drop(guard); - thread - }; + // Clean up the environment + ctx.cleanup((&mut store)); + + // Return the result + ret as u32 + } + }; + + // This next function gets a context for the local thread and then + // calls into the process + let mut execute_module = { + let state = child_env.state.clone(); + move |store: &mut Option, module: Module, memory: &mut Option| + { + // We capture the thread handle here, it is used to notify + // anyone that is interested when this thread has terminated + let _captured_handle = Box::new(&mut child_handle); + + // Given that it is not safe to assume this delegate will run on the + // same thread we need to capture a simple process that will create + // context objects on demand and reuse them + let caller_id = current_caller_id(); + + // We loop because read locks are held while functions run which need + // to be relocked in the case of a miss hit. + loop { + let thread = { + let guard = state.threading.read().unwrap(); + guard.thread_ctx.get(&caller_id).map(|a| a.clone()) + }; + if let Some(thread) = thread + { + let mut store = thread.store.borrow_mut(); + let ret = call_module(&thread.ctx, store.deref_mut()); + return ret; + } + + // Otherwise we need to create a new context under a write lock + debug!("encountered a new caller (ref={}) - creating WASM execution context...", caller_id.raw()); + + // We can only create the context once per thread + let memory = match memory.take() { + Some(m) => m, + None => { + debug!("{} failed - memory can only be consumed once per context creation", fork_op); + return __WASI_ENOEXEC as u32; + } + }; + let store = match store.take() { + Some(s) => s, + None => { + debug!("{} failed - store can only be consumed once per context creation", fork_op); + return __WASI_ENOEXEC as u32; + } + }; - if let Some(thread) = thread { - let mut thread_guard = thread.exit.lock().unwrap(); - thread_guard.take(); + // Now create the context and hook it up + let mut guard = state.threading.write().unwrap(); + let ctx = match create_ctx(store, module.clone(), memory) { + Ok(c) => c, + Err(err) => { + return err; + } + }; + guard.thread_ctx.insert(caller_id, Arc::new(ctx)); } - drop(sub_thread); - })) + } + }; + + // Now spawn a thread + trace!("{}: spawning child process (pid={})", fork_op, child_pid); + let thread_module = env.inner().module.clone(); + runtime + .task_wasm(Box::new(move |store, module, thread_memory| { + trace!("{}: child process started (pid={})", fork_op, child_pid); + let mut thread_memory = thread_memory; + let mut store = Some(store); + execute_module(&mut store, module, &mut thread_memory); + }), + store, + thread_module, + crate::runtime::SpawnType::NewThread(fork_memory) + ) .map_err(|err| { - let err: Errno = err.into(); + let err: __wasi_errno_t = err.into(); err - })); - id - }; - let child: Tid = child.into(); - - wasi_try_mem!(ret_tid.write(&memory, child)); - Errno::Success -} - -/// ### `thread_sleep()` -/// Sends the current thread to sleep for a period of time -/// -/// ## Parameters -/// -/// * `duration` - Amount of time that the thread should sleep -pub fn thread_sleep( - ctx: FunctionEnvMut<'_, WasiEnv>, - duration: Timestamp, -) -> Result { - debug!("wasi::thread_sleep"); + }) + .unwrap(); + */ + + // If the return value offset is within the memory stack then we need + // to update it here rather than in the real memory + let pid_offset: u64 = pid_offset.into(); + if pid_offset >= env.stack_start && pid_offset < env.stack_base + { + // Make sure its within the "active" part of the memory stack + let offset = env.stack_base - pid_offset; + if offset as usize > memory_stack.len() { + warn!("{} failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", fork_op, offset, memory_stack.len()); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } + + // Update the memory stack with the new PID + let val_bytes = child_pid.raw().to_ne_bytes(); + let pstart = memory_stack.len() - offset as usize; + let pend = pstart + val_bytes.len(); + let pbytes = &mut memory_stack[pstart..pend]; + pbytes.clone_from_slice(&val_bytes); + } else { + warn!("{} failed - the return value (pid) is not being returned on the stack - which is not supported", fork_op); + return OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))); + } - let env = ctx.data(); - let duration = Duration::from_nanos(duration as u64); - env.sleep(duration)?; - Ok(Errno::Success) + // Rewind the stack and carry on + match rewind::(ctx, memory_stack.freeze(), rewind_stack.freeze(), store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("{} failed - could not rewind the stack - errno={}", fork_op, err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + } + }) } -/// ### `thread_id()` -/// Returns the index of the current thread -/// (threads indices are sequencial from zero) -pub fn thread_id( - ctx: FunctionEnvMut<'_, WasiEnv>, - ret_tid: WasmPtr, -) -> Errno { - debug!("wasi::thread_id"); - - let env = ctx.data(); - let tid: Tid = env.id.into(); - let memory = env.memory_view(&ctx); - wasi_try_mem!(ret_tid.write(&memory, tid)); - Errno::Success +#[cfg(not(feature = "os"))] +pub fn proc_fork( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + mut copy_memory: __wasi_bool_t, + pid_ptr: WasmPtr<__wasi_pid_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + warn!("wasi[{}:{}]::proc_fork - not supported without 'os' feature", ctx.data().pid(), ctx.data().tid()); + Ok(__WASI_ENOTSUP) } -/// ### `thread_join()` -/// Joins this thread with another thread, blocking this -/// one until the other finishes +/// Replaces the current process with a new process /// /// ## Parameters /// -/// * `tid` - Handle of the thread to wait on -pub fn thread_join(ctx: FunctionEnvMut<'_, WasiEnv>, tid: Tid) -> Result { - debug!("wasi::thread_join"); - - let env = ctx.data(); - let tid: WasiThreadId = tid.into(); - let other_thread = { - let guard = env.state.threading.lock().unwrap(); - guard.threads.get(&tid).cloned() - }; - if let Some(other_thread) = other_thread { - loop { - if other_thread.join(Duration::from_millis(5)) { - break; +/// * `name` - Name of the process to be spawned +/// * `args` - List of the arguments to pass the process +/// (entries are separated by line feeds) +/// +/// ## Return +/// +/// Returns a bus process id that can be used to invoke calls +#[cfg(feature = "os")] +pub fn proc_exec( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, + args: WasmPtr, + args_len: M::Offset, +) -> Result<(), WasiError> { + let memory = ctx.data().memory_view(&ctx); + let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| { + warn!("failed to execve as the name could not be read - {}", err); + WasiError::Exit(__WASI_EFAULT as __wasi_exitcode_t) + })?; + trace!("wasi[{}:{}]::proc_exec (name={})", ctx.data().pid(), ctx.data().tid(), name); + + let args = args.read_utf8_string(&memory, args_len).map_err(|err| { + warn!("failed to execve as the args could not be read - {}", err); + WasiError::Exit(__WASI_EFAULT as __wasi_exitcode_t) + })?; + let args: Vec<_> = args.split(&['\n', '\r']).map(|a| a.to_string()).filter(|a| a.len() > 0).collect(); + + // Convert relative paths into absolute paths + if name.starts_with("./") { + name = ctx.data().state.fs.relative_path_to_absolute(name); + trace!("wasi[{}:{}]::rel_to_abs (name={}))", ctx.data().pid(), ctx.data().tid(), name); + } + + // Convert the preopen directories + let preopen = ctx.data().state.preopen.clone(); + + // Get the current working directory + let (_, cur_dir) = { + let (memory, state, mut inodes) = ctx.data().get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); + match state + .fs + .get_current_dir(inodes.deref_mut(), crate::VIRTUAL_ROOT_FD,) + { + Ok(a) => a, + Err(err) => { + warn!("failed to create subprocess for fork - {}", err); + return Err(WasiError::Exit(__WASI_EFAULT as __wasi_exitcode_t)); } - env.yield_now()?; } - Ok(Errno::Success) - } else { - Ok(Errno::Success) - } -} + }; -/// ### `thread_parallelism()` -/// Returns the available parallelism which is normally the -/// number of available cores that can run concurrently -pub fn thread_parallelism( - ctx: FunctionEnvMut<'_, WasiEnv>, - ret_parallelism: WasmPtr, -) -> Errno { - debug!("wasi::thread_parallelism"); + // Build a new store that will be passed to the thread + #[cfg(feature = "compiler")] + let engine = ctx.as_store_ref().engine().clone(); + #[cfg(feature = "compiler")] + let new_store = Store::new(engine); + #[cfg(not(feature = "compiler"))] + let new_store = Store::default(); + + // If we are in a vfork we need to first spawn a subprocess of this type + // with the forked WasiEnv, then do a longjmp back to the vfork point. + if let Some(mut vfork) = ctx.data_mut().vfork.take() + { + // We will need the child pid later + let child_pid = ctx.data().process.pid(); + + // Restore the WasiEnv to the point when we vforked + std::mem::swap(&mut vfork.env.inner, &mut ctx.data_mut().inner); + std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); + let mut wasi_env = *vfork.env; + wasi_env.owned_handles.push(vfork.handle); + _prepare_wasi(&mut wasi_env, Some(args)); + + // Recrod the stack offsets before we give up ownership of the wasi_env + let stack_base = wasi_env.stack_base; + let stack_start = wasi_env.stack_start; + + // Spawn a new process with this current execution environment + let mut err_exit_code = -2i32 as u32; + let bus = ctx.data().bus(); + let mut process = bus + .spawn(wasi_env) + .spawn(Some(&ctx), name.as_str(), new_store, &ctx.data().bin_factory) + .map_err(|err| { + err_exit_code = conv_bus_err_to_exit_code(err); + warn!("failed to execve as the process could not be spawned (vfork) - {}", err); + let _ = stderr_write(&ctx, format!("wasm execute failed [{}] - {}\n", name.as_str(), err).as_bytes()); + err + }) + .ok(); + + // If no process was created then we create a dummy one so that an + // exit code can be processed + let process = match process { + Some(a) => a, + None => { + debug!("wasi[{}:{}]::process failed with (err={})", ctx.data().pid(), ctx.data().tid(), err_exit_code); + BusSpawnedProcess::exited_process(err_exit_code) + } + }; + + // Add the process to the environment state + { + trace!("wasi[{}:{}]::spawned sub-process (pid={})", ctx.data().pid(), ctx.data().tid(), child_pid.raw()); + let mut inner = ctx.data().process.write(); + inner.bus_processes.insert(child_pid.into(), Box::new(process)); + } - let env = ctx.data(); - let parallelism = wasi_try!(env.runtime().thread_parallelism().map_err(|err| { - let err: Errno = err.into(); - err - })); - let parallelism: M::Offset = wasi_try!(parallelism.try_into().map_err(|_| Errno::Overflow)); - let memory = env.memory_view(&ctx); - wasi_try_mem!(ret_parallelism.write(&memory, parallelism)); - Errno::Success -} + let mut memory_stack = vfork.memory_stack; + let rewind_stack = vfork.rewind_stack; + let store_data = vfork.store_data; -/// ### `getpid()` -/// Returns the handle of the current process -pub fn getpid(ctx: FunctionEnvMut<'_, WasiEnv>, ret_pid: WasmPtr) -> Errno { - debug!("wasi::getpid"); + // If the return value offset is within the memory stack then we need + // to update it here rather than in the real memory + let pid_offset: u64 = vfork.pid_offset.into(); + if pid_offset >= stack_start && pid_offset < stack_base + { + // Make sure its within the "active" part of the memory stack + let offset = stack_base - pid_offset; + if offset as usize > memory_stack.len() { + warn!("vfork failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", offset, memory_stack.len()); + } else { + // Update the memory stack with the new PID + let val_bytes = child_pid.raw().to_ne_bytes(); + let pstart = memory_stack.len() - offset as usize; + let pend = pstart + val_bytes.len(); + let pbytes = &mut memory_stack[pstart..pend]; + pbytes.clone_from_slice(&val_bytes); + } + } else { + warn!("vfork failed - the return value (pid) is not being returned on the stack - which is not supported"); + } - let env = ctx.data(); - let pid = env.runtime().getpid(); - if let Some(pid) = pid { - let memory = env.memory_view(&ctx); - wasi_try_mem!(ret_pid.write(&memory, pid as Pid)); - Errno::Success - } else { - Errno::Notsup + // Jump back to the vfork point and current on execution + unwind::(ctx, move |mut ctx, _, _| + { + // Rewind the stack + match rewind::(ctx, memory_stack.freeze(), rewind_stack.freeze(), store_data) { + __WASI_ESUCCESS => OnCalledAction::InvokeAgain, + err => { + warn!("fork failed - could not rewind the stack - errno={}", err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_EFAULT as u32))) + } + } + })?; + return Ok(()); + } + + // Otherwise we need to unwind the stack to get out of the current executing + // callstack, steal the memory/WasiEnv and switch it over to a new thread + // on the new module + else + { + // We need to unwind out of this process and launch a new process in its place + unwind::(ctx, move |mut ctx, _, _| + { + // Grab a reference to the bus + let bus = ctx.data().bus().clone(); + + // Prepare the environment + let mut wasi_env = ctx.data_mut().clone(); + _prepare_wasi(&mut wasi_env, Some(args)); + + // Get a reference to the runtime + let bin_factory = ctx.data().bin_factory.clone(); + let tasks = wasi_env.tasks.clone(); + + // Create the process and drop the context + let builder = ctx.data().bus() + .spawn(wasi_env); + + // Spawn a new process with this current execution environment + //let pid = wasi_env.process.pid(); + match builder.spawn(Some(&ctx), name.as_str(), new_store, &bin_factory) + { + Ok(mut process) => { + // Wait for the sub-process to exit itself - then we will exit + loop { + tasks.sleep_now(current_caller_id(), 5); + if let Some(exit_code) = process.inst.exit_code() { + return OnCalledAction::Trap(Box::new(WasiError::Exit(exit_code as crate::syscalls::types::__wasi_exitcode_t))); + } + } + } + Err(err) => { + warn!("failed to execve as the process could not be spawned (fork) - {}", err); + let exit_code = conv_bus_err_to_exit_code(err); + OnCalledAction::Trap(Box::new(WasiError::Exit(__WASI_ENOEXEC as crate::syscalls::types::__wasi_exitcode_t))) + } + } + })?; } + + // Success + Ok(()) } -/// ### `thread_exit()` -/// Terminates the current running thread, if this is the last thread then -/// the process will also exit with the specified exit code. An exit code -/// of 0 indicates successful termination of the thread. The meanings of -/// other values is dependent on the environment. -/// -/// ## Parameters -/// -/// * `rval` - The exit code returned by the process. -pub fn thread_exit( - ctx: FunctionEnvMut<'_, WasiEnv>, - exitcode: __wasi_exitcode_t, -) -> Result { - debug!("wasi::thread_exit"); - Err(WasiError::Exit(exitcode)) +#[cfg(not(feature = "os"))] +pub fn proc_exec( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + _name: WasmPtr, + _name_len: M::Offset, + _args: WasmPtr, + _args_len: M::Offset, +) -> Result<(), WasiError> { + warn!("wasi[{}:{}]::exec is not supported in this build", ctx.data().pid(), ctx.data().tid()); + Err(WasiError::Exit(__WASI_ENOTSUP as __wasi_exitcode_t)) } /// Spawns a new process within the context of this machine @@ -3732,8 +6148,8 @@ pub fn thread_exit( /// ## Return /// /// Returns a bus process id that can be used to invoke calls -pub fn process_spawn( - ctx: FunctionEnvMut<'_, WasiEnv>, +pub fn proc_spawn( + mut ctx: FunctionEnvMut<'_, WasiEnv>, name: WasmPtr, name_len: M::Offset, chroot: Bool, @@ -3749,76 +6165,282 @@ pub fn process_spawn( ret_handles: WasmPtr, ) -> BusErrno { let env = ctx.data(); - let bus = env.runtime.bus(); + let control_plane = env.process.control_plane(); let memory = env.memory_view(&ctx); let name = unsafe { get_input_str_bus!(&memory, name, name_len) }; let args = unsafe { get_input_str_bus!(&memory, args, args_len) }; let preopen = unsafe { get_input_str_bus!(&memory, preopen, preopen_len) }; let working_dir = unsafe { get_input_str_bus!(&memory, working_dir, working_dir_len) }; - let chroot = chroot == Bool::True; - debug!("wasi::process_spawn (name={})", name); + debug!("wasi[{}:{}]::process_spawn (name={})", ctx.data().pid(), ctx.data().tid(), name); + + if chroot == __WASI_BOOL_TRUE { + warn!("wasi[{}:{}]::chroot is not currently supported", ctx.data().pid(), ctx.data().tid()); + return __BUS_EUNSUPPORTED; + } - let args: Vec<_> = args.split(&['\n', '\r']).map(|a| a.to_string()).collect(); + let args: Vec<_> = args.split(&['\n', '\r']).map(|a| a.to_string()).filter(|a| a.len() > 0).collect(); let preopen: Vec<_> = preopen .split(&['\n', '\r']) .map(|a| a.to_string()) + .filter(|a| a.len() > 0) .collect(); - let conv_stdio_mode = |mode: WasiStdioMode| match mode { - WasiStdioMode::Piped => StdioMode::Piped, - WasiStdioMode::Inherit => StdioMode::Inherit, - WasiStdioMode::Log => StdioMode::Log, - /*__WASI_STDIO_MODE_NULL |*/ _ => StdioMode::Null, + let (handles, ctx) = match proc_spawn_internal( + ctx, + name, + Some(args), + Some(preopen), + Some(working_dir), + stdin, + stdout, + stderr + ) { + Ok(a) => a, + Err(err) => { + return err; + } }; - let process = wasi_try_bus!(bus - .new_spawn() - .chroot(chroot) - .args(args) - .preopen(preopen) - .stdin_mode(conv_stdio_mode(stdin)) - .stdout_mode(conv_stdio_mode(stdout)) - .stderr_mode(conv_stdio_mode(stderr)) - .working_dir(working_dir) - .spawn(name.as_str()) - .map_err(bus_error_into_wasi_err)); - - let conv_stdio_fd = |a: Option| match a { - Some(fd) => OptionFd { - tag: OptionTag::Some, - fd: fd.into(), - }, - None => OptionFd { - tag: OptionTag::None, - fd: 0, - }, + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_bus!(ret_handles.write(&memory, handles)); + __BUS_ESUCCESS +} + +#[cfg(feature = "os")] +pub fn proc_spawn_internal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: String, + args: Option>, + preopen: Option>, + working_dir: Option, + stdin: __wasi_stdiomode_t, + stdout: __wasi_stdiomode_t, + stderr: __wasi_stdiomode_t, +) -> Result<(__wasi_bus_handles_t, FunctionEnvMut<'_, WasiEnv>), __bus_errno_t> +{ + let env = ctx.data(); + + // Build a new store that will be passed to the thread + #[cfg(feature = "compiler")] + let engine = ctx.as_store_ref().engine().clone(); + #[cfg(feature = "compiler")] + let new_store = Store::new(engine); + #[cfg(not(feature = "compiler"))] + let new_store = Store::default(); + + // Fork the current environment and set the new arguments + let (mut child_env, handle) = ctx.data().fork(); + if let Some(args) = args { + let mut child_state = env.state.fork(); + child_state.args = args; + child_env.state = Arc::new(child_state); + } + + // Take ownership of this child + ctx.data_mut().owned_handles.push(handle); + let env = ctx.data(); + + // Preopen + if let Some(preopen) = preopen { + if preopen.is_empty() == false { + for preopen in preopen { + warn!("wasi[{}:{}]::preopens are not yet supported for spawned processes [{}]", ctx.data().pid(), ctx.data().tid(), preopen); + } + return Err(__BUS_EUNSUPPORTED); + } + } + + // Change the current directory + if let Some(working_dir) = working_dir { + child_env.state.fs.set_current_dir(working_dir.as_str()); + } + + // Replace the STDIO + let (stdin, stdout, stderr) = { + let (_, child_state, mut child_inodes) = child_env.get_memory_and_wasi_state_and_inodes_mut(&new_store, 0); + let mut conv_stdio_mode = |mode: __wasi_stdiomode_t, fd: __wasi_fd_t| -> Result<__wasi_option_fd_t, __bus_errno_t> + { + match mode { + __WASI_STDIO_MODE_PIPED => { + let (pipe1, pipe2) = WasiPipe::new(); + let inode1 = child_state.fs.create_inode_with_default_stat( + child_inodes.deref_mut(), + Kind::Pipe { pipe: pipe1 }, + false, + "pipe".into(), + ); + let inode2 = child_state.fs.create_inode_with_default_stat( + child_inodes.deref_mut(), + Kind::Pipe { pipe: pipe2 }, + false, + "pipe".into(), + ); + + let rights = super::state::all_socket_rights(); + let pipe = ctx.data().state.fs.create_fd(rights, rights, 0, 0, inode1)?; + child_state.fs.create_fd_ext(rights, rights, 0, 0, inode2, fd)?; + + trace!("wasi[{}:{}]::fd_pipe (fd1={}, fd2={})", ctx.data().pid(), ctx.data().tid(), pipe, fd); + Ok( + __wasi_option_fd_t { + tag: __WASI_OPTION_SOME, + fd: pipe + } + ) + }, + __WASI_STDIO_MODE_INHERIT => { + Ok( + __wasi_option_fd_t { + tag: __WASI_OPTION_NONE, + fd: u32::MAX + } + ) + }, + __WASI_STDIO_MODE_LOG | __WASI_STDIO_MODE_NULL | _ => { + child_state.fs.close_fd(child_inodes.deref(), fd); + Ok( + __wasi_option_fd_t { + tag: __WASI_OPTION_NONE, + fd: u32::MAX + } + ) + }, + } + }; + let stdin = conv_stdio_mode(stdin, 0)?; + let stdout = conv_stdio_mode(stdout, 1)?; + let stderr = conv_stdio_mode(stderr, 2)?; + (stdin, stdout, stderr) }; - // Convert the stdio - let stdin = conv_stdio_fd(process.inst.stdin_fd()); - let stdout = conv_stdio_fd(process.inst.stdout_fd()); - let stderr = conv_stdio_fd(process.inst.stderr_fd()); + // Create the new process + let bus = env.runtime.bus(); + let mut process = bus + .spawn(child_env) + .spawn(Some(&ctx), name.as_str(), new_store, &ctx.data().bin_factory) + .map_err(bus_error_into_wasi_err)?; + + // Add the process to the environment state + let pid = env.process.pid(); + { + let mut children = ctx.data().process.children.write().unwrap(); + children.push(pid); + } + let env = ctx.data(); + let memory = env.memory_view(&ctx); // Add the process to the environment state - let bid = { - let mut guard = env.state.threading.lock().unwrap(); - guard.process_seed += 1; - let bid = guard.process_seed; - guard.processes.insert(bid.into(), process); - bid + let pid = env.process.pid(); + { + let mut children = ctx.data().process.children.write().unwrap(); + children.push(pid); + } + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + { + let mut guard = env.process.write(); + guard.bus_processes.insert(pid.into(), Box::new(process)); }; - let handles = BusHandles { - bid, + let handles = __wasi_bus_handles_t { + bid: pid.raw(), stdin, stdout, stderr, }; + Ok((handles, ctx)) +} - wasi_try_mem_bus!(ret_handles.write(&memory, handles)); +#[cfg(not(feature = "os"))] +pub fn proc_spawn_internal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + _name: String, + _args: Option>, + _preopen: Option>, + _working_dir: Option, + _stdin: __wasi_stdiomode_t, + _stdout: __wasi_stdiomode_t, + _stderr: __wasi_stdiomode_t, +) -> Result<(__wasi_bus_handles_t, FunctionEnvMut<'_, WasiEnv>), __bus_errno_t> +{ + warn!("wasi[{}:{}]::spawn is not currently supported", ctx.data().pid(), ctx.data().tid()); + Err(__BUS_EUNSUPPORTED) +} + +/// ### `proc_join()` +/// Joins the child process,blocking this one until the other finishes +/// +/// ## Parameters +/// +/// * `pid` - Handle of the child process to wait on +pub fn proc_join( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + pid_ptr: WasmPtr<__wasi_pid_t, M>, + exit_code_ptr: WasmPtr<__wasi_exitcode_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + let pid = wasi_try_mem_ok!(pid_ptr.read(&memory)); + trace!("wasi[{}:{}]::proc_join (pid={})", ctx.data().pid(), ctx.data().tid(), pid); + + // If the ID is maximum then it means wait for all the children + if pid == u32::MAX { + let _guard = WasiProcessWait::new(&ctx.data().process); + loop { + ctx.data().clone().sleep(&mut ctx, std::time::Duration::from_millis(5))?; + { + let children = ctx.data().process.children.read().unwrap(); + if children.is_empty() { + trace!("wasi[{}:{}]::no children", ctx.data().pid(), ctx.data().tid()); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!(pid_ptr.write(&memory, -1i32 as __wasi_pid_t)); + wasi_try_mem_ok!(exit_code_ptr.write(&memory, __WASI_ECHILD as u32)); + return Ok(__WASI_ECHILD); + } + } + if let Some((pid, exit_code)) = wasi_try_ok!(ctx.data_mut().process.join_any_child(Duration::from_millis(0))) { + trace!("wasi[{}:{}]::child ({}) exited with {}", ctx.data().pid(), ctx.data().tid(), pid, exit_code); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!(pid_ptr.write(&memory, pid.raw() as __wasi_pid_t)); + wasi_try_mem_ok!(exit_code_ptr.write(&memory, exit_code)); + return Ok(__WASI_ESUCCESS); + } + } + } + + // Otherwise we wait for the specific PID + let env = ctx.data(); + let pid: WasiProcessId = pid.into(); + let process = env.process.control_plane().get_process(pid).map(|a| a.clone()); + if let Some(process) = process { + loop { + env.yield_now()?; + if let Some(exit_code) = process.join(Duration::from_millis(50)) { + trace!("child ({}) exited with {}", pid.raw(), exit_code); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!(pid_ptr.write(&memory, pid.raw() as __wasi_pid_t)); + wasi_try_mem_ok!(exit_code_ptr.write(&memory, exit_code)); + break; + } + } + let env = ctx.data_mut(); - BusErrno::Success + let mut children = env.process.children.write().unwrap(); + children.retain(|a| *a != pid); + Ok(__WASI_ESUCCESS) + } else { + debug!("process already terminated or not registered (pid={})", pid.raw()); + let memory = env.memory_view(&ctx); + wasi_try_mem_ok!(pid_ptr.write(&memory, pid.raw() as __wasi_pid_t)); + wasi_try_mem_ok!(exit_code_ptr.write(&memory, __WASI_ECHILD as u32)); + Ok(__WASI_ECHILD) + } } /// Spawns a new bus process for a particular web WebAssembly @@ -3837,17 +6459,17 @@ pub fn bus_open_local( ctx: FunctionEnvMut<'_, WasiEnv>, name: WasmPtr, name_len: M::Offset, - reuse: Bool, - ret_bid: WasmPtr, -) -> BusErrno { + reuse: __wasi_bool_t, + ret_bid: WasmPtr<__wasi_bid_t, M>, +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); - let name = unsafe { get_input_str_bus!(&memory, name, name_len) }; - let reuse = reuse == Bool::True; - debug!("wasi::bus_open_local (name={}, reuse={})", name, reuse); + let name = unsafe { get_input_str_bus_ok!(&memory, name, name_len) }; + let reuse = reuse == __WASI_BOOL_TRUE; + debug!("wasi[{}:{}]::bus_open_local (name={}, reuse={})", ctx.data().pid(), ctx.data().tid(), name, reuse); - bus_open_local_internal(ctx, name, reuse, None, None, ret_bid) + bus_open_internal(ctx, name, reuse, None, None, ret_bid) } /// Spawns a new bus process for a particular web WebAssembly @@ -3873,79 +6495,68 @@ pub fn bus_open_remote( instance_len: M::Offset, token: WasmPtr, token_len: M::Offset, - ret_bid: WasmPtr, -) -> BusErrno { + ret_bid: WasmPtr<__wasi_bid_t, M>, +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); - let name = unsafe { get_input_str_bus!(&memory, name, name_len) }; - let instance = unsafe { get_input_str_bus!(&memory, instance, instance_len) }; - let token = unsafe { get_input_str_bus!(&memory, token, token_len) }; - let reuse = reuse == Bool::True; + let name = unsafe { get_input_str_bus_ok!(&memory, name, name_len) }; + let instance = unsafe { get_input_str_bus_ok!(&memory, instance, instance_len) }; + let token = unsafe { get_input_str_bus_ok!(&memory, token, token_len) }; + let reuse = reuse == __WASI_BOOL_TRUE; debug!( "wasi::bus_open_remote (name={}, reuse={}, instance={})", name, reuse, instance ); - bus_open_local_internal(ctx, name, reuse, Some(instance), Some(token), ret_bid) + bus_open_internal(ctx, name, reuse, Some(instance), Some(token), ret_bid) } -fn bus_open_local_internal( - ctx: FunctionEnvMut<'_, WasiEnv>, +fn bus_open_internal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, name: String, reuse: bool, instance: Option, token: Option, - ret_bid: WasmPtr, -) -> BusErrno { + ret_bid: WasmPtr<__wasi_bid_t, M>, +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); - let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); let name: Cow<'static, str> = name.into(); // Check if it already exists if reuse { - let guard = env.state.threading.lock().unwrap(); - if let Some(bid) = guard.process_reuse.get(&name) { - if guard.processes.contains_key(bid) { - wasi_try_mem_bus!(ret_bid.write(&memory, (*bid).into())); - return BusErrno::Success; - } - } - } - - let mut process = bus.new_spawn(); - process - .reuse(reuse) - .stdin_mode(StdioMode::Null) - .stdout_mode(StdioMode::Null) - .stderr_mode(StdioMode::Log); - - if let Some(instance) = instance { - process.remote_instance(instance); - } - - if let Some(token) = token { - process.access_token(token); + let guard = env.process.read(); + if let Some(bid) = guard.bus_process_reuse.get(&name) { + if guard.bus_processes.contains_key(bid) { + wasi_try_mem_bus_ok!(ret_bid.write(&memory, bid.clone().into())); + return Ok(__BUS_ESUCCESS); + } + } } - let process = wasi_try_bus!(process - .spawn(name.as_ref()) - .map_err(bus_error_into_wasi_err)); + let (handles, ctx) = wasi_try_bus_ok!(proc_spawn_internal( + ctx, + name.to_string(), + None, + None, + None, + __WASI_STDIO_MODE_NULL, + __WASI_STDIO_MODE_NULL, + __WASI_STDIO_MODE_LOG + )); + let env = ctx.data(); + let memory = env.memory_view(&ctx); - // Add the process to the environment state - let bid = { - let mut guard = env.state.threading.lock().unwrap(); - guard.process_seed += 1; - let bid: WasiBusProcessId = guard.process_seed.into(); - guard.processes.insert(bid, process); - guard.process_reuse.insert(name, bid); - bid + let pid: WasiProcessId = handles.bid.into(); + let memory = env.memory_view(&ctx); + { + let mut inner = env.process.write(); + inner.bus_process_reuse.insert(name, pid); }; - wasi_try_mem_bus!(ret_bid.write(&memory, bid.into())); - - BusErrno::Success + wasi_try_mem_bus_ok!(ret_bid.write(&memory, pid.into())); + Ok(__BUS_ESUCCESS) } /// Closes a bus process and releases all associated resources @@ -3953,15 +6564,19 @@ fn bus_open_local_internal( /// ## Parameters /// /// * `bid` - Handle of the bus process handle to be closed -pub fn bus_close(ctx: FunctionEnvMut<'_, WasiEnv>, bid: Bid) -> BusErrno { - trace!("wasi::bus_close (bid={})", bid); - let bid: WasiBusProcessId = bid.into(); +pub fn bus_close(ctx: FunctionEnvMut<'_, WasiEnv>, bid: __wasi_bid_t) -> __bus_errno_t { + trace!("wasi[{}:{}]::bus_close (bid={})", ctx.data().pid(), ctx.data().tid(), bid); + let bid: WasiProcessId = bid.into(); let env = ctx.data(); - let mut guard = env.state.threading.lock().unwrap(); - guard.processes.remove(&bid); + let mut inner = env.process.write(); + if let Some(process) = inner.bus_processes.remove(&bid) { + // TODO: Fix this + //let name: Cow<'static, str> = process.name.clone().into(); + //inner.bus_process_reuse.remove(&name); + } - BusErrno::Unsupported + __BUS_ESUCCESS } /// Invokes a call within a running bus process. @@ -3977,28 +6592,103 @@ pub fn bus_close(ctx: FunctionEnvMut<'_, WasiEnv>, bid: Bid) -> BusErrno { /// * `buf` - The buffer where data to be transmitted is stored pub fn bus_call( ctx: FunctionEnvMut<'_, WasiEnv>, - bid: Bid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: M::Offset, - format: BusDataFormat, + bid: __wasi_bid_t, + topic_hash: WasmPtr<__wasi_hash_t>, + format: __wasi_busdataformat_t, buf: WasmPtr, buf_len: M::Offset, - ret_cid: WasmPtr, -) -> BusErrno { + ret_cid: WasmPtr<__wasi_cid_t, M>, +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); - let topic = unsafe { get_input_str_bus!(&memory, topic, topic_len) }; - let keep_alive = keep_alive == Bool::True; + let topic_hash = wasi_try_mem_bus_ok!(topic_hash.read(&memory)); + let buf_slice = wasi_try_mem_bus_ok!(buf.slice(&memory, buf_len)); trace!( - "wasi::bus_call (bid={}, topic={}, buf_len={})", + "wasi::bus_call (bid={}, buf_len={})", bid, - topic, buf_len ); - BusErrno::Unsupported + // Get the process that we'll invoke this call for + let mut guard = env.process.read(); + let bid: WasiProcessId = bid.into(); + let process = if let Some(process) = { + guard.bus_processes.get(&bid) + } { process } else { + return Ok(__BUS_EBADHANDLE); + }; + + // Invoke the bus process + let format = wasi_try_bus_ok!(conv_bus_format_from(format)); + + // Check if the process has finished + if let Some(code) = process.inst.exit_code() { + debug!("process has already exited (code = {})", code); + return Ok(__BUS_EABORTED); + } + + // Invoke the call + let buf = wasi_try_mem_bus_ok!(buf_slice.read_to_vec()); + let mut invoked = process.inst.invoke(topic_hash, format, buf); + drop(process); + drop(guard); + + // Poll the invocation until it does its thing + let mut invocation; + { + // Fast path (does not have to create a futex creation) + let waker = WasiDummyWaker.into_waker(); + let mut cx = Context::from_waker(&waker); + let pinned_invoked = Pin::new(invoked.deref_mut()); + match pinned_invoked.poll_invoked(&mut cx) { + Poll::Ready(i) => { + invocation = wasi_try_bus_ok!(i + .map_err(bus_error_into_wasi_err)); + }, + Poll::Pending => { + // Slow path (will put the thread to sleep) + let parking = WasiParkingLot::default(); + let waker = parking.get_waker(); + let mut cx = Context::from_waker(&waker); + loop { + let pinned_invoked = Pin::new(invoked.deref_mut()); + match pinned_invoked.poll_invoked(&mut cx) { + Poll::Ready(i) => { + invocation = wasi_try_bus_ok!(i + .map_err(bus_error_into_wasi_err)); + break; + }, + Poll::Pending => { + env.yield_now()?; + parking.wait(Duration::from_millis(5)); + } + } + } + } + } + } + + // Record the invocation + let cid = { + let mut guard = env.state.bus.protected(); + guard.call_seed += 1; + let cid = guard.call_seed; + guard.calls.insert(cid, WasiBusCall { + bid, + invocation + }); + cid + }; + + // Now we wake any BUS pollers so that they can drive forward the + // call to completion - when they poll the call they will also + // register a BUS waker + env.state.bus.poll_wake(); + + // Return the CID and success to the caller + wasi_try_mem_bus_ok!(ret_cid.write(&memory, cid)); + Ok(__BUS_ESUCCESS) } /// Invokes a call within the context of another call @@ -4014,28 +6704,122 @@ pub fn bus_call( /// * `buf` - The buffer where data to be transmitted is stored pub fn bus_subcall( ctx: FunctionEnvMut<'_, WasiEnv>, - parent: Cid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: M::Offset, - format: BusDataFormat, + parent: __wasi_cid_t, + topic_hash: WasmPtr<__wasi_hash_t>, + format: __wasi_busdataformat_t, buf: WasmPtr, buf_len: M::Offset, - ret_cid: WasmPtr, -) -> BusErrno { + ret_cid: WasmPtr<__wasi_cid_t, M>, +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); - let topic = unsafe { get_input_str_bus!(&memory, topic, topic_len) }; - let keep_alive = keep_alive == Bool::True; + let topic_hash = wasi_try_mem_bus_ok!(topic_hash.read(&memory)); + let buf_slice = wasi_try_mem_bus_ok!(buf.slice(&memory, buf_len)); trace!( - "wasi::bus_subcall (parent={}, topic={}, buf_len={})", + "wasi::bus_subcall (parent={}, buf_len={})", parent, - topic, buf_len ); - BusErrno::Unsupported + let format = wasi_try_bus_ok!(conv_bus_format_from(format)); + let buf = wasi_try_mem_bus_ok!(buf_slice.read_to_vec()); + + // Get the parent call that we'll invoke this call for + let mut guard = env.state.bus.protected(); + if let Some(parent) = guard.calls.get(&parent) + { + let bid = parent.bid.clone(); + + // Invoke the sub-call in the existing parent call + let mut invoked = parent.invocation.invoke(topic_hash, format, buf); + drop(parent); + drop(guard); + + // Poll the invocation until it does its thing + let invocation; + { + // Fast path (does not have to create a futex creation) + let waker = WasiDummyWaker.into_waker(); + let mut cx = Context::from_waker(&waker); + let pinned_invoked = Pin::new(invoked.deref_mut()); + match pinned_invoked.poll_invoked(&mut cx) { + Poll::Ready(i) => { + invocation = wasi_try_bus_ok!(i + .map_err(bus_error_into_wasi_err)); + }, + Poll::Pending => { + // Slow path (will put the thread to sleep) + let parking = WasiParkingLot::default(); + let waker = parking.get_waker(); + let mut cx = Context::from_waker(&waker); + loop { + let pinned_invoked = Pin::new(invoked.deref_mut()); + match pinned_invoked.poll_invoked(&mut cx) { + Poll::Ready(i) => { + invocation = wasi_try_bus_ok!(i + .map_err(bus_error_into_wasi_err)); + break; + }, + Poll::Pending => { + env.yield_now()?; + parking.wait(Duration::from_millis(5)); + } + } + } + } + } + } + + // Add the call and return the ID + let cid = { + let mut guard = env.state.bus.protected(); + guard.call_seed += 1; + let cid = guard.call_seed; + guard.calls.insert(cid, WasiBusCall { + bid, + invocation + }); + cid + }; + + // Now we wake any BUS pollers so that they can drive forward the + // call to completion - when they poll the call they will also + // register a BUS waker + env.state.bus.poll_wake(); + + // Return the CID and success to the caller + wasi_try_mem_bus_ok!(ret_cid.write(&memory, cid)); + Ok(__BUS_ESUCCESS) + } else { + Ok(__BUS_EBADHANDLE) + } +} + +// Function for converting the format +fn conv_bus_format(format: BusDataFormat) -> __wasi_busdataformat_t { + match format { + BusDataFormat::Raw => __WASI_BUS_DATA_FORMAT_RAW, + BusDataFormat::Bincode => __WASI_BUS_DATA_FORMAT_BINCODE, + BusDataFormat::MessagePack => __WASI_BUS_DATA_FORMAT_MESSAGE_PACK, + BusDataFormat::Json => __WASI_BUS_DATA_FORMAT_JSON, + BusDataFormat::Yaml => __WASI_BUS_DATA_FORMAT_YAML, + BusDataFormat::Xml => __WASI_BUS_DATA_FORMAT_XML, + } +} + +fn conv_bus_format_from(format: __wasi_busdataformat_t) -> Result { + Ok( + match format { + __WASI_BUS_DATA_FORMAT_RAW => BusDataFormat::Raw, + __WASI_BUS_DATA_FORMAT_BINCODE => BusDataFormat::Bincode, + __WASI_BUS_DATA_FORMAT_MESSAGE_PACK => BusDataFormat::MessagePack, + __WASI_BUS_DATA_FORMAT_JSON => BusDataFormat::Json, + __WASI_BUS_DATA_FORMAT_YAML => BusDataFormat::Yaml, + __WASI_BUS_DATA_FORMAT_XML => BusDataFormat::Xml, + _ => { return Err(__BUS_EDES); } + } + ) } /// Polls for any outstanding events from a particular @@ -4052,22 +6836,359 @@ pub fn bus_subcall( /// ## Return /// /// Returns the number of events that have occured +#[cfg(feature = "os")] pub fn bus_poll( ctx: FunctionEnvMut<'_, WasiEnv>, - timeout: Timestamp, - events: WasmPtr, - nevents: M::Offset, - malloc: WasmPtr, - malloc_len: M::Offset, + timeout: __wasi_timestamp_t, + events: WasmPtr<__wasi_busevent_t, M>, + maxevents: M::Offset, ret_nevents: WasmPtr, -) -> BusErrno { +) -> Result<__bus_errno_t, WasiError> { let env = ctx.data(); let bus = env.runtime.bus(); let memory = env.memory_view(&ctx); - let malloc = unsafe { get_input_str_bus!(&memory, malloc, malloc_len) }; - trace!("wasi::bus_poll (timeout={}, malloc={})", timeout, malloc); + trace!("wasi[{}:{}]::bus_poll (timeout={})", ctx.data().pid(), ctx.data().tid(), timeout); + + // Lets start by processing events for calls that are already running + let mut nevents = M::ZERO; + let events = wasi_try_mem_bus_ok!(events.slice(&memory, maxevents)); + + let state = env.state.clone(); + let start = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + loop + { + // The waker will wake this thread should any work arrive + // or need further processing (i.e. async operation) + let waker = state.bus.get_poll_waker(); + let mut cx = Context::from_waker(&waker); + + // Check if any of the processes have closed + let mut exited_bids = HashSet::new(); + { + let mut inner = env.process.write(); + for (pid, process) in inner.bus_processes.iter_mut() { + let pinned_process = Pin::new(process.inst.as_mut()); + if pinned_process.poll_finished(&mut cx) == Poll::Ready(()) { + exited_bids.insert(*pid); + } + } + for pid in exited_bids.iter() { + inner.bus_processes.remove(pid); + } + } + + { + // The waker will trigger the reactors when work arrives from the BUS + let mut guard = env.state.bus.protected(); + + // Function that hashes the topic using SHA256 + let hash_topic = |topic: Cow<'static, str>| -> __wasi_hash_t { + use sha2::{Sha256, Digest}; + let mut hasher = Sha256::new(); + hasher.update(&topic.bytes().collect::>()); + let hash: [u8; 16] = hasher.finalize()[..16].try_into().unwrap(); + u128::from_le_bytes(hash) + }; + + // Function that turns a buffer into a readable file handle + let buf_to_fd = { + let state = env.state.clone(); + let inodes = state.inodes.clone(); + move |data: Vec| -> __wasi_fd_t { + let mut inodes = inodes.write().unwrap(); + let inode = state.fs.create_inode_with_default_stat( + inodes.deref_mut(), + Kind::Buffer { buffer: data }, + false, + "bus".into(), + ); + let rights = super::state::bus_read_rights(); + wasi_try_bus!(state.fs.create_fd(rights, rights, 0, 0, inode) + .map_err(|err| { + debug!("failed to create file descriptor for BUS event buffer - {}", err); + __BUS_EALLOC + })) + } + }; + + // Grab all the events we can from all the existing calls up to the limit of + // maximum events that the user requested + if nevents < maxevents { + let mut drop_calls = Vec::new(); + let mut call_seed = guard.call_seed; + for (key, call) in guard.calls.iter_mut() { + let cid: __wasi_cid_t = (*key).into(); + + if nevents >= maxevents { + break; + } + + // If the process that is hosting the call is finished then so is the call + if exited_bids.contains(&call.bid) { + drop_calls.push(*key); + trace!("wasi[{}:{}]::bus_poll (aborted, cid={})", ctx.data().pid(), ctx.data().tid(), cid); + let evt = unsafe { + std::mem::transmute(__wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_FAULT, + u: __wasi_busevent_u { + fault: __wasi_busevent_fault_t { + cid, + err: __BUS_EABORTED + } + } + }) + }; + + let nevents64: u64 = wasi_try_bus_ok!(nevents.try_into().map_err(|_| __BUS_EINTERNAL)); + wasi_try_mem_bus_ok!(events.write(nevents64, evt)); + + nevents += M::ONE; + continue; + } + + // Otherwise lets poll for events + while nevents < maxevents { + let mut finished = false; + let call = Pin::new(call.invocation.as_mut()); + match call.poll_event(&mut cx) { + Poll::Ready(evt) => + { + let evt = match evt { + BusInvocationEvent::Callback { topic_hash, format, data } => { + let sub_cid = { + call_seed += 1; + call_seed + }; + + trace!("wasi[{}:{}]::bus_poll (callback, parent={}, cid={}, topic={})", ctx.data().pid(), ctx.data().tid(), cid, sub_cid, topic_hash); + __wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_CALL, + u: __wasi_busevent_u { + call: __wasi_busevent_call_t { + parent: __wasi_option_cid_t { + tag: __WASI_OPTION_SOME, + cid, + }, + cid: sub_cid, + format: conv_bus_format(format), + topic_hash, + fd: buf_to_fd(data), + } + } + } + }, + BusInvocationEvent::Response { format, data } => { + drop_calls.push(*key); + finished = true; + + trace!("wasi[{}:{}]::bus_poll (response, cid={}, len={})", ctx.data().pid(), ctx.data().tid(), cid, data.len()); + __wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_RESULT, + u: __wasi_busevent_u { + result: __wasi_busevent_result_t { + format: conv_bus_format(format), + cid, + fd: buf_to_fd(data), + } + } + } + }, + BusInvocationEvent::Fault { fault } => { + drop_calls.push(*key); + finished = true; + + trace!("wasi[{}:{}]::bus_poll (fault, cid={}, err={})", ctx.data().pid(), ctx.data().tid(), cid, fault); + __wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_FAULT, + u: __wasi_busevent_u { + fault: __wasi_busevent_fault_t { + cid, + err: bus_error_into_wasi_err(fault) + } + } + } + } + }; + let evt = unsafe { + std::mem::transmute(evt) + }; + + let nevents64: u64 = wasi_try_bus_ok!(nevents.try_into().map_err(|_| __BUS_EINTERNAL)); + wasi_try_mem_bus_ok!(events.write(nevents64, evt)); + + nevents += M::ONE; + + if finished { + break; + } + }, + Poll::Pending => { break; } + } + } + } + guard.call_seed = call_seed; + + // Drop any calls that are no longer in scope + if drop_calls.is_empty() == false { + for key in drop_calls { + guard.calls.remove(&key); + } + } + } + + if nevents < maxevents { + let mut call_seed = guard.call_seed; + let mut to_add = Vec::new(); + for (key, call) in guard.called.iter_mut() { + let cid: __wasi_cid_t = (*key).into(); + while nevents < maxevents { + let call = Pin::new(call.deref_mut()); + match call.poll(&mut cx) { + Poll::Ready(event) => { + // Register the call + let sub_cid = { + call_seed += 1; + to_add.push((call_seed, event.called)); + call_seed + }; + + let event = __wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_CALL, + u: __wasi_busevent_u { + call: __wasi_busevent_call_t { + parent: __wasi_option_cid_t { + tag: __WASI_OPTION_SOME, + cid, + }, + cid: sub_cid, + format: conv_bus_format(event.format), + topic_hash: event.topic_hash, + fd: buf_to_fd(event.data), + } + } + }; + let event = unsafe { + std::mem::transmute(event) + }; + + let nevents64: u64 = wasi_try_bus_ok!(nevents.try_into().map_err(|_| __BUS_EINTERNAL)); + wasi_try_mem_bus_ok!(events.write(nevents64, event)); + nevents += M::ONE; + }, + Poll::Pending => { break; } + }; + } + if nevents >= maxevents { + break; + } + } + + guard.call_seed = call_seed; + for (cid, called) in to_add { + guard.called.insert(cid, called); + } + } + + while nevents < maxevents + { + // Check the listener (if none exists then one is created) + let event = { + let bus = env.runtime.bus(); + let listener = wasi_try_bus_ok!(bus + .listen() + .map_err(bus_error_into_wasi_err)); + let listener = Pin::new(listener.deref()); + listener.poll(&mut cx) + }; + + // Process the event returned by the listener or exit the poll loop + let event = match event { + Poll::Ready(event) => { + + // Register the call + let sub_cid = { + guard.call_seed += 1; + let cid = guard.call_seed; + guard.called.insert(cid, event.called); + cid + }; + + __wasi_busevent_t2 { + tag: __WASI_BUS_EVENT_TYPE_CALL, + u: __wasi_busevent_u { + call: __wasi_busevent_call_t { + parent: __wasi_option_cid_t { + tag: __WASI_OPTION_NONE, + cid: 0, + }, + cid: sub_cid, + format: conv_bus_format(event.format), + topic_hash: event.topic_hash, + fd: buf_to_fd(event.data), + } + } + } + }, + Poll::Pending => { break; } + }; + let event = unsafe { + std::mem::transmute(event) + }; + + let nevents64: u64 = wasi_try_bus_ok!(nevents.try_into().map_err(|_| __BUS_EINTERNAL)); + wasi_try_mem_bus_ok!(events.write(nevents64, event)); + nevents += M::ONE; + } + } + + // If we still have no events + if nevents >= M::ONE { + break; + } + + // Every 100 milliseconds we check if the thread needs to terminate (via `env.yield_now`) + // otherwise the loop will break if the BUS futex is triggered or a timeout is reached + loop { + // Check for timeout (zero will mean the loop will not wait) + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + let delta = now.checked_sub(start).unwrap_or(0) as __wasi_timestamp_t; + if delta >= timeout { + trace!("wasi[{}:{}]::bus_poll (timeout)", ctx.data().pid(), ctx.data().tid()); + wasi_try_mem_bus_ok!(ret_nevents.write(&memory, nevents)); + return Ok(__BUS_ESUCCESS); + } + + env.yield_now()?; + + let remaining = timeout.checked_sub(delta).unwrap_or(0); + let interval = Duration::from_nanos(remaining) + .min(Duration::from_millis(5)) // we don't want the CPU burning + .max(Duration::from_millis(100)); // 100 milliseconds to kill worker threads seems acceptable + if state.bus.poll_wait(interval) == true { + break; + } + } + } + if nevents > M::ZERO { + trace!("wasi[{}:{}]::bus_poll (return nevents={})", ctx.data().pid(), ctx.data().tid(), nevents); + } else { + trace!("wasi[{}:{}]::bus_poll (idle - no events)", ctx.data().pid(), ctx.data().tid()); + } - BusErrno::Unsupported + wasi_try_mem_bus_ok!(ret_nevents.write(&memory, nevents)); + Ok(__BUS_ESUCCESS) +} + +#[cfg(not(feature = "os"))] +pub fn bus_poll( + ctx: FunctionEnvMut<'_, WasiEnv>, + timeout: __wasi_timestamp_t, + events: WasmPtr<__wasi_busevent_t, M>, + maxevents: M::Offset, + ret_nevents: WasmPtr, +) -> Result<__bus_errno_t, WasiError> { + trace!("wasi[{}:{}]::bus_poll (timeout={}) is not supported without 'os' feature", ctx.data().pid(), ctx.data().tid(), timeout); + Ok(__BUS_EUNSUPPORTED) } /// Replies to a call that was made to this process @@ -4088,6 +7209,7 @@ pub fn call_reply( buf_len: M::Offset, ) -> BusErrno { let env = ctx.data(); + let memory = env.memory_view(&ctx); let bus = env.runtime.bus(); trace!( "wasi::call_reply (cid={}, format={}, data_len={})", @@ -4095,8 +7217,19 @@ pub fn call_reply( format, buf_len ); + let buf_slice = wasi_try_mem_bus!(buf.slice(&memory, buf_len)); + let buf = wasi_try_mem_bus!(buf_slice.read_to_vec()); - BusErrno::Unsupported + let mut guard = env.state.bus.protected(); + if let Some(call) = guard.called.remove(&cid) { + drop(guard); + + let format = wasi_try_bus!(conv_bus_format_from(format)); + call.reply(format, buf); + __BUS_ESUCCESS + } else { + __BUS_EBADHANDLE + } } /// Causes a fault on a particular call that was made @@ -4107,12 +7240,22 @@ pub fn call_reply( /// /// * `cid` - Handle of the call to raise a fault on /// * `fault` - Fault to be raised on the bus -pub fn call_fault(ctx: FunctionEnvMut<'_, WasiEnv>, cid: Cid, fault: BusErrno) -> BusErrno { +pub fn call_fault( + ctx: FunctionEnvMut<'_, WasiEnv>, + cid: __wasi_cid_t, + fault: __bus_errno_t) +{ let env = ctx.data(); let bus = env.runtime.bus(); - debug!("wasi::call_fault (cid={}, fault={})", cid, fault); + debug!("wasi[{}:{}]::call_fault (cid={}, fault={})", ctx.data().pid(), ctx.data().tid(), cid, fault); + + let mut guard = env.state.bus.protected(); + guard.calls.remove(&cid); - BusErrno::Unsupported + if let Some(call) = guard.called.remove(&cid) { + drop(guard); + call.fault(wasi_error_into_bus_err(fault)); + } } /// Closes a bus call based on its bus call handle @@ -4120,12 +7263,17 @@ pub fn call_fault(ctx: FunctionEnvMut<'_, WasiEnv>, cid: Cid, fault: BusErrno) - /// ## Parameters /// /// * `cid` - Handle of the bus call handle to be dropped -pub fn call_close(ctx: FunctionEnvMut<'_, WasiEnv>, cid: Cid) -> BusErrno { +pub fn call_close( + ctx: FunctionEnvMut<'_, WasiEnv>, + cid: __wasi_cid_t +) { let env = ctx.data(); let bus = env.runtime.bus(); - trace!("wasi::call_close (cid={})", cid); + trace!("wasi[{}:{}]::call_close (cid={})", ctx.data().pid(), ctx.data().tid(), cid); - BusErrno::Unsupported + let mut guard = env.state.bus.protected(); + guard.calls.remove(&cid); + guard.called.remove(&cid); } /// ### `ws_connect()` @@ -4142,17 +7290,25 @@ pub fn ws_connect( ctx: FunctionEnvMut<'_, WasiEnv>, url: WasmPtr, url_len: M::Offset, - ret_sock: WasmPtr, -) -> Errno { - debug!("wasi::ws_connect"); + ret_sock: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::ws_connect", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let url = unsafe { get_input_str!(&memory, url, url_len) }; - let socket = wasi_try!(env - .net() - .ws_connect(url.as_str()) - .map_err(net_error_into_wasi_err)); + let net = env.net(); + let tasks = env.tasks.clone(); + let socket = wasi_try!( + __asyncify( + tasks, + &env.thread, + None, + async move { + net.ws_connect(url.as_str()).await.map_err(net_error_into_wasi_err) + } + ) + ); let (memory, state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -4164,7 +7320,7 @@ pub fn ws_connect( inodes.deref_mut(), kind, false, - "socket".to_string(), + "socket".into(), ); let rights = Rights::all_socket(); let fd = wasi_try!(state @@ -4200,10 +7356,10 @@ pub fn http_request( method_len: M::Offset, headers: WasmPtr, headers_len: M::Offset, - gzip: Bool, - ret_handles: WasmPtr, -) -> Errno { - debug!("wasi::http_request"); + gzip: __wasi_bool_t, + ret_handles: WasmPtr<__wasi_http_handles_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::http_request", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let url = unsafe { get_input_str!(&memory, url, url_len) }; @@ -4216,10 +7372,18 @@ pub fn http_request( _ => return Errno::Inval, }; - let socket = wasi_try!(env - .net() - .http_request(url.as_str(), method.as_str(), headers.as_str(), gzip) - .map_err(net_error_into_wasi_err)); + let net = env.net(); + let tasks = env.tasks.clone(); + let socket = wasi_try!( + __asyncify( + tasks, + &env.thread, + None, + async move { + net.http_request(url.as_str(), method.as_str(), headers.as_str(), gzip).await.map_err(net_error_into_wasi_err) + } + ) + ); let socket_req = SocketHttpRequest { request: socket.request, response: None, @@ -4264,19 +7428,19 @@ pub fn http_request( inodes.deref_mut(), kind_req, false, - "http_request".to_string(), + "http_request".into(), ); let inode_res = state.fs.create_inode_with_default_stat( inodes.deref_mut(), kind_res, false, - "http_response".to_string(), + "http_response".into(), ); let inode_hdr = state.fs.create_inode_with_default_stat( inodes.deref_mut(), kind_hdr, false, - "http_headers".to_string(), + "http_headers".into(), ); let rights = Rights::all_socket(); @@ -4307,16 +7471,16 @@ pub fn http_request( /// status of this HTTP request pub fn http_status( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - status: WasmPtr, -) -> Errno { - debug!("wasi::http_status"); + sock: __wasi_fd_t, + status: WasmPtr<__wasi_http_status_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::http_status", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let ref_status = status.deref(&memory); - let http_status = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { + let http_status = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.http_status() })); @@ -4350,9 +7514,9 @@ pub fn port_bridge( network_len: M::Offset, token: WasmPtr, token_len: M::Offset, - security: Streamsecurity, -) -> Errno { - debug!("wasi::port_bridge"); + security: __wasi_streamsecurity_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_bridge", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let network = unsafe { get_input_str!(&memory, network, network_len) }; @@ -4374,8 +7538,8 @@ pub fn port_bridge( /// ### `port_unbridge()` /// Disconnects from a remote network -pub fn port_unbridge(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { - debug!("wasi::port_unbridge"); +pub fn port_unbridge(ctx: FunctionEnvMut<'_, WasiEnv>) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_unbridge", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); wasi_try!(env.net().unbridge().map_err(net_error_into_wasi_err)); Errno::Success @@ -4383,11 +7547,22 @@ pub fn port_unbridge(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { /// ### `port_dhcp_acquire()` /// Acquires a set of IP addresses using DHCP -pub fn port_dhcp_acquire(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { - debug!("wasi::port_dhcp_acquire"); +pub fn port_dhcp_acquire(ctx: FunctionEnvMut<'_, WasiEnv>) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_dhcp_acquire", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); - wasi_try!(env.net().dhcp_acquire().map_err(net_error_into_wasi_err)); - Errno::Success + let net = env.net(); + let tasks = env.tasks.clone(); + wasi_try!( + __asyncify( + tasks, + &env.thread, + None, + async move { + net.dhcp_acquire().await.map_err(net_error_into_wasi_err) + } + ) + ); + __WASI_ESUCCESS } /// ### `port_addr_add()` @@ -4399,8 +7574,8 @@ pub fn port_dhcp_acquire(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { pub fn port_addr_add( ctx: FunctionEnvMut<'_, WasiEnv>, ip: WasmPtr<__wasi_cidr_t, M>, -) -> Errno { - debug!("wasi::port_addr_add"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_addr_add", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let cidr = wasi_try!(super::state::read_cidr(&memory, ip)); @@ -4420,8 +7595,8 @@ pub fn port_addr_add( pub fn port_addr_remove( ctx: FunctionEnvMut<'_, WasiEnv>, ip: WasmPtr<__wasi_addr_t, M>, -) -> Errno { - debug!("wasi::port_addr_remove"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_addr_remove", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let ip = wasi_try!(super::state::read_ip(&memory, ip)); @@ -4431,8 +7606,8 @@ pub fn port_addr_remove( /// ### `port_addr_clear()` /// Clears all the addresses on the local port -pub fn port_addr_clear(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { - debug!("wasi::port_addr_clear"); +pub fn port_addr_clear(ctx: FunctionEnvMut<'_, WasiEnv>) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_addr_clear", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); wasi_try!(env.net().ip_clear().map_err(net_error_into_wasi_err)); Errno::Success @@ -4443,8 +7618,8 @@ pub fn port_addr_clear(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { pub fn port_mac( ctx: FunctionEnvMut<'_, WasiEnv>, ret_mac: WasmPtr<__wasi_hardwareaddress_t, M>, -) -> Errno { - debug!("wasi::port_mac"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_mac", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let mac = wasi_try!(env.net().mac().map_err(net_error_into_wasi_err)); @@ -4470,8 +7645,8 @@ pub fn port_addr_list( ctx: FunctionEnvMut<'_, WasiEnv>, addrs: WasmPtr<__wasi_cidr_t, M>, naddrs: WasmPtr, -) -> Errno { - debug!("wasi::port_addr_list"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_addr_list", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let max_addrs = wasi_try_mem!(naddrs.read(&memory)); @@ -4504,8 +7679,8 @@ pub fn port_addr_list( pub fn port_gateway_set( ctx: FunctionEnvMut<'_, WasiEnv>, ip: WasmPtr<__wasi_addr_t, M>, -) -> Errno { - debug!("wasi::port_gateway_set"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_gateway_set", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let ip = wasi_try!(super::state::read_ip(&memory, ip)); @@ -4520,10 +7695,10 @@ pub fn port_route_add( ctx: FunctionEnvMut<'_, WasiEnv>, cidr: WasmPtr<__wasi_cidr_t, M>, via_router: WasmPtr<__wasi_addr_t, M>, - preferred_until: WasmPtr, - expires_at: WasmPtr, -) -> Errno { - debug!("wasi::port_route_add"); + preferred_until: WasmPtr<__wasi_option_timestamp_t, M>, + expires_at: WasmPtr<__wasi_option_timestamp_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_route_add", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let cidr = wasi_try!(super::state::read_cidr(&memory, cidr)); @@ -4553,8 +7728,8 @@ pub fn port_route_add( pub fn port_route_remove( ctx: FunctionEnvMut<'_, WasiEnv>, ip: WasmPtr<__wasi_addr_t, M>, -) -> Errno { - debug!("wasi::port_route_remove"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_route_remove", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let ip = wasi_try!(super::state::read_ip(&memory, ip)); @@ -4564,8 +7739,8 @@ pub fn port_route_remove( /// ### `port_route_clear()` /// Clears all the routes in the local port -pub fn port_route_clear(ctx: FunctionEnvMut<'_, WasiEnv>) -> Errno { - debug!("wasi::port_route_clear"); +pub fn port_route_clear(ctx: FunctionEnvMut<'_, WasiEnv>) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_route_clear", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); wasi_try!(env.net().route_clear().map_err(net_error_into_wasi_err)); Errno::Success @@ -4584,15 +7759,16 @@ pub fn port_route_list( ctx: FunctionEnvMut<'_, WasiEnv>, routes: WasmPtr, nroutes: WasmPtr, -) -> Errno { - debug!("wasi::port_route_list"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::port_route_list", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let memory = env.memory_view(&ctx); let nroutes = nroutes.deref(&memory); let max_routes: usize = wasi_try!(wasi_try_mem!(nroutes.read()) .try_into() - .map_err(|_| Errno::Inval)); - let ref_routes = wasi_try_mem!(routes.slice(&memory, wasi_try!(to_offset::(max_routes)))); + .map_err(|_| __WASI_EINVAL)); + let ref_routes = + wasi_try_mem!(routes.slice(&memory, wasi_try!(to_offset::(max_routes)))); let routes = wasi_try!(env.net().route_list().map_err(net_error_into_wasi_err)); @@ -4621,8 +7797,12 @@ pub fn port_route_list( /// ## Parameters /// /// * `how` - Which channels on the socket to shut down. -pub fn sock_shutdown(ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, how: SdFlags) -> Errno { - debug!("wasi::sock_shutdown"); +pub fn sock_shutdown( + ctx: FunctionEnvMut<'_, WasiEnv>, + sock: __wasi_fd_t, + how: __wasi_sdflags_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_shutdown (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let both = __WASI_SHUT_RD | __WASI_SHUT_WR; let how = match how { @@ -4635,8 +7815,10 @@ pub fn sock_shutdown(ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, how: SdFlag wasi_try!(__sock_actor_mut( &ctx, sock, - Rights::SOCK_SHUTDOWN, - |socket| { socket.shutdown(how) } + __WASI_RIGHT_SOCK_SHUTDOWN, + move |socket| async move { + socket.shutdown(how).await + } )); Errno::Success @@ -4646,14 +7828,12 @@ pub fn sock_shutdown(ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, how: SdFlag /// Returns the current status of a socket pub fn sock_status( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - ret_status: WasmPtr, -) -> Errno { - debug!("wasi::sock_status"); + sock: __wasi_fd_t, + ret_status: WasmPtr<__wasi_sockstatus_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_status (fd={})", ctx.data().pid(), ctx.data().tid(), sock); - let status = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { - socket.status() - })); + let status = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.status() })); use super::state::WasiSocketStatus; let status = match status { @@ -4685,10 +7865,10 @@ pub fn sock_addr_local( ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, ret_addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { - debug!("wasi::sock_addr_local"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_addr_local (fd={})", ctx.data().pid(), ctx.data().tid(), sock); - let addr = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { + let addr = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.addr_local() })); let memory = ctx.data().memory_view(&ctx); @@ -4716,13 +7896,11 @@ pub fn sock_addr_peer( ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, ro_addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { - debug!("wasi::sock_addr_peer"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_addr_peer (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); - let addr = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { - socket.addr_peer() - })); + let addr = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.addr_peer() })); let memory = env.memory_view(&ctx); wasi_try!(super::state::write_ip_port( &memory, @@ -4754,12 +7932,12 @@ pub fn sock_addr_peer( /// The file descriptor of the socket that has been opened. pub fn sock_open( ctx: FunctionEnvMut<'_, WasiEnv>, - af: Addressfamily, - ty: Socktype, - pt: SockProto, - ro_sock: WasmPtr, -) -> Errno { - debug!("wasi::sock_open"); + af: __wasi_addressfamily_t, + ty: __wasi_socktype_t, + pt: __wasi_sockproto_t, + ro_sock: WasmPtr<__wasi_fd_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_open", ctx.data().pid(), ctx.data().tid()); let env = ctx.data(); let (memory, state, mut inodes) = env.get_memory_and_wasi_state_and_inodes_mut(&ctx, 0); @@ -4774,6 +7952,7 @@ pub fn sock_open( only_v6: false, reuse_port: false, reuse_addr: false, + nonblocking: false, send_buf_size: None, recv_buf_size: None, send_timeout: None, @@ -4789,7 +7968,7 @@ pub fn sock_open( inodes.deref_mut(), kind, false, - "socket".to_string(), + "socket".into(), ); let rights = Rights::all_socket(); let fd = wasi_try!(state @@ -4812,11 +7991,11 @@ pub fn sock_open( /// * `flag` - Value to set the option to pub fn sock_set_opt_flag( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - flag: Bool, -) -> Errno { - debug!("wasi::sock_set_opt_flag(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + flag: __wasi_bool_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_set_opt_flag(fd={}, ty={}, flag={})", ctx.data().pid(), ctx.data().tid(), sock, opt, flag); let flag = match flag { Bool::False => false, @@ -4825,7 +8004,7 @@ pub fn sock_set_opt_flag( }; let option: super::state::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { socket.set_opt_flag(option, flag) })); Errno::Success @@ -4841,16 +8020,16 @@ pub fn sock_set_opt_flag( /// * `sockopt` - Socket option to be retrieved pub fn sock_get_opt_flag( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - ret_flag: WasmPtr, -) -> Errno { - debug!("wasi::sock_get_opt_flag(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + ret_flag: WasmPtr<__wasi_bool_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_get_opt_flag(fd={}, ty={})", ctx.data().pid(), ctx.data().tid(), sock, opt); let env = ctx.data(); let memory = env.memory_view(&ctx); let option: super::state::WasiSocketOption = opt.into(); - let flag = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { + let flag = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.get_opt_flag(option) })); let flag = match flag { @@ -4873,11 +8052,11 @@ pub fn sock_get_opt_flag( /// * `time` - Value to set the time to pub fn sock_set_opt_time( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - time: WasmPtr, -) -> Errno { - debug!("wasi::sock_set_opt_time(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + time: WasmPtr<__wasi_option_timestamp_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_set_opt_time(fd={}, ty={})", ctx.data().pid(), ctx.data().tid(), sock, opt); let env = ctx.data(); let memory = env.memory_view(&ctx); @@ -4898,7 +8077,7 @@ pub fn sock_set_opt_time( }; let option: super::state::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { socket.set_opt_time(ty, time) })); Errno::Success @@ -4913,11 +8092,11 @@ pub fn sock_set_opt_time( /// * `sockopt` - Socket option to be retrieved pub fn sock_get_opt_time( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - ret_time: WasmPtr, -) -> Errno { - debug!("wasi::sock_get_opt_time(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + ret_time: WasmPtr<__wasi_option_timestamp_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_get_opt_time(fd={}, ty={})", ctx.data().pid(), ctx.data().tid(), sock, opt); let env = ctx.data(); let memory = env.memory_view(&ctx); @@ -4930,7 +8109,7 @@ pub fn sock_get_opt_time( _ => return Errno::Inval, }; - let time = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { + let time = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { socket.opt_time(ty) })); let time = match time { @@ -4960,11 +8139,11 @@ pub fn sock_get_opt_time( /// * `size` - Buffer size pub fn sock_set_opt_size( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - size: Filesize, -) -> Errno { - debug!("wasi::sock_set_opt_size(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + size: __wasi_filesize_t, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_set_opt_size(fd={}, ty={})", ctx.data().pid(), ctx.data().tid(), sock, opt); let ty = match opt { Sockoption::RecvTimeout => wasmer_vnet::TimeType::ReadTimeout, @@ -4976,7 +8155,7 @@ pub fn sock_set_opt_size( }; let option: super::state::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { match opt { Sockoption::RecvBufSize => socket.set_recv_buf_size(size as usize), Sockoption::SendBufSize => socket.set_send_buf_size(size as usize), @@ -4998,15 +8177,15 @@ pub fn sock_set_opt_size( /// * `sockopt` - Socket option to be retrieved pub fn sock_get_opt_size( ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - opt: Sockoption, - ret_size: WasmPtr, -) -> Errno { - debug!("wasi::sock_get_opt_size(ty={})", opt); + sock: __wasi_fd_t, + opt: __wasi_sockoption_t, + ret_size: WasmPtr<__wasi_filesize_t, M>, +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_get_opt_size(fd={}, ty={})", ctx.data().pid(), ctx.data().tid(), sock, opt); let env = ctx.data(); let memory = env.memory_view(&ctx); - let size = wasi_try!(__sock_actor(&ctx, sock, Rights::empty(), |socket| { + let size = wasi_try!(__sock_actor(&ctx, sock, 0, move |socket| async move { match opt { Sockoption::RecvBufSize => socket.recv_buf_size().map(|a| a as Filesize), Sockoption::SendBufSize => socket.send_buf_size().map(|a| a as Filesize), @@ -5033,15 +8212,15 @@ pub fn sock_join_multicast_v4( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip4_t, M>, iface: WasmPtr<__wasi_addr_ip4_t, M>, -) -> Errno { - debug!("wasi::sock_join_multicast_v4"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_join_multicast_v4 (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let multiaddr = wasi_try!(super::state::read_ip_v4(&memory, multiaddr)); let iface = wasi_try!(super::state::read_ip_v4(&memory, iface)); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { - socket.join_multicast_v4(multiaddr, iface) + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { + socket.join_multicast_v4(multiaddr, iface).await })); Errno::Success } @@ -5059,15 +8238,15 @@ pub fn sock_leave_multicast_v4( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip4_t, M>, iface: WasmPtr<__wasi_addr_ip4_t, M>, -) -> Errno { - debug!("wasi::sock_leave_multicast_v4"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_leave_multicast_v4 (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let multiaddr = wasi_try!(super::state::read_ip_v4(&memory, multiaddr)); let iface = wasi_try!(super::state::read_ip_v4(&memory, iface)); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { - socket.leave_multicast_v4(multiaddr, iface) + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { + socket.leave_multicast_v4(multiaddr, iface).await })); Errno::Success } @@ -5085,14 +8264,14 @@ pub fn sock_join_multicast_v6( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip6_t, M>, iface: u32, -) -> Errno { - debug!("wasi::sock_join_multicast_v6"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_join_multicast_v6 (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let multiaddr = wasi_try!(super::state::read_ip_v6(&memory, multiaddr)); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { - socket.join_multicast_v6(multiaddr, iface) + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { + socket.join_multicast_v6(multiaddr, iface).await })); Errno::Success } @@ -5110,14 +8289,14 @@ pub fn sock_leave_multicast_v6( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip6_t, M>, iface: u32, -) -> Errno { - debug!("wasi::sock_leave_multicast_v6"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_leave_multicast_v6 (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let multiaddr = wasi_try!(super::state::read_ip_v6(&memory, multiaddr)); - wasi_try!(__sock_actor_mut(&ctx, sock, Rights::empty(), |socket| { - socket.leave_multicast_v6(multiaddr, iface) + wasi_try!(__sock_actor_mut(&ctx, sock, 0, move |socket| async move { + socket.leave_multicast_v6(multiaddr, iface).await })); Errno::Success } @@ -5134,17 +8313,23 @@ pub fn sock_bind( ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { - debug!("wasi::sock_bind"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_bind (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let addr = wasi_try!(super::state::read_ip_port(&memory, addr)); let addr = SocketAddr::new(addr.0, addr.1); - wasi_try!(__sock_upgrade(&ctx, sock, Rights::SOCK_BIND, |socket| { - socket.bind(env.net(), addr) - })); - Errno::Success + let net = env.net(); + wasi_try!(__sock_upgrade( + &ctx, + sock, + __WASI_RIGHT_SOCK_BIND, + move |socket| async move { + socket.bind(net, addr).await + } + )); + __WASI_ESUCCESS } /// ### `sock_listen()` @@ -5163,15 +8348,21 @@ pub fn sock_listen( ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, backlog: M::Offset, -) -> Errno { - debug!("wasi::sock_listen"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_listen (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); - let backlog: usize = wasi_try!(backlog.try_into().map_err(|_| Errno::Inval)); - wasi_try!(__sock_upgrade(&ctx, sock, Rights::SOCK_BIND, |socket| { - socket.listen(env.net(), backlog) - })); - Errno::Success + let net = env.net(); + let backlog: usize = wasi_try!(backlog.try_into().map_err(|_| __WASI_EINVAL)); + wasi_try!(__sock_upgrade( + &ctx, + sock, + __WASI_RIGHT_SOCK_BIND, + move |socket| async move { + socket.listen(net, backlog).await + } + )); + __WASI_ESUCCESS } /// ### `sock_accept()` @@ -5187,33 +8378,49 @@ pub fn sock_listen( /// /// New socket connection pub fn sock_accept( - ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - fd_flags: Fdflags, - ro_fd: WasmPtr, + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sock: __wasi_fd_t, + fd_flags: __wasi_fdflags_t, + ro_fd: WasmPtr<__wasi_fd_t, M>, ro_addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Result { - debug!("wasi::sock_accept"); +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::sock_accept (fd={})", ctx.data().pid(), ctx.data().tid(), sock); - let env = ctx.data(); + let mut env = ctx.data(); let (child, addr) = { let mut ret; let (_, state) = env.get_memory_and_wasi_state(&ctx, 0); + let nonblocking = wasi_try_ok!(__sock_actor(&ctx, sock, __WASI_RIGHT_SOCK_ACCEPT, move |socket| async move { + socket.nonblocking() + })); loop { wasi_try_ok!( - match __sock_actor(&ctx, sock, Rights::SOCK_ACCEPT, |socket| socket - .accept_timeout(fd_flags, Duration::from_millis(5))) + match __sock_actor(&ctx, sock, __WASI_RIGHT_SOCK_ACCEPT, move |socket| async move { + socket.set_nonblocking(true); + let ret = socket.accept(fd_flags).await; + socket.set_nonblocking(nonblocking); + ret + }) { Ok(a) => { ret = a; break; } - Err(Errno::Timedout) => { + Err(__WASI_ETIMEDOUT) => { + if nonblocking { + trace!("wasi[{}:{}]::sock_accept - (ret=EAGAIN)", ctx.data().pid(), ctx.data().tid()); + return Ok(__WASI_EAGAIN); + } env.yield_now()?; continue; } - Err(Errno::Again) => { - env.sleep(Duration::from_millis(5))?; + Err(__WASI_EAGAIN) => { + if nonblocking { + trace!("wasi[{}:{}]::sock_accept - (ret=EAGAIN)", ctx.data().pid(), ctx.data().tid()); + return Ok(__WASI_EAGAIN); + } + env.clone().sleep(&mut ctx, Duration::from_millis(5))?; + env = ctx.data(); continue; } Err(err) => Err(err), @@ -5232,7 +8439,7 @@ pub fn sock_accept( inodes.deref_mut(), kind, false, - "socket".to_string(), + "socket".into(), ); let rights = Rights::all_socket(); @@ -5240,6 +8447,8 @@ pub fn sock_accept( .fs .create_fd(rights, rights, Fdflags::empty(), 0, inode)); + debug!("wasi[{}:{}]::sock_accept (ret=ESUCCESS, peer={})", ctx.data().pid(), ctx.data().tid(), fd); + wasi_try_mem_ok!(ro_fd.write(&memory, fd)); wasi_try_ok!(super::state::write_ip_port( &memory, @@ -5264,20 +8473,26 @@ pub fn sock_accept( /// * `fd` - Socket descriptor /// * `addr` - Address of the socket to connect to pub fn sock_connect( - ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, + ctx: FunctionEnvMut, + sock: __wasi_fd_t, addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { - debug!("wasi::sock_connect"); +) -> __wasi_errno_t { + debug!("wasi[{}:{}]::sock_connect (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); + let net = env.net(); let memory = env.memory_view(&ctx); let addr = wasi_try!(super::state::read_ip_port(&memory, addr)); let addr = SocketAddr::new(addr.0, addr.1); - wasi_try!(__sock_upgrade(&ctx, sock, Rights::SOCK_CONNECT, |socket| { - socket.connect(env.net(), addr) - })); - Errno::Success + wasi_try!(__sock_upgrade( + &ctx, + sock, + __WASI_RIGHT_SOCK_CONNECT, + move |socket| async move { + socket.connect(net, addr).await + } + )); + __WASI_ESUCCESS } /// ### `sock_recv()` @@ -5300,18 +8515,33 @@ pub fn sock_recv( ri_data_len: M::Offset, _ri_flags: RiFlags, ro_data_len: WasmPtr, - ro_flags: WasmPtr, -) -> Result { - debug!("wasi::sock_recv"); + ro_flags: WasmPtr<__wasi_roflags_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::sock_recv (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let iovs_arr = wasi_try_mem_ok!(ri_data.slice(&memory, ri_data_len)); - let bytes_read = wasi_try_ok!(__sock_actor_mut(&ctx, sock, Rights::SOCK_RECV, |socket| { - socket.recv(&memory, iovs_arr) - })); - let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| Errno::Overflow)); + let mut max_size = 0usize; + for iovs in iovs_arr.iter() { + let iovs = wasi_try_mem_ok!(iovs.read()); + let buf_len: usize = wasi_try_ok!(iovs.buf_len.try_into().map_err(|_| __WASI_EOVERFLOW)); + max_size += buf_len; + } + + let data = wasi_try_ok!(__sock_actor_mut( + &ctx, + sock, + __WASI_RIGHT_SOCK_RECV, + move |socket| async move { + socket.recv(max_size).await + } + )); + let data_len = data.len(); + let mut reader = &data[..]; + let bytes_read = wasi_try_ok!(read_bytes(reader, &memory, iovs_arr).map(|_| data_len)); + let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| __WASI_EOVERFLOW)); wasi_try_mem_ok!(ro_flags.write(&memory, 0)); wasi_try_mem_ok!(ro_data_len.write(&memory, bytes_read)); @@ -5341,20 +8571,36 @@ pub fn sock_recv_from( ro_data_len: WasmPtr, ro_flags: WasmPtr, ro_addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Result { - debug!("wasi::sock_recv_from"); +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::sock_recv_from (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let iovs_arr = wasi_try_mem_ok!(ri_data.slice(&memory, ri_data_len)); - let bytes_read = wasi_try_ok!(__sock_actor_mut( + let mut max_size = 0usize; + for iovs in iovs_arr.iter() { + let iovs = wasi_try_mem_ok!(iovs.read()); + let buf_len: usize = wasi_try_ok!(iovs.buf_len.try_into().map_err(|_| __WASI_EOVERFLOW)); + max_size += buf_len; + } + + let (data, peer) = wasi_try_ok!(__sock_actor_mut( &ctx, sock, - Rights::SOCK_RECV_FROM, - |socket| { socket.recv_from(&memory, iovs_arr, ro_addr) } + __WASI_RIGHT_SOCK_RECV_FROM, + move |socket| async move + { + socket.recv_from(max_size).await + } )); - let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| Errno::Overflow)); + + wasi_try_ok!(write_ip_port(&memory, ro_addr, peer.ip(), peer.port())); + + let data_len = data.len(); + let mut reader = &data[..]; + let bytes_read = wasi_try_ok!(read_bytes(reader, &memory, iovs_arr).map(|_| data_len)); + let bytes_read: M::Offset = wasi_try_ok!(bytes_read.try_into().map_err(|_| __WASI_EOVERFLOW)); wasi_try_mem_ok!(ro_flags.write(&memory, 0)); wasi_try_mem_ok!(ro_data_len.write(&memory, bytes_read)); @@ -5382,16 +8628,32 @@ pub fn sock_send( si_data_len: M::Offset, _si_flags: SiFlags, ret_data_len: WasmPtr, -) -> Result { - debug!("wasi::sock_send"); +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::sock_send (fd={})", ctx.data().pid(), ctx.data().tid(), sock); + let env = ctx.data(); + let runtime = env.runtime.clone(); let memory = env.memory_view(&ctx); let iovs_arr = wasi_try_mem_ok!(si_data.slice(&memory, si_data_len)); - let bytes_written = wasi_try_ok!(__sock_actor_mut(&ctx, sock, Rights::SOCK_SEND, |socket| { - socket.send(&memory, iovs_arr) - })); + let buf_len: M::Offset = iovs_arr + .iter() + .filter_map(|a| a.read().ok()) + .map(|a| a.buf_len) + .sum(); + let buf_len: usize = wasi_try_ok!(buf_len.try_into().map_err(|_| __WASI_EINVAL)); + let mut buf = Vec::with_capacity(buf_len); + wasi_try_ok!(write_bytes(&mut buf, &memory, iovs_arr)); + + let bytes_written = wasi_try_ok!(__sock_actor_mut( + &ctx, + sock, + __WASI_RIGHT_SOCK_SEND, + move |socket| async move { + socket.send(buf).await + } + )); let bytes_written: M::Offset = wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); @@ -5422,18 +8684,32 @@ pub fn sock_send_to( _si_flags: SiFlags, addr: WasmPtr<__wasi_addr_port_t, M>, ret_data_len: WasmPtr, -) -> Result { - debug!("wasi::sock_send_to"); +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::sock_send_to (fd={})", ctx.data().pid(), ctx.data().tid(), sock); let env = ctx.data(); let memory = env.memory_view(&ctx); let iovs_arr = wasi_try_mem_ok!(si_data.slice(&memory, si_data_len)); + let buf_len: M::Offset = iovs_arr + .iter() + .filter_map(|a| a.read().ok()) + .map(|a| a.buf_len) + .sum(); + let buf_len: usize = wasi_try_ok!(buf_len.try_into().map_err(|_| __WASI_EINVAL)); + let mut buf = Vec::with_capacity(buf_len); + wasi_try_ok!(write_bytes(&mut buf, &memory, iovs_arr)); + + let (addr_ip, addr_port) = wasi_try_ok!(read_ip_port(&memory, addr)); + let addr = SocketAddr::new(addr_ip, addr_port); + let bytes_written = wasi_try_ok!(__sock_actor_mut( &ctx, sock, - Rights::SOCK_SEND_TO, - |socket| { socket.send_to::(&memory, iovs_arr, addr) } + __WASI_RIGHT_SOCK_SEND_TO, + move |socket| async move { + socket.send_to::(buf, addr).await + } )); let bytes_written: M::Offset = @@ -5455,23 +8731,25 @@ pub fn sock_send_to( /// ## Return /// /// Number of bytes transmitted. -pub unsafe fn sock_send_file( - mut ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - in_fd: WasiFd, - offset: Filesize, - mut count: Filesize, - ret_sent: WasmPtr, -) -> Result { - debug!("wasi::send_file"); +pub fn sock_send_file( + ctx: FunctionEnvMut<'_, WasiEnv>, + sock: __wasi_fd_t, + in_fd: __wasi_fd_t, + offset: __wasi_filesize_t, + mut count: __wasi_filesize_t, + ret_sent: WasmPtr<__wasi_filesize_t, M>, +) -> Result<__wasi_errno_t, WasiError> { + debug!("wasi[{}:{}]::send_file (fd={}, file_fd={})", ctx.data().pid(), ctx.data().tid(), sock, in_fd); let env = ctx.data(); + let net = env.net(); + let tasks = env.tasks.clone(); let (memory, mut state, inodes) = env.get_memory_and_wasi_state_and_inodes(&ctx, 0); // Set the offset of the file { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); - fd_entry.offset = offset as u64; + let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(__WASI_EBADF)); + fd_entry.offset.store(offset as u64, Ordering::Release); } // Enter a loop that will process all the data @@ -5484,17 +8762,13 @@ pub unsafe fn sock_send_file( let fd_entry = wasi_try_ok!(state.fs.get_fd(in_fd)); let bytes_read = match in_fd { __WASI_STDIN_FILENO => { - let mut guard = wasi_try_ok!( + let mut stdin = wasi_try_ok!( inodes .stdin_mut(&state.fs.fd_map) .map_err(fs_error_into_wasi_err), env ); - if let Some(ref mut stdin) = guard.deref_mut() { - wasi_try_ok!(stdin.read(&mut buf).map_err(map_io_err)) - } else { - return Ok(Errno::Badf); - } + wasi_try_ok!(stdin.read(&mut buf).map_err(map_io_err)) } __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return Ok(Errno::Inval), _ => { @@ -5503,16 +8777,16 @@ pub unsafe fn sock_send_file( return Ok(Errno::Access); } - let offset = fd_entry.offset as usize; + let offset = fd_entry.offset.load(Ordering::Acquire) as usize; let inode_idx = fd_entry.inode; let inode = &inodes.arena[inode_idx]; let bytes_read = { let mut guard = inode.write(); - let deref_mut = guard.deref_mut(); - match deref_mut { + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { + let mut handle = handle.write().unwrap(); wasi_try_ok!( handle .seek(std::io::SeekFrom::Start(offset as u64)) @@ -5525,7 +8799,21 @@ pub unsafe fn sock_send_file( } } Kind::Socket { socket } => { - wasi_try_ok!(socket.read(&mut buf).map_err(map_io_err)) + let socket = socket.clone(); + let tasks = tasks.clone(); + let max_size = buf.len(); + let data = wasi_try_ok!( + __asyncify( + tasks, + &env.thread, + None, + async move { + socket.recv(max_size).await + } + ) + ); + buf.copy_from_slice(&data[..]); + data.len() } Kind::Pipe { pipe } => { wasi_try_ok!(pipe.read(&mut buf).map_err(map_io_err)) @@ -5546,19 +8834,23 @@ pub unsafe fn sock_send_file( // reborrow let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); - fd_entry.offset += bytes_read as u64; + let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(__WASI_EBADF)); + fd_entry.offset.fetch_add(bytes_read as u64, Ordering::AcqRel); bytes_read } }; // Write it down to the socket - let bytes_written = - wasi_try_ok!(__sock_actor_mut(&ctx, sock, Rights::SOCK_SEND, |socket| { - let buf = (&buf[..]).to_vec(); - socket.send_bytes::(Bytes::from(buf)) - })); + let buf = (&buf[..]).to_vec(); + let bytes_written = wasi_try_ok!(__sock_actor_mut( + &ctx, + sock, + __WASI_RIGHT_SOCK_SEND, + move |socket| async move { + socket.send(buf).await + } + )); total_written += bytes_written as u64; } @@ -5593,21 +8885,29 @@ pub fn resolve( addrs: WasmPtr<__wasi_addr_t, M>, naddrs: M::Offset, ret_naddrs: WasmPtr, -) -> Errno { - debug!("wasi::resolve"); - - let naddrs: usize = wasi_try!(naddrs.try_into().map_err(|_| Errno::Inval)); +) -> __wasi_errno_t { + let naddrs: usize = wasi_try!(naddrs.try_into().map_err(|_| __WASI_EINVAL)); let env = ctx.data(); let memory = env.memory_view(&ctx); let host_str = unsafe { get_input_str!(&memory, host, host_len) }; let addrs = wasi_try_mem!(addrs.slice(&memory, wasi_try!(to_offset::(naddrs)))); + debug!("wasi[{}:{}]::resolve (host={})", ctx.data().pid(), ctx.data().tid(), host_str); + let port = if port > 0 { Some(port) } else { None }; - let found_ips = wasi_try!(env - .net() - .resolve(host_str.as_str(), port, None) - .map_err(net_error_into_wasi_err)); + let net = env.net(); + let tasks = env.tasks.clone(); + let found_ips = wasi_try!( + __asyncify( + tasks, + &env.thread, + None, + async move { + net.resolve(host_str.as_str(), port, None).await.map_err(net_error_into_wasi_err) + } + ) + ); let mut idx = 0; for found_ip in found_ips.iter().take(naddrs) { diff --git a/lib/wasi/src/syscalls/wasi.rs b/lib/wasi/src/syscalls/wasi.rs deleted file mode 100644 index f90e5955ba0..00000000000 --- a/lib/wasi/src/syscalls/wasi.rs +++ /dev/null @@ -1,449 +0,0 @@ -#![deny(dead_code)] -use crate::{WasiEnv, WasiError, WasiState, WasiThread}; -use wasmer::{Memory, Memory32, MemorySize, StoreMut, WasmPtr, WasmSlice}; -use wasmer_wasi_types::{ - wasi::{Errno, Event, Fd as WasiFd, Filesize, Fstflags, Fstflags, Timestamp, Whence, Clockid}, - types::*, -}; - -type MemoryType = Memory32; -type MemoryOffset = u32; - -pub(crate) fn args_get( - ctx: FunctionEnvMut, - argv: WasmPtr, MemoryType>, - argv_buf: WasmPtr, -) -> Errno { - super::args_get::(ctx, argv, argv_buf) -} - -pub(crate) fn args_sizes_get( - ctx: FunctionEnvMut, - argc: WasmPtr, - argv_buf_size: WasmPtr, -) -> Errno { - super::args_sizes_get::(ctx, argc, argv_buf_size) -} - -pub(crate) fn clock_res_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - resolution: WasmPtr, -) -> Errno { - super::clock_res_get::(ctx, clock_id, resolution) -} - -pub(crate) fn clock_time_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - precision: Timestamp, - time: WasmPtr, -) -> Errno { - super::clock_time_get::(ctx, clock_id, precision, time) -} - -pub(crate) fn environ_get( - ctx: FunctionEnvMut, - environ: WasmPtr, MemoryType>, - environ_buf: WasmPtr, -) -> Errno { - super::environ_get::(ctx, environ, environ_buf) -} - -pub(crate) fn environ_sizes_get( - ctx: FunctionEnvMut, - environ_count: WasmPtr, - environ_buf_size: WasmPtr, -) -> Errno { - super::environ_sizes_get::(ctx, environ_count, environ_buf_size) -} - -pub(crate) fn fd_advise( - ctx: FunctionEnvMut, - fd: WasiFd, - offset: Filesize, - len: Filesize, - advice: __wasi_advice_t, -) -> Errno { - super::fd_advise(ctx, fd, offset, len, advice) -} - -pub(crate) fn fd_allocate( - ctx: FunctionEnvMut, - fd: WasiFd, - offset: Filesize, - len: Filesize, -) -> Errno { - super::fd_allocate(ctx, fd, offset, len) -} - -pub(crate) fn fd_close(ctx: FunctionEnvMut, fd: WasiFd) -> Errno { - super::fd_close(ctx, fd) -} - -pub(crate) fn fd_datasync(ctx: FunctionEnvMut, fd: WasiFd) -> Errno { - super::fd_datasync(ctx, fd) -} - -pub(crate) fn fd_fdstat_get( - ctx: FunctionEnvMut, - fd: WasiFd, - buf_ptr: WasmPtr, -) -> Errno { - super::fd_fdstat_get::(ctx, fd, buf_ptr) -} - -pub(crate) fn fd_fdstat_set_flags( - ctx: FunctionEnvMut, - fd: WasiFd, - flags: WasiFdflags, -) -> Errno { - super::fd_fdstat_set_flags(ctx, fd, flags) -} - -pub(crate) fn fd_fdstat_set_rights( - ctx: FunctionEnvMut, - fd: WasiFd, - fs_rights_base: __wasi_rights_t, - fs_rights_inheriting: __wasi_rights_t, -) -> Errno { - super::fd_fdstat_set_rights(ctx, fd, fs_rights_base, fs_rights_inheriting) -} - -pub(crate) fn fd_filestat_get( - ctx: FunctionEnvMut, - fd: WasiFd, - buf: WasmPtr, -) -> Errno { - super::fd_filestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_filestat_set_size( - ctx: FunctionEnvMut, - fd: WasiFd, - st_size: Filesize, -) -> Errno { - super::fd_filestat_set_size(ctx, fd, st_size) -} - -pub(crate) fn fd_filestat_set_times( - ctx: FunctionEnvMut, - fd: WasiFd, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::fd_filestat_set_times(ctx, fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn fd_pread( - ctx: FunctionEnvMut, - fd: WasiFd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nread: WasmPtr, -) -> Result { - super::fd_pread::(ctx, fd, iovs, iovs_len, offset, nread) -} - -pub(crate) fn fd_prestat_get( - ctx: FunctionEnvMut, - fd: WasiFd, - buf: WasmPtr, -) -> Errno { - super::fd_prestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_prestat_dir_name( - ctx: FunctionEnvMut, - fd: WasiFd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::fd_prestat_dir_name::(ctx, fd, path, path_len) -} - -pub(crate) fn fd_pwrite( - ctx: FunctionEnvMut, - fd: WasiFd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nwritten: WasmPtr, -) -> Result { - super::fd_pwrite::(ctx, fd, iovs, iovs_len, offset, nwritten) -} - -pub(crate) fn fd_read( - ctx: FunctionEnvMut, - fd: WasiFd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - nread: WasmPtr, -) -> Result { - super::fd_read::(ctx, fd, iovs, iovs_len, nread) -} - -pub(crate) fn fd_readdir( - ctx: FunctionEnvMut, - fd: WasiFd, - buf: WasmPtr, - buf_len: MemoryOffset, - cookie: __wasi_dircookie_t, - bufused: WasmPtr, -) -> Errno { - super::fd_readdir::(ctx, fd, buf, buf_len, cookie, bufused) -} - -pub(crate) fn fd_renumber(ctx: FunctionEnvMut, from: WasiFd, to: WasiFd) -> Errno { - super::fd_renumber(ctx, from, to) -} - -pub(crate) fn fd_seek( - ctx: FunctionEnvMut, - fd: WasiFd, - offset: FileDelta, - whence: Whence, - newoffset: WasmPtr, -) -> Result { - super::fd_seek::(ctx, fd, offset, whence, newoffset) -} - -pub(crate) fn fd_sync(ctx: FunctionEnvMut, fd: WasiFd) -> Errno { - super::fd_sync(ctx, fd) -} - -pub(crate) fn fd_tell( - ctx: FunctionEnvMut, - fd: WasiFd, - offset: WasmPtr, -) -> Errno { - super::fd_tell::(ctx, fd, offset) -} - -pub(crate) fn fd_write( - ctx: FunctionEnvMut, - fd: WasiFd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - nwritten: WasmPtr, -) -> Result { - super::fd_write::(ctx, fd, iovs, iovs_len, nwritten) -} - -pub(crate) fn path_create_directory( - ctx: FunctionEnvMut, - fd: WasiFd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_create_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_filestat_get( - ctx: FunctionEnvMut, - fd: WasiFd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, -) -> Errno { - super::path_filestat_get::(ctx, fd, flags, path, path_len, buf) -} - -pub(crate) fn path_filestat_set_times( - ctx: FunctionEnvMut, - fd: WasiFd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::path_filestat_set_times::( - ctx, fd, flags, path, path_len, st_atim, st_mtim, fst_flags, - ) -} - -pub(crate) fn path_link( - ctx: FunctionEnvMut, - old_fd: WasiFd, - old_flags: LookupFlags, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: WasiFd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_link::( - ctx, - old_fd, - old_flags, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_open( - ctx: FunctionEnvMut, - dirfd: WasiFd, - dirflags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - o_flags: Oflags, - fs_rights_base: __wasi_rights_t, - fs_rights_inheriting: __wasi_rights_t, - fs_flags: WasiFdflags, - fd: WasmPtr, -) -> Errno { - super::path_open::( - ctx, - dirfd, - dirflags, - path, - path_len, - o_flags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - fd, - ) -} - -pub(crate) fn path_readlink( - ctx: FunctionEnvMut, - dir_fd: WasiFd, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, - buf_len: MemoryOffset, - buf_used: WasmPtr, -) -> Errno { - super::path_readlink::(ctx, dir_fd, path, path_len, buf, buf_len, buf_used) -} - -pub(crate) fn path_remove_directory( - ctx: FunctionEnvMut, - fd: WasiFd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_remove_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_rename( - ctx: FunctionEnvMut, - old_fd: WasiFd, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: WasiFd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_rename::( - ctx, - old_fd, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_symlink( - ctx: FunctionEnvMut, - old_path: WasmPtr, - old_path_len: MemoryOffset, - fd: WasiFd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_symlink::(ctx, old_path, old_path_len, fd, new_path, new_path_len) -} - -pub(crate) fn path_unlink_file( - ctx: FunctionEnvMut, - fd: WasiFd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_unlink_file::(ctx, fd, path, path_len) -} - -pub(crate) fn poll_oneoff( - ctx: FunctionEnvMut, - in_: WasmPtr<__wasi_subscription_t, MemoryType>, - out_: WasmPtr, - nsubscriptions: MemoryOffset, - nevents: WasmPtr, -) -> Result { - super::poll_oneoff::(ctx, in_, out_, nsubscriptions, nevents) -} - -pub(crate) fn proc_exit( - ctx: FunctionEnvMut, - code: __wasi_exitcode_t, -) -> Result<(), WasiError> { - super::proc_exit(ctx, code) -} - -pub(crate) fn proc_raise(ctx: FunctionEnvMut, sig: Signal) -> Errno { - super::proc_raise(ctx, sig) -} - -pub(crate) fn random_get( - ctx: FunctionEnvMut, - buf: WasmPtr, - buf_len: MemoryOffset, -) -> Errno { - super::random_get::(ctx, buf, buf_len) -} - -pub(crate) fn sched_yield(ctx: FunctionEnvMut) -> Result { - super::sched_yield(ctx) -} - -pub(crate) fn sock_recv( - ctx: FunctionEnvMut, - sock: WasiFd, - ri_data: WasmPtr<__wasi_iovec_t, MemoryType>, - ri_data_len: MemoryOffset, - ri_flags: RiFlags, - ro_data_len: WasmPtr, - ro_flags: WasmPtr, -) -> Result { - super::sock_recv::( - ctx, - sock, - ri_data, - ri_data_len, - ri_flags, - ro_data_len, - ro_flags, - ) -} - -pub(crate) fn sock_send( - ctx: FunctionEnvMut, - sock: WasiFd, - si_data: WasmPtr<__wasi_ciovec_t, MemoryType>, - si_data_len: MemoryOffset, - si_flags: SiFlags, - ret_data_len: WasmPtr, -) -> Result { - super::sock_send::(ctx, sock, si_data, si_data_len, si_flags, ret_data_len) -} - -pub(crate) fn sock_shutdown( - ctx: FunctionEnvMut, - sock: WasiFd, - how: SdFlags, -) -> Errno { - super::sock_shutdown(ctx, sock, how) -} diff --git a/lib/wasi/src/syscalls/wasix32.rs b/lib/wasi/src/syscalls/wasix32.rs deleted file mode 100644 index 53ca959643d..00000000000 --- a/lib/wasi/src/syscalls/wasix32.rs +++ /dev/null @@ -1,1031 +0,0 @@ -#![deny(dead_code)] -use crate::{WasiEnv, WasiError, WasiState, WasiThread}; -use wasmer::{FunctionEnvMut, Memory, Memory32, MemorySize, StoreMut, WasmPtr, WasmSlice}; -use wasmer_wasi_types::types::*; -use wasmer_wasi_types::wasi::{ - Addressfamily, Advice, Bid, BusDataFormat, BusErrno, BusHandles, Cid, Clockid, Dircookie, - Errno, Event, EventFdFlags, Fd, Fdflags, Fdstat, Filesize, Filestat, Fstflags, Pid, Prestat, - Rights, Sockoption, Sockstatus, Socktype, Streamsecurity, Subscription, Tid, Timestamp, Tty, - Whence, -}; - -type MemoryType = Memory32; -type MemoryOffset = u32; - -pub(crate) fn args_get( - ctx: FunctionEnvMut, - argv: WasmPtr, MemoryType>, - argv_buf: WasmPtr, -) -> Errno { - super::args_get::(ctx, argv, argv_buf) -} - -pub(crate) fn args_sizes_get( - ctx: FunctionEnvMut, - argc: WasmPtr, - argv_buf_size: WasmPtr, -) -> Errno { - super::args_sizes_get::(ctx, argc, argv_buf_size) -} - -pub(crate) fn clock_res_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - resolution: WasmPtr, -) -> Errno { - super::clock_res_get::(ctx, clock_id, resolution) -} - -pub(crate) fn clock_time_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - precision: Timestamp, - time: WasmPtr, -) -> Errno { - super::clock_time_get::(ctx, clock_id, precision, time) -} - -pub(crate) fn environ_get( - ctx: FunctionEnvMut, - environ: WasmPtr, MemoryType>, - environ_buf: WasmPtr, -) -> Errno { - super::environ_get::(ctx, environ, environ_buf) -} - -pub(crate) fn environ_sizes_get( - ctx: FunctionEnvMut, - environ_count: WasmPtr, - environ_buf_size: WasmPtr, -) -> Errno { - super::environ_sizes_get::(ctx, environ_count, environ_buf_size) -} - -pub(crate) fn fd_advise( - ctx: FunctionEnvMut, - fd: Fd, - offset: Filesize, - len: Filesize, - advice: Advice, -) -> Errno { - super::fd_advise(ctx, fd, offset, len, advice) -} - -pub(crate) fn fd_allocate( - ctx: FunctionEnvMut, - fd: Fd, - offset: Filesize, - len: Filesize, -) -> Errno { - super::fd_allocate(ctx, fd, offset, len) -} - -pub(crate) fn fd_close(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_close(ctx, fd) -} - -pub(crate) fn fd_datasync(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_datasync(ctx, fd) -} - -pub(crate) fn fd_fdstat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf_ptr: WasmPtr, -) -> Errno { - super::fd_fdstat_get::(ctx, fd, buf_ptr) -} - -pub(crate) fn fd_fdstat_set_flags(ctx: FunctionEnvMut, fd: Fd, flags: Fdflags) -> Errno { - super::fd_fdstat_set_flags(ctx, fd, flags) -} - -pub(crate) fn fd_fdstat_set_rights( - ctx: FunctionEnvMut, - fd: Fd, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, -) -> Errno { - super::fd_fdstat_set_rights(ctx, fd, fs_rights_base, fs_rights_inheriting) -} - -pub(crate) fn fd_filestat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, -) -> Errno { - super::fd_filestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_filestat_set_size( - ctx: FunctionEnvMut, - fd: Fd, - st_size: Filesize, -) -> Errno { - super::fd_filestat_set_size(ctx, fd, st_size) -} - -pub(crate) fn fd_filestat_set_times( - ctx: FunctionEnvMut, - fd: Fd, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::fd_filestat_set_times(ctx, fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn fd_pread( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nread: WasmPtr, -) -> Result { - super::fd_pread::(ctx, fd, iovs, iovs_len, offset, nread) -} - -pub(crate) fn fd_prestat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, -) -> Errno { - super::fd_prestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_prestat_dir_name( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::fd_prestat_dir_name::(ctx, fd, path, path_len) -} - -pub(crate) fn fd_pwrite( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nwritten: WasmPtr, -) -> Result { - super::fd_pwrite::(ctx, fd, iovs, iovs_len, offset, nwritten) -} - -pub(crate) fn fd_read( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - nread: WasmPtr, -) -> Result { - super::fd_read::(ctx, fd, iovs, iovs_len, nread) -} - -pub(crate) fn fd_readdir( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, - buf_len: MemoryOffset, - cookie: Dircookie, - bufused: WasmPtr, -) -> Errno { - super::fd_readdir::(ctx, fd, buf, buf_len, cookie, bufused) -} - -pub(crate) fn fd_renumber(ctx: FunctionEnvMut, from: Fd, to: Fd) -> Errno { - super::fd_renumber(ctx, from, to) -} - -pub(crate) fn fd_seek( - ctx: FunctionEnvMut, - fd: Fd, - offset: FileDelta, - whence: Whence, - newoffset: WasmPtr, -) -> Result { - super::fd_seek::(ctx, fd, offset, whence, newoffset) -} - -pub(crate) fn fd_sync(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_sync(ctx, fd) -} - -pub(crate) fn fd_tell( - ctx: FunctionEnvMut, - fd: Fd, - offset: WasmPtr, -) -> Errno { - super::fd_tell::(ctx, fd, offset) -} - -pub(crate) fn fd_write( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - nwritten: WasmPtr, -) -> Result { - super::fd_write::(ctx, fd, iovs, iovs_len, nwritten) -} - -pub(crate) fn path_create_directory( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_create_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_filestat_get( - ctx: FunctionEnvMut, - fd: Fd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, -) -> Errno { - super::path_filestat_get::(ctx, fd, flags, path, path_len, buf) -} - -pub(crate) fn path_filestat_set_times( - ctx: FunctionEnvMut, - fd: Fd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::path_filestat_set_times::( - ctx, fd, flags, path, path_len, st_atim, st_mtim, fst_flags, - ) -} - -pub(crate) fn path_link( - ctx: FunctionEnvMut, - old_fd: Fd, - old_flags: LookupFlags, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_link::( - ctx, - old_fd, - old_flags, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_open( - ctx: FunctionEnvMut, - dirfd: Fd, - dirflags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - o_flags: Oflags, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, - fs_flags: Fdflags, - fd: WasmPtr, -) -> Errno { - super::path_open::( - ctx, - dirfd, - dirflags, - path, - path_len, - o_flags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - fd, - ) -} - -pub(crate) fn path_readlink( - ctx: FunctionEnvMut, - dir_fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, - buf_len: MemoryOffset, - buf_used: WasmPtr, -) -> Errno { - super::path_readlink::(ctx, dir_fd, path, path_len, buf, buf_len, buf_used) -} - -pub(crate) fn path_remove_directory( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_remove_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_rename( - ctx: FunctionEnvMut, - old_fd: Fd, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_rename::( - ctx, - old_fd, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_symlink( - ctx: FunctionEnvMut, - old_path: WasmPtr, - old_path_len: MemoryOffset, - fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_symlink::(ctx, old_path, old_path_len, fd, new_path, new_path_len) -} - -pub(crate) fn path_unlink_file( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_unlink_file::(ctx, fd, path, path_len) -} - -pub(crate) fn poll_oneoff( - ctx: FunctionEnvMut, - in_: WasmPtr, - out_: WasmPtr, - nsubscriptions: MemoryOffset, - nevents: WasmPtr, -) -> Result { - super::poll_oneoff::(ctx, in_, out_, nsubscriptions, nevents) -} - -pub(crate) fn proc_exit( - ctx: FunctionEnvMut, - code: __wasi_exitcode_t, -) -> Result<(), WasiError> { - super::proc_exit(ctx, code) -} - -pub(crate) fn proc_raise(ctx: FunctionEnvMut, sig: Signal) -> Errno { - super::proc_raise(ctx, sig) -} - -pub(crate) fn random_get( - ctx: FunctionEnvMut, - buf: WasmPtr, - buf_len: MemoryOffset, -) -> Errno { - super::random_get::(ctx, buf, buf_len) -} - -pub(crate) fn fd_dup( - ctx: FunctionEnvMut, - fd: Fd, - ret_fd: WasmPtr, -) -> Errno { - super::fd_dup::(ctx, fd, ret_fd) -} - -pub(crate) fn fd_event( - ctx: FunctionEnvMut, - initial_val: u64, - flags: EventFdFlags, - ret_fd: WasmPtr, -) -> Errno { - super::fd_event(ctx, initial_val, flags, ret_fd) -} - -pub(crate) fn fd_pipe( - ctx: FunctionEnvMut, - ro_fd1: WasmPtr, - ro_fd2: WasmPtr, -) -> Errno { - super::fd_pipe::(ctx, ro_fd1, ro_fd2) -} - -pub(crate) fn tty_get(ctx: FunctionEnvMut, tty_state: WasmPtr) -> Errno { - super::tty_get::(ctx, tty_state) -} - -pub(crate) fn tty_set(ctx: FunctionEnvMut, tty_state: WasmPtr) -> Errno { - super::tty_set::(ctx, tty_state) -} - -pub(crate) fn getcwd( - ctx: FunctionEnvMut, - path: WasmPtr, - path_len: WasmPtr, -) -> Errno { - super::getcwd::(ctx, path, path_len) -} - -pub(crate) fn chdir( - ctx: FunctionEnvMut, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::chdir::(ctx, path, path_len) -} - -pub(crate) fn thread_spawn( - ctx: FunctionEnvMut, - method: WasmPtr, - method_len: MemoryOffset, - user_data: u64, - reactor: Bool, - ret_tid: WasmPtr, -) -> Errno { - super::thread_spawn::(ctx, method, method_len, user_data, reactor, ret_tid) -} - -pub(crate) fn thread_sleep( - ctx: FunctionEnvMut, - duration: Timestamp, -) -> Result { - super::thread_sleep(ctx, duration) -} - -pub(crate) fn thread_id(ctx: FunctionEnvMut, ret_tid: WasmPtr) -> Errno { - super::thread_id::(ctx, ret_tid) -} - -pub(crate) fn thread_join(ctx: FunctionEnvMut, tid: Tid) -> Result { - super::thread_join(ctx, tid) -} - -pub(crate) fn thread_parallelism( - ctx: FunctionEnvMut, - ret_parallelism: WasmPtr, -) -> Errno { - super::thread_parallelism::(ctx, ret_parallelism) -} - -pub(crate) fn thread_exit( - ctx: FunctionEnvMut, - exitcode: __wasi_exitcode_t, -) -> Result { - super::thread_exit(ctx, exitcode) -} - -pub(crate) fn sched_yield(ctx: FunctionEnvMut) -> Result { - super::sched_yield(ctx) -} - -pub(crate) fn getpid(ctx: FunctionEnvMut, ret_pid: WasmPtr) -> Errno { - super::getpid::(ctx, ret_pid) -} - -pub(crate) fn process_spawn( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - chroot: Bool, - args: WasmPtr, - args_len: MemoryOffset, - preopen: WasmPtr, - preopen_len: MemoryOffset, - stdin: StdioMode, - stdout: StdioMode, - stderr: StdioMode, - working_dir: WasmPtr, - working_dir_len: MemoryOffset, - ret_handles: WasmPtr, -) -> BusErrno { - super::process_spawn::( - ctx, - name, - name_len, - chroot, - args, - args_len, - preopen, - preopen_len, - stdin, - stdout, - stderr, - working_dir, - working_dir_len, - ret_handles, - ) -} - -pub(crate) fn bus_open_local( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - reuse: Bool, - ret_bid: WasmPtr, -) -> BusErrno { - super::bus_open_local::(ctx, name, name_len, reuse, ret_bid) -} - -pub(crate) fn bus_open_remote( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - reuse: Bool, - instance: WasmPtr, - instance_len: MemoryOffset, - token: WasmPtr, - token_len: MemoryOffset, - ret_bid: WasmPtr, -) -> BusErrno { - super::bus_open_remote::( - ctx, - name, - name_len, - reuse, - instance, - instance_len, - token, - token_len, - ret_bid, - ) -} - -pub(crate) fn bus_close(ctx: FunctionEnvMut, bid: Bid) -> BusErrno { - super::bus_close(ctx, bid) -} - -pub(crate) fn bus_call( - ctx: FunctionEnvMut, - bid: Bid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: MemoryOffset, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, - ret_cid: WasmPtr, -) -> BusErrno { - super::bus_call::( - ctx, bid, keep_alive, topic, topic_len, format, buf, buf_len, ret_cid, - ) -} - -pub(crate) fn bus_subcall( - ctx: FunctionEnvMut, - parent: Cid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: MemoryOffset, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, - ret_cid: WasmPtr, -) -> BusErrno { - super::bus_subcall::( - ctx, parent, keep_alive, topic, topic_len, format, buf, buf_len, ret_cid, - ) -} - -pub(crate) fn bus_poll( - ctx: FunctionEnvMut, - timeout: Timestamp, - events: WasmPtr, - nevents: MemoryOffset, - malloc: WasmPtr, - malloc_len: MemoryOffset, - ret_nevents: WasmPtr, -) -> BusErrno { - super::bus_poll::( - ctx, - timeout, - events, - nevents, - malloc, - malloc_len, - ret_nevents, - ) -} - -pub(crate) fn call_reply( - ctx: FunctionEnvMut, - cid: Cid, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, -) -> BusErrno { - super::call_reply::(ctx, cid, format, buf, buf_len) -} - -pub(crate) fn call_fault(ctx: FunctionEnvMut, cid: Cid, fault: BusErrno) -> BusErrno { - super::call_fault(ctx, cid, fault) -} - -pub(crate) fn call_close(ctx: FunctionEnvMut, cid: Cid) -> BusErrno { - super::call_close(ctx, cid) -} - -pub(crate) fn port_bridge( - ctx: FunctionEnvMut, - network: WasmPtr, - network_len: MemoryOffset, - token: WasmPtr, - token_len: MemoryOffset, - security: Streamsecurity, -) -> Errno { - super::port_bridge::(ctx, network, network_len, token, token_len, security) -} - -pub(crate) fn port_unbridge(ctx: FunctionEnvMut) -> Errno { - super::port_unbridge(ctx) -} - -pub(crate) fn port_dhcp_acquire(ctx: FunctionEnvMut) -> Errno { - super::port_dhcp_acquire(ctx) -} - -pub(crate) fn port_addr_add( - ctx: FunctionEnvMut, - addr: WasmPtr<__wasi_cidr_t, MemoryType>, -) -> Errno { - super::port_addr_add::(ctx, addr) -} - -pub(crate) fn port_addr_remove( - ctx: FunctionEnvMut, - addr: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_addr_remove::(ctx, addr) -} - -pub(crate) fn port_addr_clear(ctx: FunctionEnvMut) -> Errno { - super::port_addr_clear(ctx) -} - -pub(crate) fn port_addr_list( - ctx: FunctionEnvMut, - addrs: WasmPtr<__wasi_cidr_t, MemoryType>, - naddrs: WasmPtr, -) -> Errno { - super::port_addr_list::(ctx, addrs, naddrs) -} - -pub(crate) fn port_mac( - ctx: FunctionEnvMut, - ret_mac: WasmPtr<__wasi_hardwareaddress_t, MemoryType>, -) -> Errno { - super::port_mac::(ctx, ret_mac) -} - -pub(crate) fn port_gateway_set( - ctx: FunctionEnvMut, - ip: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_gateway_set::(ctx, ip) -} - -pub(crate) fn port_route_add( - ctx: FunctionEnvMut, - cidr: WasmPtr<__wasi_cidr_t, MemoryType>, - via_router: WasmPtr<__wasi_addr_t, MemoryType>, - preferred_until: WasmPtr, - expires_at: WasmPtr, -) -> Errno { - super::port_route_add::(ctx, cidr, via_router, preferred_until, expires_at) -} - -pub(crate) fn port_route_remove( - ctx: FunctionEnvMut, - ip: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_route_remove::(ctx, ip) -} - -pub(crate) fn port_route_clear(ctx: FunctionEnvMut) -> Errno { - super::port_route_clear(ctx) -} - -pub(crate) fn port_route_list( - ctx: FunctionEnvMut, - routes: WasmPtr, - nroutes: WasmPtr, -) -> Errno { - super::port_route_list::(ctx, routes, nroutes) -} - -pub(crate) fn ws_connect( - ctx: FunctionEnvMut, - url: WasmPtr, - url_len: MemoryOffset, - ret_sock: WasmPtr, -) -> Errno { - super::ws_connect::(ctx, url, url_len, ret_sock) -} - -pub(crate) fn http_request( - ctx: FunctionEnvMut, - url: WasmPtr, - url_len: MemoryOffset, - method: WasmPtr, - method_len: MemoryOffset, - headers: WasmPtr, - headers_len: MemoryOffset, - gzip: Bool, - ret_handles: WasmPtr, -) -> Errno { - super::http_request::( - ctx, - url, - url_len, - method, - method_len, - headers, - headers_len, - gzip, - ret_handles, - ) -} - -pub(crate) fn http_status( - ctx: FunctionEnvMut, - sock: Fd, - status: WasmPtr, - status_text: WasmPtr, - status_text_len: WasmPtr, - headers: WasmPtr, - headers_len: WasmPtr, -) -> Errno { - super::http_status::(ctx, sock, status) -} - -pub(crate) fn sock_status( - ctx: FunctionEnvMut, - sock: Fd, - ret_status: WasmPtr, -) -> Errno { - super::sock_status::(ctx, sock, ret_status) -} - -pub(crate) fn sock_addr_local( - ctx: FunctionEnvMut, - sock: Fd, - ret_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_addr_local::(ctx, sock, ret_addr) -} - -pub(crate) fn sock_addr_peer( - ctx: FunctionEnvMut, - sock: Fd, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_addr_peer::(ctx, sock, ro_addr) -} - -pub(crate) fn sock_open( - ctx: FunctionEnvMut, - af: Addressfamily, - ty: Socktype, - pt: SockProto, - ro_sock: WasmPtr, -) -> Errno { - super::sock_open::(ctx, af, ty, pt, ro_sock) -} - -pub(crate) fn sock_set_opt_flag( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - flag: Bool, -) -> Errno { - super::sock_set_opt_flag(ctx, sock, opt, flag) -} - -pub(crate) fn sock_get_opt_flag( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_flag: WasmPtr, -) -> Errno { - super::sock_get_opt_flag::(ctx, sock, opt, ret_flag) -} - -pub fn sock_set_opt_time( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - time: WasmPtr, -) -> Errno { - super::sock_set_opt_time(ctx, sock, opt, time) -} - -pub fn sock_get_opt_time( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_time: WasmPtr, -) -> Errno { - super::sock_get_opt_time(ctx, sock, opt, ret_time) -} - -pub fn sock_set_opt_size( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - size: Filesize, -) -> Errno { - super::sock_set_opt_size(ctx, sock, opt, size) -} - -pub fn sock_get_opt_size( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_size: WasmPtr, -) -> Errno { - super::sock_get_opt_size(ctx, sock, opt, ret_size) -} - -pub(crate) fn sock_join_multicast_v4( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip4_t, MemoryType>, - iface: WasmPtr<__wasi_addr_ip4_t, MemoryType>, -) -> Errno { - super::sock_join_multicast_v4::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_leave_multicast_v4( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip4_t, MemoryType>, - iface: WasmPtr<__wasi_addr_ip4_t, MemoryType>, -) -> Errno { - super::sock_leave_multicast_v4::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_join_multicast_v6( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip6_t, MemoryType>, - iface: u32, -) -> Errno { - super::sock_join_multicast_v6::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_leave_multicast_v6( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip6_t, MemoryType>, - iface: u32, -) -> Errno { - super::sock_leave_multicast_v6::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_bind( - ctx: FunctionEnvMut, - sock: Fd, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_bind::(ctx, sock, addr) -} - -pub(crate) fn sock_listen(ctx: FunctionEnvMut, sock: Fd, backlog: MemoryOffset) -> Errno { - super::sock_listen::(ctx, sock, backlog) -} - -pub(crate) fn sock_accept( - ctx: FunctionEnvMut, - sock: Fd, - fd_flags: Fdflags, - ro_fd: WasmPtr, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Result { - super::sock_accept::(ctx, sock, fd_flags, ro_fd, ro_addr) -} - -pub(crate) fn sock_connect( - ctx: FunctionEnvMut, - sock: Fd, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_connect::(ctx, sock, addr) -} - -pub(crate) fn sock_recv( - ctx: FunctionEnvMut, - sock: Fd, - ri_data: WasmPtr<__wasi_iovec_t, MemoryType>, - ri_data_len: MemoryOffset, - ri_flags: RiFlags, - ro_data_len: WasmPtr, - ro_flags: WasmPtr, -) -> Result { - super::sock_recv::( - ctx, - sock, - ri_data, - ri_data_len, - ri_flags, - ro_data_len, - ro_flags, - ) -} - -pub(crate) fn sock_recv_from( - ctx: FunctionEnvMut, - sock: Fd, - ri_data: WasmPtr<__wasi_iovec_t, MemoryType>, - ri_data_len: MemoryOffset, - ri_flags: RiFlags, - ro_data_len: WasmPtr, - ro_flags: WasmPtr, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Result { - super::sock_recv_from::( - ctx, - sock, - ri_data, - ri_data_len, - ri_flags, - ro_data_len, - ro_flags, - ro_addr, - ) -} - -pub(crate) fn sock_send( - ctx: FunctionEnvMut, - sock: Fd, - si_data: WasmPtr<__wasi_ciovec_t, MemoryType>, - si_data_len: MemoryOffset, - si_flags: SiFlags, - ret_data_len: WasmPtr, -) -> Result { - super::sock_send::(ctx, sock, si_data, si_data_len, si_flags, ret_data_len) -} - -pub(crate) fn sock_send_to( - ctx: FunctionEnvMut, - sock: Fd, - si_data: WasmPtr<__wasi_ciovec_t, MemoryType>, - si_data_len: MemoryOffset, - si_flags: SiFlags, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, - ret_data_len: WasmPtr, -) -> Result { - super::sock_send_to::( - ctx, - sock, - si_data, - si_data_len, - si_flags, - addr, - ret_data_len, - ) -} - -pub(crate) fn sock_send_file( - ctx: FunctionEnvMut, - out_fd: Fd, - in_fd: Fd, - offset: Filesize, - count: Filesize, - ret_sent: WasmPtr, -) -> Result { - unsafe { super::sock_send_file::(ctx, out_fd, in_fd, offset, count, ret_sent) } -} - -pub(crate) fn sock_shutdown(ctx: FunctionEnvMut, sock: Fd, how: SdFlags) -> Errno { - super::sock_shutdown(ctx, sock, how) -} - -pub(crate) fn resolve( - ctx: FunctionEnvMut, - host: WasmPtr, - host_len: MemoryOffset, - port: u16, - ips: WasmPtr<__wasi_addr_t, MemoryType>, - nips: MemoryOffset, - ret_nips: WasmPtr, -) -> Errno { - super::resolve::(ctx, host, host_len, port, ips, nips, ret_nips) -} diff --git a/lib/wasi/src/syscalls/wasix64.rs b/lib/wasi/src/syscalls/wasix64.rs deleted file mode 100644 index df48c26a6ea..00000000000 --- a/lib/wasi/src/syscalls/wasix64.rs +++ /dev/null @@ -1,1031 +0,0 @@ -#![deny(dead_code)] -use crate::{WasiEnv, WasiError, WasiState, WasiThread}; -use wasmer::{FunctionEnvMut, Memory, Memory64, MemorySize, StoreMut, WasmPtr, WasmSlice}; -use wasmer_wasi_types::types::*; -use wasmer_wasi_types::wasi::{ - Addressfamily, Advice, Bid, BusDataFormat, BusErrno, BusHandles, Cid, Clockid, Dircookie, - Errno, Event, EventFdFlags, Fd, Fdflags, Fdstat, Filesize, Filestat, Fstflags, Pid, Prestat, - Rights, Sockoption, Sockstatus, Socktype, Streamsecurity, Subscription, Tid, Timestamp, Tty, - Whence, -}; - -type MemoryType = Memory64; -type MemoryOffset = u64; - -pub(crate) fn args_get( - ctx: FunctionEnvMut, - argv: WasmPtr, MemoryType>, - argv_buf: WasmPtr, -) -> Errno { - super::args_get::(ctx, argv, argv_buf) -} - -pub(crate) fn args_sizes_get( - ctx: FunctionEnvMut, - argc: WasmPtr, - argv_buf_size: WasmPtr, -) -> Errno { - super::args_sizes_get::(ctx, argc, argv_buf_size) -} - -pub(crate) fn clock_res_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - resolution: WasmPtr, -) -> Errno { - super::clock_res_get::(ctx, clock_id, resolution) -} - -pub(crate) fn clock_time_get( - ctx: FunctionEnvMut, - clock_id: Clockid, - precision: Timestamp, - time: WasmPtr, -) -> Errno { - super::clock_time_get::(ctx, clock_id, precision, time) -} - -pub(crate) fn environ_get( - ctx: FunctionEnvMut, - environ: WasmPtr, MemoryType>, - environ_buf: WasmPtr, -) -> Errno { - super::environ_get::(ctx, environ, environ_buf) -} - -pub(crate) fn environ_sizes_get( - ctx: FunctionEnvMut, - environ_count: WasmPtr, - environ_buf_size: WasmPtr, -) -> Errno { - super::environ_sizes_get::(ctx, environ_count, environ_buf_size) -} - -pub(crate) fn fd_advise( - ctx: FunctionEnvMut, - fd: Fd, - offset: Filesize, - len: Filesize, - advice: Advice, -) -> Errno { - super::fd_advise(ctx, fd, offset, len, advice) -} - -pub(crate) fn fd_allocate( - ctx: FunctionEnvMut, - fd: Fd, - offset: Filesize, - len: Filesize, -) -> Errno { - super::fd_allocate(ctx, fd, offset, len) -} - -pub(crate) fn fd_close(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_close(ctx, fd) -} - -pub(crate) fn fd_datasync(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_datasync(ctx, fd) -} - -pub(crate) fn fd_fdstat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf_ptr: WasmPtr, -) -> Errno { - super::fd_fdstat_get::(ctx, fd, buf_ptr) -} - -pub(crate) fn fd_fdstat_set_flags(ctx: FunctionEnvMut, fd: Fd, flags: Fdflags) -> Errno { - super::fd_fdstat_set_flags(ctx, fd, flags) -} - -pub(crate) fn fd_fdstat_set_rights( - ctx: FunctionEnvMut, - fd: Fd, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, -) -> Errno { - super::fd_fdstat_set_rights(ctx, fd, fs_rights_base, fs_rights_inheriting) -} - -pub(crate) fn fd_filestat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, -) -> Errno { - super::fd_filestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_filestat_set_size( - ctx: FunctionEnvMut, - fd: Fd, - st_size: Filesize, -) -> Errno { - super::fd_filestat_set_size(ctx, fd, st_size) -} - -pub(crate) fn fd_filestat_set_times( - ctx: FunctionEnvMut, - fd: Fd, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::fd_filestat_set_times(ctx, fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn fd_pread( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nread: WasmPtr, -) -> Result { - super::fd_pread::(ctx, fd, iovs, iovs_len, offset, nread) -} - -pub(crate) fn fd_prestat_get( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, -) -> Errno { - super::fd_prestat_get::(ctx, fd, buf) -} - -pub(crate) fn fd_prestat_dir_name( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::fd_prestat_dir_name::(ctx, fd, path, path_len) -} - -pub(crate) fn fd_pwrite( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - offset: Filesize, - nwritten: WasmPtr, -) -> Result { - super::fd_pwrite::(ctx, fd, iovs, iovs_len, offset, nwritten) -} - -pub(crate) fn fd_read( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_iovec_t, MemoryType>, - iovs_len: MemoryOffset, - nread: WasmPtr, -) -> Result { - super::fd_read::(ctx, fd, iovs, iovs_len, nread) -} - -pub(crate) fn fd_readdir( - ctx: FunctionEnvMut, - fd: Fd, - buf: WasmPtr, - buf_len: MemoryOffset, - cookie: Dircookie, - bufused: WasmPtr, -) -> Errno { - super::fd_readdir::(ctx, fd, buf, buf_len, cookie, bufused) -} - -pub(crate) fn fd_renumber(ctx: FunctionEnvMut, from: Fd, to: Fd) -> Errno { - super::fd_renumber(ctx, from, to) -} - -pub(crate) fn fd_seek( - ctx: FunctionEnvMut, - fd: Fd, - offset: FileDelta, - whence: Whence, - newoffset: WasmPtr, -) -> Result { - super::fd_seek::(ctx, fd, offset, whence, newoffset) -} - -pub(crate) fn fd_sync(ctx: FunctionEnvMut, fd: Fd) -> Errno { - super::fd_sync(ctx, fd) -} - -pub(crate) fn fd_tell( - ctx: FunctionEnvMut, - fd: Fd, - offset: WasmPtr, -) -> Errno { - super::fd_tell::(ctx, fd, offset) -} - -pub(crate) fn fd_write( - ctx: FunctionEnvMut, - fd: Fd, - iovs: WasmPtr<__wasi_ciovec_t, MemoryType>, - iovs_len: MemoryOffset, - nwritten: WasmPtr, -) -> Result { - super::fd_write::(ctx, fd, iovs, iovs_len, nwritten) -} - -pub(crate) fn path_create_directory( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_create_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_filestat_get( - ctx: FunctionEnvMut, - fd: Fd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, -) -> Errno { - super::path_filestat_get::(ctx, fd, flags, path, path_len, buf) -} - -pub(crate) fn path_filestat_set_times( - ctx: FunctionEnvMut, - fd: Fd, - flags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - st_atim: Timestamp, - st_mtim: Timestamp, - fst_flags: Fstflags, -) -> Errno { - super::path_filestat_set_times::( - ctx, fd, flags, path, path_len, st_atim, st_mtim, fst_flags, - ) -} - -pub(crate) fn path_link( - ctx: FunctionEnvMut, - old_fd: Fd, - old_flags: LookupFlags, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_link::( - ctx, - old_fd, - old_flags, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_open( - ctx: FunctionEnvMut, - dirfd: Fd, - dirflags: LookupFlags, - path: WasmPtr, - path_len: MemoryOffset, - o_flags: Oflags, - fs_rights_base: Rights, - fs_rights_inheriting: Rights, - fs_flags: Fdflags, - fd: WasmPtr, -) -> Errno { - super::path_open::( - ctx, - dirfd, - dirflags, - path, - path_len, - o_flags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - fd, - ) -} - -pub(crate) fn path_readlink( - ctx: FunctionEnvMut, - dir_fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, - buf: WasmPtr, - buf_len: MemoryOffset, - buf_used: WasmPtr, -) -> Errno { - super::path_readlink::(ctx, dir_fd, path, path_len, buf, buf_len, buf_used) -} - -pub(crate) fn path_remove_directory( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_remove_directory::(ctx, fd, path, path_len) -} - -pub(crate) fn path_rename( - ctx: FunctionEnvMut, - old_fd: Fd, - old_path: WasmPtr, - old_path_len: MemoryOffset, - new_fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_rename::( - ctx, - old_fd, - old_path, - old_path_len, - new_fd, - new_path, - new_path_len, - ) -} - -pub(crate) fn path_symlink( - ctx: FunctionEnvMut, - old_path: WasmPtr, - old_path_len: MemoryOffset, - fd: Fd, - new_path: WasmPtr, - new_path_len: MemoryOffset, -) -> Errno { - super::path_symlink::(ctx, old_path, old_path_len, fd, new_path, new_path_len) -} - -pub(crate) fn path_unlink_file( - ctx: FunctionEnvMut, - fd: Fd, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::path_unlink_file::(ctx, fd, path, path_len) -} - -pub(crate) fn poll_oneoff( - ctx: FunctionEnvMut, - in_: WasmPtr, - out_: WasmPtr, - nsubscriptions: MemoryOffset, - nevents: WasmPtr, -) -> Result { - super::poll_oneoff::(ctx, in_, out_, nsubscriptions, nevents) -} - -pub(crate) fn proc_exit( - ctx: FunctionEnvMut, - code: __wasi_exitcode_t, -) -> Result<(), WasiError> { - super::proc_exit(ctx, code) -} - -pub(crate) fn proc_raise(ctx: FunctionEnvMut, sig: Signal) -> Errno { - super::proc_raise(ctx, sig) -} - -pub(crate) fn random_get( - ctx: FunctionEnvMut, - buf: WasmPtr, - buf_len: MemoryOffset, -) -> Errno { - super::random_get::(ctx, buf, buf_len) -} - -pub(crate) fn fd_dup( - ctx: FunctionEnvMut, - fd: Fd, - ret_fd: WasmPtr, -) -> Errno { - super::fd_dup::(ctx, fd, ret_fd) -} - -pub(crate) fn fd_event( - ctx: FunctionEnvMut, - initial_val: u64, - flags: EventFdFlags, - ret_fd: WasmPtr, -) -> Errno { - super::fd_event(ctx, initial_val, flags, ret_fd) -} - -pub(crate) fn fd_pipe( - ctx: FunctionEnvMut, - ro_fd1: WasmPtr, - ro_fd2: WasmPtr, -) -> Errno { - super::fd_pipe::(ctx, ro_fd1, ro_fd2) -} - -pub(crate) fn tty_get(ctx: FunctionEnvMut, tty_state: WasmPtr) -> Errno { - super::tty_get::(ctx, tty_state) -} - -pub(crate) fn tty_set(ctx: FunctionEnvMut, tty_state: WasmPtr) -> Errno { - super::tty_set::(ctx, tty_state) -} - -pub(crate) fn getcwd( - ctx: FunctionEnvMut, - path: WasmPtr, - path_len: WasmPtr, -) -> Errno { - super::getcwd::(ctx, path, path_len) -} - -pub(crate) fn chdir( - ctx: FunctionEnvMut, - path: WasmPtr, - path_len: MemoryOffset, -) -> Errno { - super::chdir::(ctx, path, path_len) -} - -pub(crate) fn thread_spawn( - ctx: FunctionEnvMut, - method: WasmPtr, - method_len: MemoryOffset, - user_data: u64, - reactor: Bool, - ret_tid: WasmPtr, -) -> Errno { - super::thread_spawn::(ctx, method, method_len, user_data, reactor, ret_tid) -} - -pub(crate) fn thread_sleep( - ctx: FunctionEnvMut, - duration: Timestamp, -) -> Result { - super::thread_sleep(ctx, duration) -} - -pub(crate) fn thread_id(ctx: FunctionEnvMut, ret_tid: WasmPtr) -> Errno { - super::thread_id::(ctx, ret_tid) -} - -pub(crate) fn thread_join(ctx: FunctionEnvMut, tid: Tid) -> Result { - super::thread_join(ctx, tid) -} - -pub(crate) fn thread_parallelism( - ctx: FunctionEnvMut, - ret_parallelism: WasmPtr, -) -> Errno { - super::thread_parallelism::(ctx, ret_parallelism) -} - -pub(crate) fn thread_exit( - ctx: FunctionEnvMut, - exitcode: __wasi_exitcode_t, -) -> Result { - super::thread_exit(ctx, exitcode) -} - -pub(crate) fn sched_yield(ctx: FunctionEnvMut) -> Result { - super::sched_yield(ctx) -} - -pub(crate) fn getpid(ctx: FunctionEnvMut, ret_pid: WasmPtr) -> Errno { - super::getpid::(ctx, ret_pid) -} - -pub(crate) fn process_spawn( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - chroot: Bool, - args: WasmPtr, - args_len: MemoryOffset, - preopen: WasmPtr, - preopen_len: MemoryOffset, - stdin: StdioMode, - stdout: StdioMode, - stderr: StdioMode, - working_dir: WasmPtr, - working_dir_len: MemoryOffset, - ret_handles: WasmPtr, -) -> BusErrno { - super::process_spawn::( - ctx, - name, - name_len, - chroot, - args, - args_len, - preopen, - preopen_len, - stdin, - stdout, - stderr, - working_dir, - working_dir_len, - ret_handles, - ) -} - -pub(crate) fn bus_open_local( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - reuse: Bool, - ret_bid: WasmPtr, -) -> BusErrno { - super::bus_open_local::(ctx, name, name_len, reuse, ret_bid) -} - -pub(crate) fn bus_open_remote( - ctx: FunctionEnvMut, - name: WasmPtr, - name_len: MemoryOffset, - reuse: Bool, - instance: WasmPtr, - instance_len: MemoryOffset, - token: WasmPtr, - token_len: MemoryOffset, - ret_bid: WasmPtr, -) -> BusErrno { - super::bus_open_remote::( - ctx, - name, - name_len, - reuse, - instance, - instance_len, - token, - token_len, - ret_bid, - ) -} - -pub(crate) fn bus_close(ctx: FunctionEnvMut, bid: Bid) -> BusErrno { - super::bus_close(ctx, bid) -} - -pub(crate) fn bus_call( - ctx: FunctionEnvMut, - bid: Bid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: MemoryOffset, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, - ret_cid: WasmPtr, -) -> BusErrno { - super::bus_call::( - ctx, bid, keep_alive, topic, topic_len, format, buf, buf_len, ret_cid, - ) -} - -pub(crate) fn bus_subcall( - ctx: FunctionEnvMut, - parent: Cid, - keep_alive: Bool, - topic: WasmPtr, - topic_len: MemoryOffset, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, - ret_cid: WasmPtr, -) -> BusErrno { - super::bus_subcall::( - ctx, parent, keep_alive, topic, topic_len, format, buf, buf_len, ret_cid, - ) -} - -pub(crate) fn bus_poll( - ctx: FunctionEnvMut, - timeout: Timestamp, - events: WasmPtr, - nevents: MemoryOffset, - malloc: WasmPtr, - malloc_len: MemoryOffset, - ret_nevents: WasmPtr, -) -> BusErrno { - super::bus_poll::( - ctx, - timeout, - events, - nevents, - malloc, - malloc_len, - ret_nevents, - ) -} - -pub(crate) fn call_reply( - ctx: FunctionEnvMut, - cid: Cid, - format: BusDataFormat, - buf: WasmPtr, - buf_len: MemoryOffset, -) -> BusErrno { - super::call_reply::(ctx, cid, format, buf, buf_len) -} - -pub(crate) fn call_fault(ctx: FunctionEnvMut, cid: Cid, fault: BusErrno) -> BusErrno { - super::call_fault(ctx, cid, fault) -} - -pub(crate) fn call_close(ctx: FunctionEnvMut, cid: Cid) -> BusErrno { - super::call_close(ctx, cid) -} - -pub(crate) fn port_bridge( - ctx: FunctionEnvMut, - network: WasmPtr, - network_len: MemoryOffset, - token: WasmPtr, - token_len: MemoryOffset, - security: Streamsecurity, -) -> Errno { - super::port_bridge::(ctx, network, network_len, token, token_len, security) -} - -pub(crate) fn port_unbridge(ctx: FunctionEnvMut) -> Errno { - super::port_unbridge(ctx) -} - -pub(crate) fn port_dhcp_acquire(ctx: FunctionEnvMut) -> Errno { - super::port_dhcp_acquire(ctx) -} - -pub(crate) fn port_addr_add( - ctx: FunctionEnvMut, - addr: WasmPtr<__wasi_cidr_t, MemoryType>, -) -> Errno { - super::port_addr_add::(ctx, addr) -} - -pub(crate) fn port_addr_remove( - ctx: FunctionEnvMut, - addr: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_addr_remove::(ctx, addr) -} - -pub(crate) fn port_addr_clear(ctx: FunctionEnvMut) -> Errno { - super::port_addr_clear(ctx) -} - -pub(crate) fn port_addr_list( - ctx: FunctionEnvMut, - addrs: WasmPtr<__wasi_cidr_t, MemoryType>, - naddrs: WasmPtr, -) -> Errno { - super::port_addr_list::(ctx, addrs, naddrs) -} - -pub(crate) fn port_mac( - ctx: FunctionEnvMut, - ret_mac: WasmPtr<__wasi_hardwareaddress_t, MemoryType>, -) -> Errno { - super::port_mac::(ctx, ret_mac) -} - -pub(crate) fn port_gateway_set( - ctx: FunctionEnvMut, - ip: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_gateway_set::(ctx, ip) -} - -pub(crate) fn port_route_add( - ctx: FunctionEnvMut, - cidr: WasmPtr<__wasi_cidr_t, MemoryType>, - via_router: WasmPtr<__wasi_addr_t, MemoryType>, - preferred_until: WasmPtr, - expires_at: WasmPtr, -) -> Errno { - super::port_route_add::(ctx, cidr, via_router, preferred_until, expires_at) -} - -pub(crate) fn port_route_remove( - ctx: FunctionEnvMut, - ip: WasmPtr<__wasi_addr_t, MemoryType>, -) -> Errno { - super::port_route_remove::(ctx, ip) -} - -pub(crate) fn port_route_clear(ctx: FunctionEnvMut) -> Errno { - super::port_route_clear(ctx) -} - -pub(crate) fn port_route_list( - ctx: FunctionEnvMut, - routes: WasmPtr, - nroutes: WasmPtr, -) -> Errno { - super::port_route_list::(ctx, routes, nroutes) -} - -pub(crate) fn ws_connect( - ctx: FunctionEnvMut, - url: WasmPtr, - url_len: MemoryOffset, - ret_sock: WasmPtr, -) -> Errno { - super::ws_connect::(ctx, url, url_len, ret_sock) -} - -pub(crate) fn http_request( - ctx: FunctionEnvMut, - url: WasmPtr, - url_len: MemoryOffset, - method: WasmPtr, - method_len: MemoryOffset, - headers: WasmPtr, - headers_len: MemoryOffset, - gzip: Bool, - ret_handles: WasmPtr, -) -> Errno { - super::http_request::( - ctx, - url, - url_len, - method, - method_len, - headers, - headers_len, - gzip, - ret_handles, - ) -} - -pub(crate) fn http_status( - ctx: FunctionEnvMut, - sock: Fd, - status: WasmPtr, - status_text: WasmPtr, - status_text_len: WasmPtr, - headers: WasmPtr, - headers_len: WasmPtr, -) -> Errno { - super::http_status::(ctx, sock, status) -} - -pub(crate) fn sock_status( - ctx: FunctionEnvMut, - sock: Fd, - ret_status: WasmPtr, -) -> Errno { - super::sock_status::(ctx, sock, ret_status) -} - -pub(crate) fn sock_addr_local( - ctx: FunctionEnvMut, - sock: Fd, - ret_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_addr_local::(ctx, sock, ret_addr) -} - -pub(crate) fn sock_addr_peer( - ctx: FunctionEnvMut, - sock: Fd, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_addr_peer::(ctx, sock, ro_addr) -} - -pub(crate) fn sock_open( - ctx: FunctionEnvMut, - af: Addressfamily, - ty: Socktype, - pt: SockProto, - ro_sock: WasmPtr, -) -> Errno { - super::sock_open::(ctx, af, ty, pt, ro_sock) -} - -pub(crate) fn sock_set_opt_flag( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - flag: Bool, -) -> Errno { - super::sock_set_opt_flag(ctx, sock, opt, flag) -} - -pub(crate) fn sock_get_opt_flag( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_flag: WasmPtr, -) -> Errno { - super::sock_get_opt_flag::(ctx, sock, opt, ret_flag) -} - -pub fn sock_set_opt_time( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - time: WasmPtr, -) -> Errno { - super::sock_set_opt_time(ctx, sock, opt, time) -} - -pub fn sock_get_opt_time( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_time: WasmPtr, -) -> Errno { - super::sock_get_opt_time(ctx, sock, opt, ret_time) -} - -pub fn sock_set_opt_size( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - size: Filesize, -) -> Errno { - super::sock_set_opt_size(ctx, sock, opt, size) -} - -pub fn sock_get_opt_size( - ctx: FunctionEnvMut, - sock: Fd, - opt: Sockoption, - ret_size: WasmPtr, -) -> Errno { - super::sock_get_opt_size(ctx, sock, opt, ret_size) -} - -pub(crate) fn sock_join_multicast_v4( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip4_t, MemoryType>, - iface: WasmPtr<__wasi_addr_ip4_t, MemoryType>, -) -> Errno { - super::sock_join_multicast_v4::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_leave_multicast_v4( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip4_t, MemoryType>, - iface: WasmPtr<__wasi_addr_ip4_t, MemoryType>, -) -> Errno { - super::sock_leave_multicast_v4::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_join_multicast_v6( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip6_t, MemoryType>, - iface: u32, -) -> Errno { - super::sock_join_multicast_v6::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_leave_multicast_v6( - ctx: FunctionEnvMut, - sock: Fd, - multiaddr: WasmPtr<__wasi_addr_ip6_t, MemoryType>, - iface: u32, -) -> Errno { - super::sock_leave_multicast_v6::(ctx, sock, multiaddr, iface) -} - -pub(crate) fn sock_bind( - ctx: FunctionEnvMut, - sock: Fd, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_bind::(ctx, sock, addr) -} - -pub(crate) fn sock_listen(ctx: FunctionEnvMut, sock: Fd, backlog: MemoryOffset) -> Errno { - super::sock_listen::(ctx, sock, backlog) -} - -pub(crate) fn sock_accept( - ctx: FunctionEnvMut, - sock: Fd, - fd_flags: Fdflags, - ro_fd: WasmPtr, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Result { - super::sock_accept::(ctx, sock, fd_flags, ro_fd, ro_addr) -} - -pub(crate) fn sock_connect( - ctx: FunctionEnvMut, - sock: Fd, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Errno { - super::sock_connect::(ctx, sock, addr) -} - -pub(crate) fn sock_recv( - ctx: FunctionEnvMut, - sock: Fd, - ri_data: WasmPtr<__wasi_iovec_t, MemoryType>, - ri_data_len: MemoryOffset, - ri_flags: RiFlags, - ro_data_len: WasmPtr, - ro_flags: WasmPtr, -) -> Result { - super::sock_recv::( - ctx, - sock, - ri_data, - ri_data_len, - ri_flags, - ro_data_len, - ro_flags, - ) -} - -pub(crate) fn sock_recv_from( - ctx: FunctionEnvMut, - sock: Fd, - ri_data: WasmPtr<__wasi_iovec_t, MemoryType>, - ri_data_len: MemoryOffset, - ri_flags: RiFlags, - ro_data_len: WasmPtr, - ro_flags: WasmPtr, - ro_addr: WasmPtr<__wasi_addr_port_t, MemoryType>, -) -> Result { - super::sock_recv_from::( - ctx, - sock, - ri_data, - ri_data_len, - ri_flags, - ro_data_len, - ro_flags, - ro_addr, - ) -} - -pub(crate) fn sock_send( - ctx: FunctionEnvMut, - sock: Fd, - si_data: WasmPtr<__wasi_ciovec_t, MemoryType>, - si_data_len: MemoryOffset, - si_flags: SiFlags, - ret_data_len: WasmPtr, -) -> Result { - super::sock_send::(ctx, sock, si_data, si_data_len, si_flags, ret_data_len) -} - -pub(crate) fn sock_send_to( - ctx: FunctionEnvMut, - sock: Fd, - si_data: WasmPtr<__wasi_ciovec_t, MemoryType>, - si_data_len: MemoryOffset, - si_flags: SiFlags, - addr: WasmPtr<__wasi_addr_port_t, MemoryType>, - ret_data_len: WasmPtr, -) -> Result { - super::sock_send_to::( - ctx, - sock, - si_data, - si_data_len, - si_flags, - addr, - ret_data_len, - ) -} - -pub(crate) fn sock_send_file( - ctx: FunctionEnvMut, - out_fd: Fd, - in_fd: Fd, - offset: Filesize, - count: Filesize, - ret_sent: WasmPtr, -) -> Result { - unsafe { super::sock_send_file::(ctx, out_fd, in_fd, offset, count, ret_sent) } -} - -pub(crate) fn sock_shutdown(ctx: FunctionEnvMut, sock: Fd, how: SdFlags) -> Errno { - super::sock_shutdown(ctx, sock, how) -} - -pub(crate) fn resolve( - ctx: FunctionEnvMut, - host: WasmPtr, - host_len: MemoryOffset, - port: u16, - ips: WasmPtr<__wasi_addr_t, MemoryType>, - nips: MemoryOffset, - ret_nips: WasmPtr, -) -> Errno { - super::resolve::(ctx, host, host_len, port, ips, nips, ret_nips) -} diff --git a/lib/wasi/src/syscalls/wasm32.rs b/lib/wasi/src/syscalls/wasm.rs similarity index 100% rename from lib/wasi/src/syscalls/wasm32.rs rename to lib/wasi/src/syscalls/wasm.rs diff --git a/lib/wasi/src/wapm/manifest.rs b/lib/wasi/src/wapm/manifest.rs new file mode 100644 index 00000000000..8600f8e88d8 --- /dev/null +++ b/lib/wasi/src/wapm/manifest.rs @@ -0,0 +1,187 @@ +use serde::*; +use semver::Version; +use std::path::PathBuf; +use std::fmt; +use std::collections::HashMap; + +/// The name of the manifest file. This is hard-coded for now. +pub static MANIFEST_FILE_NAME: &str = "wapm.toml"; +pub static PACKAGES_DIR_NAME: &str = "wapm_packages"; + +/// Primitive wasm type +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum WasmType { + I32, + I64, + F32, + F64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Import { + Func { + namespace: String, + name: String, + params: Vec, + result: Vec, + }, + Global { + namespace: String, + name: String, + var_type: WasmType, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Export { + Func { + name: String, + params: Vec, + result: Vec, + }, + Global { + name: String, + var_type: WasmType, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Interface { + /// The name the interface gave itself + pub name: Option, + /// Things that the module can import + pub imports: HashMap<(String, String), Import>, + /// Things that the module must export + pub exports: HashMap, +} + +/// The ABI is a hint to WebAssembly runtimes about what additional imports to insert. +/// It currently is only used for validation (in the validation subcommand). The default value is `None`. +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] +pub enum Abi { + #[serde(rename = "emscripten")] + Emscripten, + #[serde(rename = "none")] + None, + #[serde(rename = "wasi")] + Wasi, +} + +impl Abi { + pub fn to_str(&self) -> &str { + match self { + Abi::Emscripten => "emscripten", + Abi::Wasi => "wasi", + Abi::None => "generic", + } + } + pub fn is_none(&self) -> bool { + return self == &Abi::None; + } + pub fn from_str(name: &str) -> Self { + match name.to_lowercase().as_ref() { + "emscripten" => Abi::Emscripten, + "wasi" => Abi::Wasi, + _ => Abi::None, + } + } +} + +impl fmt::Display for Abi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_str()) + } +} + +impl Default for Abi { + fn default() -> Self { + Abi::None + } +} + +impl Abi { + pub fn get_interface(&self) -> Option { + match self { + Abi::Emscripten => None, + Abi::Wasi => None, + Abi::None => None, + } + } +} + +/// Describes a command for a wapm module +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Package { + pub name: String, + pub version: Version, + pub description: String, + pub license: Option, + /// The location of the license file, useful for non-standard licenses + #[serde(rename = "license-file")] + pub license_file: Option, + pub readme: Option, + pub repository: Option, + pub homepage: Option, + #[serde(rename = "wasmer-extra-flags")] + pub wasmer_extra_flags: Option, + #[serde( + rename = "disable-command-rename", + default, + skip_serializing_if = "std::ops::Not::not" + )] + pub disable_command_rename: bool, + /// Unlike, `disable-command-rename` which prevents `wapm run `, + /// this flag enables the command rename of `wapm run ` into + /// just `. This is useful for programs that need to inspect + /// their argv[0] names and when the command name matches their executable name. + #[serde( + rename = "rename-commands-to-raw-command-name", + default, + skip_serializing_if = "std::ops::Not::not" + )] + pub rename_commands_to_raw_command_name: bool, +} + +/// Describes a command for a wapm module +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Command { + pub name: String, + pub module: String, + pub main_args: Option, + pub package: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Module { + pub name: String, + pub source: PathBuf, + #[serde(default = "Abi::default", skip_serializing_if = "Abi::is_none")] + pub abi: Abi, + #[cfg(feature = "package")] + pub fs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub interfaces: Option>, +} + +/// The manifest represents the file used to describe a Wasm package. +/// +/// The `module` field represents the wasm file to be published. +/// +/// The `source` is used to create bundles with the `fs` section. +/// +/// The `fs` section represents fs assets that will be made available to the +/// program relative to its starting current directory (there may be issues with WASI). +/// These are pairs of paths. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Manifest { + pub package: Package, + pub dependencies: Option>, + pub module: Option>, + pub command: Option>, + /// Of the form Guest -> Host path + pub fs: Option>, + /// private data + /// store the directory path of the manifest file for use later accessing relative path fields + #[serde(skip)] + pub base_directory_path: PathBuf, +} \ No newline at end of file diff --git a/lib/wasi/src/wapm/mod.rs b/lib/wasi/src/wapm/mod.rs new file mode 100644 index 00000000000..b86fe74a382 --- /dev/null +++ b/lib/wasi/src/wapm/mod.rs @@ -0,0 +1,338 @@ +use std::{ + sync::Arc, + ops::Deref, + path::PathBuf, +}; +use webc::{FsEntryType, WebC, Annotation, UrlOrManifest}; +use webc_vfs::VirtualFileSystem; +use tracing::*; + +#[allow(unused_imports)] +use tracing::{error, warn}; + +use crate::{ + runtime::{ + ReqwestOptions + }, + bin_factory::{BinaryPackage, BinaryPackageCommand}, WasiRuntimeImplementation, VirtualTaskManager +}; + +mod pirita; +#[cfg(feature = "wapm-tar")] +mod manifest; + +use pirita::*; + +pub(crate) fn fetch_webc(cache_dir: &str, webc: &str, runtime: &dyn WasiRuntimeImplementation, tasks: &dyn VirtualTaskManager) -> Option { + let name = webc.split_once(":").map(|a| a.0).unwrap_or_else(|| webc); + let (name, version) = match name.split_once("@") { + Some((name, version)) => (name, Some(version)), + None => (name, None) + }; + let url_query = match version { + Some(version) => WAPM_WEBC_QUERY_SPECIFIC + .replace(WAPM_WEBC_QUERY_TAG, name.replace("\"", "'").as_str()) + .replace(WAPM_WEBC_VERSION_TAG, version.replace("\"", "'").as_str()), + None => WAPM_WEBC_QUERY_LAST + .replace(WAPM_WEBC_QUERY_TAG,name.replace("\"", "'").as_str()) + }; + let url = format!( + "{}{}", + WAPM_WEBC_URL, + urlencoding::encode(url_query.as_str()) + ); + let options = ReqwestOptions::default(); + let headers = Default::default(); + let data = None; + match runtime.reqwest(tasks, url.as_str(), "POST", options, headers, data) { + Ok(wapm) => { + if wapm.status == 200 { + if let Some(data) = wapm.data { + match serde_json::from_slice::<'_, WapmWebQuery>(data.as_ref()) { + Ok(query) => { + if let Some(package) = query.data.get_package_version { + if let Some(pirita_download_url) = package.distribution.pirita_download_url { + let mut ret = download_webc(cache_dir, name, pirita_download_url, runtime, tasks)?; + ret.version = package.version.into(); + return Some(ret); + } else { + warn!("package ({}) has no pirita download URL: {}", webc, String::from_utf8_lossy(data.as_ref())); + } + } else if let Some(package) = query.data.get_package { + if let Some(pirita_download_url) = package.last_version.distribution.pirita_download_url { + let mut ret = download_webc(cache_dir, name, pirita_download_url, runtime, tasks)?; + ret.version = package.last_version.version.into(); + return Some(ret); + } else { + warn!("package ({}) has no pirita download URL: {}", webc, String::from_utf8_lossy(data.as_ref())); + } + } else { + warn!("failed to parse WAPM package ({}): {}", name, String::from_utf8_lossy(data.as_ref())); + } + }, + Err(err) => { + warn!("failed to deserialize WAPM response: {}", err); + } + } + } + } else { + warn!("failed to contact WAPM: http_code={}, http_response={}", wapm.status, wapm.status_text); + } + }, + Err(code) => { + warn!("failed to contact WAPM: http_code={}", code); + } + } + None +} + +fn download_webc(cache_dir: &str, name: &str, pirita_download_url: String, runtime: &dyn WasiRuntimeImplementation, tasks: &dyn VirtualTaskManager) -> Option +{ + let mut name_comps = pirita_download_url.split("/").collect::>().into_iter().rev(); + let mut name = name_comps.next().unwrap_or_else(|| name); + let mut name_store; + for _ in 0..2 { + if let Some(prefix) = name_comps.next() { + name_store = format!("{}_{}", prefix, name); + name = name_store.as_str(); + } + } + let compute_path = |cache_dir: &str, name: &str| { + let name = name.replace("/", "._."); + std::path::Path::new(cache_dir).join(format!("{}", name.as_str()).as_str()) + }; + + // build the parse options + let options = webc::ParseOptions::default(); + + // fast path + let path = compute_path(cache_dir, name); + #[cfg(feature = "sys")] + if path.exists() { + match webc::WebCMmap::parse(path.clone(), &options) { + Ok(webc) => { + unsafe { + let webc = Arc::new(webc); + return parse_webc(webc.as_webc_ref(), webc.clone()); + } + }, + Err(err) => { + warn!("failed to parse WebC: {}", err); + } + } + } + if let Ok(data) = std::fs::read(path) { + match webc::WebCOwned::parse(data, &options) { + Ok(webc) => { + unsafe { + let webc = Arc::new(webc); + return parse_webc(webc.as_webc_ref(), webc.clone()); + } + }, + Err(err) => { + warn!("failed to parse WebC: {}", err); + } + } + } + + // slow path + let cache_dir = cache_dir.to_string(); + let name = name.to_string(); + if let Some(data) = download_miss(pirita_download_url.as_str(), runtime, tasks) { + let path = compute_path(cache_dir.as_str(), name.as_str()); + let _ = std::fs::create_dir_all(path.parent().unwrap().clone()); + + let mut temp_path = path.clone(); + let rand_128: u128 = rand::random(); + temp_path = PathBuf::from(format!("{}.{}.temp", temp_path.as_os_str().to_string_lossy(), rand_128)); + + if let Err(err) = std::fs::write(temp_path.as_path(), &data[..]) { + debug!("failed to write webc cache file [{}] - {}", temp_path.as_path().to_string_lossy(), err); + } + if let Err(err) = std::fs::rename(temp_path.as_path(), path.as_path()) { + debug!("failed to rename webc cache file [{}] - {}", temp_path.as_path().to_string_lossy(), err); + } + + #[cfg(feature = "sys")] + match webc::WebCMmap::parse(path, &options) { + Ok(webc) => { + unsafe { + let webc = Arc::new(webc); + return parse_webc(webc.as_webc_ref(), webc.clone()); + } + }, + Err(err) => { + warn!("failed to parse WebC: {}", err); + } + } + + match webc::WebCOwned::parse(data, &options) { + Ok(webc) => { + unsafe { + let webc = Arc::new(webc); + return parse_webc(webc.as_webc_ref(), webc.clone()); + } + }, + Err(err) => { + warn!("failed to parse WebC: {}", err); + } + } + } + + None +} + +fn download_miss(download_url: &str, runtime: &dyn WasiRuntimeImplementation, tasks: &dyn VirtualTaskManager) -> Option> { + let mut options = ReqwestOptions::default(); + options.gzip = true; + + let headers = Default::default(); + let data = None; + + match runtime.reqwest(tasks, download_url, "GET", options, headers, data) { + Ok(wapm) => { + if wapm.status == 200 { + return wapm.data; + } else { + warn!("failed to download package: http_code={}, http_response={}", wapm.status, wapm.status_text); + } + }, + Err(code) => { + warn!("failed to download package: http_code={}", code); + } + } + None +} + +unsafe fn parse_webc<'a, 'b, T>(webc: webc::WebC<'a>, ownership: Arc) -> Option +where T: std::fmt::Debug + Send + Sync + 'static, + T: Deref> +{ + let package_name = webc.get_package_name(); + + let mut pck = webc.manifest.entrypoint + .iter() + .filter_map(|entry| { + webc.manifest.commands.get(entry) + .map(|a| (a, entry)) + }) + .filter_map(|(cmd, entry)| { + let api = if cmd.runner.starts_with("https://webc.org/runner/emscripten") { + "emscripten" + } else if cmd.runner.starts_with("https://webc.org/runner/wasi") { + "wasi" + } else { + warn!("unsupported runner - {}", cmd.runner); + return None; + }; + match webc.get_atom_name_for_command(api, entry.as_str()) { + Ok(a) => Some(a), + Err(err) => { + warn!("failed to find atom name for entry command({}) - {}", entry.as_str(), err); + None + } + } + }) + .filter_map(|atom| { + match webc.get_atom(&package_name, atom.as_str()) { + Ok(a) => Some(a), + Err(err) => { + warn!("failed to find atom for atom name({}) - {}", atom, err); + None + } + } + }) + .map(|atom| { + BinaryPackage::new_with_ownership(package_name.as_str(), atom.into(), ownership.clone()) + }) + .next(); + + if let Some(pck) = pck.as_mut() { + + // Add all the dependencies + for uses in webc.manifest.use_map.values() { + let uses = match uses { + UrlOrManifest::Url(url) => Some(url.path().to_string()), + UrlOrManifest::Manifest(manifest) => { + manifest.origin.as_ref().map(|a| a.clone()) + }, + UrlOrManifest::RegistryDependentUrl(url) => { + Some(url.clone()) + }, + }; + if let Some(uses) = uses { + pck.uses.push(uses); + } + } + + // Set the version of this package + if let Some(Annotation::Map(wapm)) = webc.manifest.package.get("wapm") { + if let Some(Annotation::Text(version)) = wapm.get(&Annotation::Text("version".to_string())) { + pck.version = version.clone().into(); + } + } else if let Some(Annotation::Text(version)) = webc.manifest.package.get("version") { + pck.version = version.clone().into(); + } + + // Add all the file system files + let top_level_dirs = webc + .get_volumes_for_package(&package_name) + .into_iter() + .flat_map(|volume| { + webc.volumes + .get(&volume) + .unwrap() + .header + .top_level + .iter() + .filter(|e| e.fs_type == FsEntryType::Dir) + .map(|e| e.text.to_string()) + }) + .collect::>(); + + pck.webc_fs = Some(Arc::new(VirtualFileSystem::init(ownership.clone(), &package_name))); + pck.webc_top_level_dirs = top_level_dirs; + + let root_package = webc.get_package_name(); + for (command, action) in webc.get_metadata().commands.iter() { + if let Some(Annotation::Map(annotations)) = action.annotations.get("wasi") { + + let mut atom = None; + let mut package = root_package.clone(); + for (k, v) in annotations { + match (k, v) { + (Annotation::Text(k), Annotation::Text(v)) if k == "atom" => { + atom = Some(v.clone()); + }, + (Annotation::Text(k), Annotation::Text(v)) if k == "package" => { + package = v.clone(); + }, + _ => { } + } + } + + // Load the atom as a command + if let Some(atom_name) = atom { + match webc.get_atom(package.as_str(), atom_name.as_str()) { + Ok(atom) => { + trace!("added atom (name={}, size={}) for command [{}]", atom_name, atom.len(), command); + let mut commands = pck.commands.write().unwrap(); + commands.push( + BinaryPackageCommand::new_with_ownership( + command.clone(), + atom.into(), + ownership.clone() + ) + ); + } + Err(err) => { + warn!("Failed to find atom [{}].[{}] - {}", package, atom_name, err); + } + } + } + } + } + } + + pck +} diff --git a/lib/wasi/src/wapm/pirita.rs b/lib/wasi/src/wapm/pirita.rs new file mode 100644 index 00000000000..aacedad0dfe --- /dev/null +++ b/lib/wasi/src/wapm/pirita.rs @@ -0,0 +1,76 @@ +use serde::*; + +pub const WAPM_WEBC_URL: &'static str = "https://registry.wapm.dev/graphql?query="; +#[allow(dead_code)] +pub const WAPM_WEBC_QUERY_ALL: &'static str = r#" +{ + getPackage(name: "") { + versions { + version, + distribution { + downloadUrl, + piritaDownloadUrl + } + } + } +}"#; +pub const WAPM_WEBC_QUERY_LAST: &'static str = r#" +{ + getPackage(name: "") { + lastVersion { + version, + distribution { + downloadUrl, + piritaDownloadUrl + } + } + } +}"#; +pub const WAPM_WEBC_QUERY_SPECIFIC: &'static str = r#" +{ + getPackageVersion(name: "", version: "") { + version, + distribution { + downloadUrl, + piritaDownloadUrl + } + } +}"#; +pub const WAPM_WEBC_QUERY_TAG: &'static str = ""; +pub const WAPM_WEBC_VERSION_TAG: &'static str = ""; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WapmWebQueryGetPackageLastVersionDistribution { + #[serde(rename = "downloadUrl")] + pub download_url: Option, + #[serde(rename = "piritaDownloadUrl")] + pub pirita_download_url: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WapmWebQueryGetPackageVersion { + #[serde(rename = "version")] + pub version: String, + #[serde(rename = "distribution")] + pub distribution: WapmWebQueryGetPackageLastVersionDistribution +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WapmWebQueryGetPackage { + #[serde(rename = "lastVersion")] + pub last_version: WapmWebQueryGetPackageVersion +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WapmWebQueryData { + #[serde(rename = "getPackage")] + pub get_package: Option, + #[serde(rename = "getPackageVersion")] + pub get_package_version: Option +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WapmWebQuery { + #[serde(rename = "data")] + pub data: WapmWebQueryData, +} \ No newline at end of file diff --git a/lib/wasi/tests/catsay.rs b/lib/wasi/tests/catsay.rs new file mode 100644 index 00000000000..4d4901840ba --- /dev/null +++ b/lib/wasi/tests/catsay.rs @@ -0,0 +1,146 @@ +#![cfg(feature = "sys")] +#![cfg(target_os = "linux")] +use std::{io::{Read, Write}, time::Duration}; + +#[allow(unused_imports)] +use tracing::{debug, info, metadata::LevelFilter}; +#[cfg(feature = "sys")] +use tracing_subscriber::fmt::SubscriberBuilder; +use wasmer::{Instance, Module, Store, Cranelift, EngineBuilder}; +use wasmer_wasi::{Pipe, WasiState, import_object_for_all_wasi_versions, WasiError}; + +#[cfg(feature = "sys")] +mod sys { + #[test] + fn test_catsay() { + super::test_catsay() + } +} + +#[cfg(feature = "js")] +mod js { + use wasm_bindgen_test::*; + #[wasm_bindgen_test] + fn test_catsay() { + super::test_catsay() + } +} + +fn test_catsay() { + + info!("Creating engine"); + let compiler = Cranelift::default(); + let engine = EngineBuilder::new(compiler.clone()); + + #[allow(unused_mut)] + let mut store = Store::new(engine); + + info!("Compiling module"); + let module = Module::new(&store, include_bytes!("catsay.wasm")).unwrap(); + + #[cfg(feature = "js")] + tracing_wasm::set_as_global_default_with_config({ + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor); + builder.build() + }); + + #[cfg(feature = "sys")] + SubscriberBuilder::default() + .with_max_level(LevelFilter::TRACE) + .init(); + + + let engine = store.engine().clone(); + for _ in 0..10 { + let module = module.clone(); + run_test(store, module); + + store = Store::new(engine.clone()); + } + + // TODO: This version will SIGSEGV (users must reuse engines) + for _ in 0..10 { + let module = module.clone(); + run_test(store, module); + + let engine = EngineBuilder::new(compiler.clone()); + store = Store::new(engine); + } +} + +fn run_test(mut store: Store, module: Module) +{ + // Create the `WasiEnv`. + let mut stdout = Pipe::new(); + let mut wasi_state_builder = WasiState::new("catsay"); + + let mut stdin_pipe = Pipe::new(); + + let mut wasi_env = wasi_state_builder + .stdin(Box::new(stdin_pipe.clone())) + .stdout(Box::new(stdout.clone())) + .stderr(Box::new(stdout.clone())) + .finalize(&mut store) + .unwrap(); + + // Start a thread that will dump STDOUT to info + #[cfg(feature = "sys")] + std::thread::spawn(move || { + loop { + let mut buf = [0u8; 8192]; + if let Ok(amt) = stdout.read(&mut buf[..]) { + if amt > 0 { + let msg = String::from_utf8_lossy(&buf[0..amt]); + for line in msg.lines() { + info!("{}", line); + } + } else { + std::thread::sleep(Duration::from_millis(1)); + } + } else { + break; + } + } + }); + + // Write some text to catsay stdin + stdin_pipe.write_all("hi there".as_bytes()).unwrap(); + drop(stdin_pipe); + + // Generate an `ImportObject`. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &wasi_env.env); + import_object.import_shared_memory(&module, &mut store); + + // Let's instantiate the module with the imports. + let instance = Instance::new(&mut store, &module, &import_object).unwrap(); + wasi_env.initialize(&mut store, &instance).unwrap(); + + // Let's call the `_start` function, which is our `main` function in Rust. + let start = instance.exports.get_function("_start").unwrap(); + let ret = start.call(&mut store, &[]); + if let Err(e) = ret { + match e.downcast::() { + Ok(WasiError::Exit(0)) => { } + Ok(WasiError::Exit(code)) => { + assert!(false, "The call should have returned Err(WasiError::Exit(0)) but returned {}", code); + } + Ok(WasiError::UnknownWasiVersion) => { + assert!(false, "The call should have returned Err(WasiError::Exit(0)) but returned UnknownWasiVersion"); + } + Err(err) => { + assert!(false, "The call returned an error {:?}", err); + } + } + } + + #[cfg(feature = "js")] + { + let mut stdout_str = String::new(); + stdout.read_to_string(&mut stdout_str).unwrap(); + let stdout_as_str = stdout_str.as_str(); + for line in stdout_str.lines() { + info!("{}", line); + } + } +} diff --git a/lib/wasi/tests/catsay.wasm b/lib/wasi/tests/catsay.wasm new file mode 100755 index 00000000000..bb103f01429 Binary files /dev/null and b/lib/wasi/tests/catsay.wasm differ diff --git a/lib/wasi/tests/condvar.rs b/lib/wasi/tests/condvar.rs new file mode 100644 index 00000000000..c3a9863762d --- /dev/null +++ b/lib/wasi/tests/condvar.rs @@ -0,0 +1,120 @@ +#![cfg(feature = "sys")] +#![cfg(target_os = "linux")] +use std::{io::Read, time::Duration}; + +#[allow(unused_imports)] +use tracing::{debug, info, metadata::LevelFilter}; +#[cfg(feature = "sys")] +use tracing_subscriber::fmt::SubscriberBuilder; +use wasmer::{Instance, Module, Store, Features, Cranelift, EngineBuilder}; +use wasmer_wasi::{Pipe, WasiState, import_object_for_all_wasi_versions, WasiError}; + +#[cfg(feature = "sys")] +mod sys { + #[test] + fn test_condvar() { + super::test_condvar() + } +} + +#[cfg(feature = "js")] +mod js { + use wasm_bindgen_test::*; + #[wasm_bindgen_test] + fn test_condvar() { + super::test_condvar() + } +} + +fn test_condvar() { + let mut features = Features::new(); + features + .threads(true); + + info!("Creating engine"); + let compiler = Cranelift::default(); + let engine = EngineBuilder::new(compiler) + .set_features(Some(features)); + + let store = Store::new(engine); + + info!("Compiling module"); + let module = Module::new(&store, include_bytes!("condvar.wasm")).unwrap(); + + #[cfg(feature = "js")] + tracing_wasm::set_as_global_default_with_config({ + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor); + builder.build() + }); + + #[cfg(feature = "sys")] + SubscriberBuilder::default() + .with_max_level(LevelFilter::TRACE) + .init(); + + run_test(store, module); +} + +fn run_test(mut store: Store, module: Module) +{ + // Create the `WasiEnv`. + let mut stdout = Pipe::new(); + let mut wasi_state_builder = WasiState::new("multi-threading"); + + let mut wasi_env = wasi_state_builder + .stdout(Box::new(stdout.clone())) + .stderr(Box::new(stdout.clone())) + .finalize(&mut store) + .unwrap(); + + // Start a thread that will dump STDOUT to info + #[cfg(feature = "sys")] + std::thread::spawn(move || { + loop { + let mut buf = [0u8; 8192]; + if let Ok(amt) = stdout.read(&mut buf[..]) { + if amt > 0 { + let msg = String::from_utf8_lossy(&buf[0..amt]); + for line in msg.lines() { + info!("{}", line); + } + } else { + std::thread::sleep(Duration::from_millis(1)); + } + } else { + break; + } + } + }); + + // Generate an `ImportObject`. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &wasi_env.env); + import_object.import_shared_memory(&module, &mut store); + + // Let's instantiate the module with the imports. + let instance = Instance::new(&mut store, &module, &import_object).unwrap(); + wasi_env.initialize(&mut store, &instance).unwrap(); + + // Let's call the `_start` function, which is our `main` function in Rust. + let start = instance.exports.get_function("_start").unwrap(); + let ret = start.call(&mut store, &[]); + if let Err(e) = ret { + match e.downcast::() { + Ok(WasiError::Exit(0)) => { } + _ => { + assert!(false, "The call should have returned Err(WasiError::Exit(0))"); + } + } + } + + #[cfg(feature = "js")] + { + let mut stdout_str = String::new(); + stdout.read_to_string(&mut stdout_str).unwrap(); + let stdout_as_str = stdout_str.as_str(); + for line in stdout_str.lines() { + info!("{}", line); + } + } +} diff --git a/lib/wasi/tests/condvar.wasm b/lib/wasi/tests/condvar.wasm new file mode 100755 index 00000000000..4aad0385307 Binary files /dev/null and b/lib/wasi/tests/condvar.wasm differ diff --git a/lib/wasi/tests/coreutils.rs b/lib/wasi/tests/coreutils.rs new file mode 100644 index 00000000000..883dca43b31 --- /dev/null +++ b/lib/wasi/tests/coreutils.rs @@ -0,0 +1,106 @@ +#![cfg(feature = "sys")] +#![cfg(target_os = "linux")] +use std::io::Read; + +#[allow(unused_imports)] +use tracing::{debug, info, metadata::LevelFilter}; +#[cfg(feature = "sys")] +use tracing_subscriber::fmt::SubscriberBuilder; +use wasmer::{Instance, Module, Store, Features, Cranelift, EngineBuilder}; +use wasmer_wasi::{Pipe, WasiState, import_object_for_all_wasi_versions, WasiError}; + +#[cfg(feature = "sys")] +mod sys { + #[test] + fn test_coreutils() { + super::test_coreutils() + } +} + +#[cfg(feature = "js")] +mod js { + use wasm_bindgen_test::*; + #[wasm_bindgen_test] + fn test_coreutils() { + super::test_coreutils() + } +} + +fn test_coreutils() { + let mut features = Features::new(); + features + .threads(true); + + info!("Creating engine"); + let compiler = Cranelift::default(); + let engine = EngineBuilder::new(compiler) + .set_features(Some(features)) + .engine(); + + let store = Store::new(engine.clone()); + + info!("Compiling module"); + let module = Module::new(&store, include_bytes!("coreutils.wasm")).unwrap(); + + #[cfg(feature = "js")] + tracing_wasm::set_as_global_default_with_config({ + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor); + builder.build() + }); + + #[cfg(feature = "sys")] + SubscriberBuilder::default() + .with_max_level(LevelFilter::DEBUG) + .init(); + + // We do it many times (to make sure the compiled modules are reusable) + for n in 0..2 + { + let store = Store::new(engine.clone()); + let module = module.clone(); + + // Run the test itself + info!("Test Round {}", n); + run_test(store, module); + } +} + +fn run_test(mut store: Store, module: Module) +{ + // Create the `WasiEnv`. + let mut stdout = Pipe::new(); + let mut wasi_state_builder = WasiState::new("echo"); + wasi_state_builder + .args(&["apple"]); + + let mut wasi_env = wasi_state_builder + .stdout(Box::new(stdout.clone())) + .finalize(&mut store) + .unwrap(); + + // Generate an `ImportObject`. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &wasi_env.env); + import_object.import_shared_memory(&module, &mut store); + + // Let's instantiate the module with the imports. + let instance = Instance::new(&mut store, &module, &import_object).unwrap(); + wasi_env.initialize(&mut store, &instance).unwrap(); + + // Let's call the `_start` function, which is our `main` function in Rust. + let start = instance.exports.get_function("_start").unwrap(); + let ret = start.call(&mut store, &[]); + if let Err(e) = ret { + match e.downcast::() { + Ok(WasiError::Exit(0)) => { } + _ => { + assert!(false, "The call should have returned Err(WasiError::Exit(0))"); + } + } + } + + let mut stdout_str = String::new(); + stdout.read_to_string(&mut stdout_str).unwrap(); + let stdout_as_str = stdout_str.as_str(); + assert_eq!(stdout_as_str, "apple\n"); +} diff --git a/lib/wasi/tests/coreutils.wasm b/lib/wasi/tests/coreutils.wasm new file mode 100755 index 00000000000..0a056a48329 Binary files /dev/null and b/lib/wasi/tests/coreutils.wasm differ diff --git a/lib/wasi/tests/multi-threading.rs b/lib/wasi/tests/multi-threading.rs new file mode 100644 index 00000000000..8a4e1f6a8e5 --- /dev/null +++ b/lib/wasi/tests/multi-threading.rs @@ -0,0 +1,119 @@ +#![cfg(feature = "sys")] +#![cfg(target_os = "linux")] +use std::{io::Read, time::Duration}; + +#[allow(unused_imports)] +use tracing::{debug, info, metadata::LevelFilter}; +#[cfg(feature = "sys")] +use tracing_subscriber::fmt::SubscriberBuilder; +use wasmer::{Instance, Module, Store, Features, Cranelift, EngineBuilder}; +use wasmer_wasi::{Pipe, WasiState, import_object_for_all_wasi_versions, WasiError}; + +mod sys { + #[test] + fn test_multithreading() { + super::test_multithreading() + } +} + +fn test_multithreading() { + let mut features = Features::new(); + features + .threads(true); + + info!("Creating engine"); + let compiler = Cranelift::default(); + let engine = EngineBuilder::new(compiler) + .set_features(Some(features)) + .engine(); + + let store = Store::new(engine.clone()); + + info!("Compiling module"); + let module = Module::new(&store, include_bytes!("multi-threading.wasm")).unwrap(); + + #[cfg(feature = "js")] + tracing_wasm::set_as_global_default_with_config({ + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor); + builder.build() + }); + + #[cfg(feature = "sys")] + SubscriberBuilder::default() + .with_max_level(LevelFilter::TRACE) + .init(); + + // We do it many times (to make sure the compiled modules are reusable) + for n in 0..2 + { + let store = Store::new(engine.clone()); + let module = module.clone(); + + info!("Test Round {}", n); + run_test(store, module); + } +} + +fn run_test(mut store: Store, module: Module) +{ + // Create the `WasiEnv`. + let mut stdout = Pipe::new(); + let mut wasi_state_builder = WasiState::new("multi-threading"); + + let mut wasi_env = wasi_state_builder + .stdout(Box::new(stdout.clone())) + .stderr(Box::new(stdout.clone())) + .finalize(&mut store) + .unwrap(); + + // Start a thread that will dump STDOUT to info + #[cfg(feature = "sys")] + std::thread::spawn(move || { + loop { + let mut buf = [0u8; 8192]; + if let Ok(amt) = stdout.read(&mut buf[..]) { + if amt > 0 { + let msg = String::from_utf8_lossy(&buf[0..amt]); + for line in msg.lines() { + info!("{}", line); + } + } else { + std::thread::sleep(Duration::from_millis(1)); + } + } else { + break; + } + } + }); + + // Generate an `ImportObject`. + let mut import_object = import_object_for_all_wasi_versions(&mut store, &wasi_env.env); + import_object.import_shared_memory(&module, &mut store); + + // Let's instantiate the module with the imports. + let instance = Instance::new(&mut store, &module, &import_object).unwrap(); + wasi_env.initialize(&mut store, &instance).unwrap(); + + // Let's call the `_start` function, which is our `main` function in Rust. + let start = instance.exports.get_function("_start").unwrap(); + let ret = start.call(&mut store, &[]); + if let Err(e) = ret { + match e.downcast::() { + Ok(WasiError::Exit(0)) => { } + _ => { + assert!(false, "The call should have returned Err(WasiError::Exit(0))"); + } + } + } + + #[cfg(feature = "js")] + { + let mut stdout_str = String::new(); + stdout.read_to_string(&mut stdout_str).unwrap(); + let stdout_as_str = stdout_str.as_str(); + for line in stdout_str.lines() { + info!("{}", line); + } + } +} diff --git a/lib/wasi/tests/multi-threading.wasm b/lib/wasi/tests/multi-threading.wasm new file mode 100644 index 00000000000..4967a3dc781 Binary files /dev/null and b/lib/wasi/tests/multi-threading.wasm differ diff --git a/lib/wasi/tests/stack.wasm b/lib/wasi/tests/stack.wasm new file mode 100755 index 00000000000..3cf2ddb2023 Binary files /dev/null and b/lib/wasi/tests/stack.wasm differ diff --git a/lib/wasi/tests/stdio.rs b/lib/wasi/tests/stdio.rs index fc29889ebc1..34e798ef766 100644 --- a/lib/wasi/tests/stdio.rs +++ b/lib/wasi/tests/stdio.rs @@ -23,7 +23,6 @@ mod sys { #[cfg(feature = "js")] mod js { use wasm_bindgen_test::*; - #[wasm_bindgen_test] fn test_stdout() { super::test_stdout() @@ -73,8 +72,8 @@ fn test_stdout() { "#).unwrap(); // Create the `WasiEnv`. - let mut pipe = WasiBidirectionalSharedPipePair::new().with_blocking(false); - let wasi_env = WasiState::new("command-name") + let mut stdout = Pipe::default(); + let mut wasi_env = WasiState::new("command-name") .args(&["Gordon"]) .stdout(Box::new(pipe.clone())) .finalize(&mut store) @@ -85,8 +84,7 @@ fn test_stdout() { // Let's instantiate the module with the imports. let instance = Instance::new(&mut store, &module, &import_object).unwrap(); - let memory = instance.exports.get_memory("memory").unwrap(); - wasi_env.data_mut(&mut store).set_memory(memory.clone()); + wasi_env.initialize(&mut store, &instance).unwrap(); // Let's call the `_start` function, which is our `main` function in Rust. let start = instance.exports.get_function("_start").unwrap(); @@ -118,8 +116,8 @@ fn test_env() { .env("TEST", "VALUE") .env("TEST2", "VALUE2"); // panic!("envs: {:?}", wasi_state_builder.envs); - let wasi_env = wasi_state_builder - .stdout(Box::new(pipe.clone())) + let mut wasi_env = wasi_state_builder + .stdout(Box::new(stdout.clone())) .finalize(&mut store) .unwrap(); @@ -128,8 +126,7 @@ fn test_env() { // Let's instantiate the module with the imports. let instance = Instance::new(&mut store, &module, &import_object).unwrap(); - let memory = instance.exports.get_memory("memory").unwrap(); - wasi_env.data_mut(&mut store).set_memory(memory.clone()); + wasi_env.initialize(&mut store, &instance).unwrap(); // Let's call the `_start` function, which is our `main` function in Rust. let start = instance.exports.get_function("_start").unwrap(); @@ -146,7 +143,11 @@ fn test_stdin() { let module = Module::new(&store, include_bytes!("stdin-hello.wasm")).unwrap(); // Create the `WasiEnv`. - let mut pipe = WasiBidirectionalSharedPipePair::new().with_blocking(false); + let mut stdin = Pipe::new(); + let mut wasi_env = WasiState::new("command-name") + .stdin(Box::new(stdin.clone())) + .finalize(&mut store) + .unwrap(); // Write to STDIN let buf = "Hello, stdin!\n".as_bytes().to_owned(); @@ -162,8 +163,7 @@ fn test_stdin() { // Let's instantiate the module with the imports. let instance = Instance::new(&mut store, &module, &import_object).unwrap(); - let memory = instance.exports.get_memory("memory").unwrap(); - wasi_env.data_mut(&mut store).set_memory(memory.clone()); + wasi_env.initialize(&mut store, &instance).unwrap(); // Let's call the `_start` function, which is our `main` function in Rust. let start = instance.exports.get_function("_start").unwrap(); diff --git a/lib/wasi/wia/wasixx_32v1.txt b/lib/wasi/wia/wasixx_32v1.txt new file mode 100644 index 00000000000..da5e31e220b --- /dev/null +++ b/lib/wasi/wia/wasixx_32v1.txt @@ -0,0 +1,116 @@ +(interface "wasix_32v1" + (func (import "wasix_32v1" "args_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "args_sizes_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "environ_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "environ_sizes_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "clock_res_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "clock_time_get") (param i32 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_advise") (param i32 i64 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_allocate") (param i32 i64 i64) (result i32)) + (func (import "wasix_32v1" "fd_close") (param i32) (result i32)) + (func (import "wasix_32v1" "fd_datasync") (param i32) (result i32)) + (func (import "wasix_32v1" "fd_fdstat_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_fdstat_set_flags") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_fdstat_set_rights") (param i32 i64 i64) (result i32)) + (func (import "wasix_32v1" "fd_filestat_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_filestat_set_size") (param i32 i64) (result i32)) + (func (import "wasix_32v1" "fd_filestat_set_times") (param i32 i64 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_pread") (param i32 i32 i32 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_prestat_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_prestat_dir_name") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_pwrite") (param i32 i32 i32 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_read") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_readdir") (param i32 i32 i32 i64 i32) (result i32)) + (func (import "wasix_32v1" "fd_renumber") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_dup") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_event") (param i64 i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_seek") (param i32 i64 i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_sync") (param i32) (result i32)) + (func (import "wasix_32v1" "fd_tell") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "fd_write") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "pipe") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "path_create_directory") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_filestat_get") (param i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_filestat_set_times") (param i32 i32 i32 i32 i64 i64 i32) (result i32)) + (func (import "wasix_32v1" "path_link") (param i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_open") (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_readlink") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_remove_directory") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_rename") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_symlink") (param i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "path_unlink_file") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "proc_exit") (param i32) + (func (import "wasix_32v1" "proc_raise") (param i32) (result i32)) + (func (import "wasix_32v1" "sched_yield") (result i32)) + (func (import "wasix_32v1" "random_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "tty_get") (param i32) (result i32)) + (func (import "wasix_32v1" "tty_set") (param i32) (result i32)) + (func (import "wasix_32v1" "getcwd") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "chdir") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "thread_spawn") (param i64 i32 i32) (result i32)) + (func (import "wasix_32v1" "thread_sleep") (param i64) (result i32)) + (func (import "wasix_32v1" "thread_id") (param i32) (result i32)) + (func (import "wasix_32v1" "thread_local_create") (param i64 i32) (result i32)) + (func (import "wasix_32v1" "thread_local_destroy") (param i32) (result i32)) + (func (import "wasix_32v1" "thread_local_set") (param i32 i64) (result i32)) + (func (import "wasix_32v1" "thread_local_get") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "thread_join") (param i32) (result i32)) + (func (import "wasix_32v1" "thread_parallelism") (param i32) (result i32)) + (func (import "wasix_32v1" "futex_wait") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "futex_wake") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "futex_wake_all") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "getpid") (param i32) (result i32)) + (func (import "wasix_32v1" "thread_exit") (param i32) + (func (import "wasix_32v1" "process_spawn") (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "bus_open_local") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "bus_open_remote") (param 32 i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "bus_close") (param i32) (result i32)) + (func (import "wasix_32v1" "bus_call") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "bus_subcall") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "bus_poll") (param i64 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "call_reply") (param i64 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "call_fault") (param i64 i32) + (func (import "wasix_32v1" "call_close") (param i64) + (func (import "wasix_32v1" "ws_connect") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "http_request") (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "http_status") (param i32 i32) + (func (import "wasix_32v1" "port_bridge") (param i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "port_unbridge") (result i32)) + (func (import "wasix_32v1" "port_dhcp_acquire") (result i32)) + (func (import "wasix_32v1" "port_addr_add") (param i32) (result i32)) + (func (import "wasix_32v1" "port_addr_remove") (param i32) (result i32)) + (func (import "wasix_32v1" "port_addr_clear") (result i32)) + (func (import "wasix_32v1" "port_mac") (param i32) (result i32)) + (func (import "wasix_32v1" "port_addr_list") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "port_gateway_set") (param i32) (result i32)) + (func (import "wasix_32v1" "port_route_add") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "port_route_remove") (param i32) (result i32)) + (func (import "wasix_32v1" "port_route_clear") (result i32)) + (func (import "wasix_32v1" "port_route_list") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_shutdown") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_status") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_addr_local") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_addr_peer") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_open") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_set_opt_flag") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_get_opt_flag") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_set_opt_time") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_get_opt_time") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_set_opt_size") (param i32 i32 i64) (result i32)) + (func (import "wasix_32v1" "sock_get_opt_size") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_join_multicast_v4") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_leave_multicast_v4") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_join_multicast_v6") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_leave_multicast_v6") (param i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_bind") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_listen") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_accept") (param i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_connect") (param i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_recv") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_recv_from") (param i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_send") (param i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_send_to") (param i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasix_32v1" "sock_send_file") (param i32 i32 i64 i64 i64) (result i32)) + (func (import "wasix_32v1" "resolve") (param i32 i32 i32 i32 i32 i32) (result i32)) +) diff --git a/lib/wasi/wia/wasixx_64v1.txt b/lib/wasi/wia/wasixx_64v1.txt new file mode 100644 index 00000000000..e904bbb1968 --- /dev/null +++ b/lib/wasi/wia/wasixx_64v1.txt @@ -0,0 +1,116 @@ +(interface "wasix_64v1" + (func (import "wasix_64v1" "args_get") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "args_sizes_get") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "environ_get") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "environ_sizes_get") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "clock_res_get") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "clock_time_get") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_advise") (param i32 i64 i64 i32) (result i32)) + (func (import "wasix_64v1" "fd_allocate") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_close") (param i32) (result i32)) + (func (import "wasix_64v1" "fd_datasync") (param i32) (result i32)) + (func (import "wasix_64v1" "fd_fdstat_get") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_fdstat_set_flags") (param i32 i32) (result i32)) + (func (import "wasix_64v1" "fd_fdstat_set_rights") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_filestat_get") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_filestat_set_size") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_filestat_set_times") (param i32 i64 i64 i32) (result i32)) + (func (import "wasix_64v1" "fd_pread") (param i32 i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_prestat_get") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_prestat_dir_name") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_pwrite") (param i32 i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_read") (param i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_readdir") (param i32 i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "fd_renumber") (param i32 i32) (result i32)) + (func (import "wasix_64v1" "fd_dup") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_event") (param i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_seek") (param i32 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_sync") (param i32) (result i32)) + (func (import "wasix_64v1" "fd_tell") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "fd_write") (param i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "pipe") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "path_create_directory") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_filestat_get") (param i32 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_filestat_set_times") (param i32 i32 i64 i64 i64 i64 i32) (result i32)) + (func (import "wasix_64v1" "path_link") (param i32 i32 i64 i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_open") (param i32 i32 i64 i64 i32 i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "path_readlink") (param i32 i64 i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_remove_directory") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_rename") (param i32 i64 i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_symlink") (param i64 i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "path_unlink_file") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "poll_oneoff") (param i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "proc_exit") (param i32) + (func (import "wasix_64v1" "proc_raise") (param i32) (result i32)) + (func (import "wasix_64v1" "sched_yield") (result i32)) + (func (import "wasix_64v1" "random_get") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "tty_get") (param i64) (result i32)) + (func (import "wasix_64v1" "tty_set") (param i64) (result i32)) + (func (import "wasix_64v1" "getcwd") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "chdir") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "thread_spawn") (param i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "thread_sleep") (param i64) (result i32)) + (func (import "wasix_64v1" "thread_id") (param i64) (result i32)) + (func (import "wasix_64v1" "thread_local_create") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "thread_local_destroy") (param i32) (result i32)) + (func (import "wasix_64v1" "thread_local_set") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "thread_local_get") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "thread_join") (param i32) (result i32)) + (func (import "wasix_64v1" "thread_parallelism") (param i64) (result i32)) + (func (import "wasix_64v1" "futex_wait") (param i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "futex_wake") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "futex_wake_all") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "getpid") (param i64) (result i32)) + (func (import "wasix_64v1" "thread_exit") (param i32) + (func (import "wasix_64v1" "process_spawn") (param i64 i64 i32 i64 i64 i64 i64 i32 i32 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "bus_open_local") (param i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "bus_open_remote") (param i64 i64 i32 i64 i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "bus_close") (param i32) (result i32)) + (func (import "wasix_64v1" "bus_call") (param i32 i64 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "bus_subcall") (param i64 i64 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "bus_poll") (param i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "call_reply") (param i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "call_fault") (param i64 i32) + (func (import "wasix_64v1" "call_close") (param i64) + (func (import "wasix_64v1" "ws_connect") (param i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "http_request") (param i64 i64 i64 i64 i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "http_status") (param i32 i64) + (func (import "wasix_64v1" "port_bridge") (param i64 i64 i64 i64 i32) (result i32)) + (func (import "wasix_64v1" "port_unbridge") (result i32)) + (func (import "wasix_64v1" "port_dhcp_acquire") (result i32)) + (func (import "wasix_64v1" "port_addr_add") (param i64) (result i32)) + (func (import "wasix_64v1" "port_addr_remove") (param i64) (result i32)) + (func (import "wasix_64v1" "port_addr_clear") (result i32)) + (func (import "wasix_64v1" "port_mac") (param i64) (result i32)) + (func (import "wasix_64v1" "port_addr_list") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "port_gateway_set") (param i64) (result i32)) + (func (import "wasix_64v1" "port_route_add") (param i64 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "port_route_remove") (param i64) (result i32)) + (func (import "wasix_64v1" "port_route_clear") (result i32)) + (func (import "wasix_64v1" "port_route_list") (param i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_shutdown") (param i32 i32) (result i32)) + (func (import "wasix_64v1" "sock_status") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_addr_local") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_addr_peer") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_open") (param i32 i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_set_opt_flag") (param i32 i32 i32) (result i32)) + (func (import "wasix_64v1" "sock_get_opt_flag") (param i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_set_opt_time") (param i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_get_opt_time") (param i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_set_opt_size") (param i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_get_opt_size") (param i32 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_join_multicast_v4") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_leave_multicast_v4") (param i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_join_multicast_v6") (param i32 i64 i32) (result i32)) + (func (import "wasix_64v1" "sock_leave_multicast_v6") (param i32 i64 i32) (result i32)) + (func (import "wasix_64v1" "sock_bind") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_listen") (param i32 i32) (result i32)) + (func (import "wasix_64v1" "sock_accept") (param i32 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_connect") (param i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_recv") (param i32 i64 i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_recv_from") (param i32 i64 i64 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_send") (param i32 i64 i64 i32 i64) (result i32)) + (func (import "wasix_64v1" "sock_send_to") (param i32 i64 i64 i32 i64 i64) (result i32)) + (func (import "wasix_64v1" "sock_send_file") (param i32 i32 i64 i64 i64) (result i32)) + (func (import "wasix_64v1" "resolve") (param i64 i64 i32 i64 i32 i64) (result i32)) +) diff --git a/lib/wasi/wia/wasm_bus.txt b/lib/wasi/wia/wasm_bus.txt new file mode 100644 index 00000000000..64d0637e4e4 --- /dev/null +++ b/lib/wasi/wia/wasm_bus.txt @@ -0,0 +1,14 @@ +(interface "wasm-bus" + (func (import "wasm-bus" "wasm_bus_drop") (param i32) + (func (import "wasm-bus" "wasm_bus_handle") (result i32)) + (func (import "wasm-bus" "wasm_bus_listen") (param i32 i32) + (func (import "wasm-bus" "wasm_bus_callback") (param i32 i32 i32 i32) + (func (import "wasm-bus" "wasm_bus_fault") (param i32 i32) + (func (import "wasm-bus" "wasm_bus_poll") + (func (import "wasm-bus" "wasm_bus_fork") + (func (import "wasm-bus" "wasm_bus_reply") (param i32 i32 i32) + (func (import "wasm-bus" "wasm_bus_reply_callback") (param i32 i32 i32 i32 i32) + (func (import "wasm-bus" "wasm_bus_call") (param i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasm-bus" "wasm_bus_call_instance") (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)) + (func (import "wasm-bus" "wasm_bus_thread_id") (result i32)) +) diff --git a/tests/compilers/imports.rs b/tests/compilers/imports.rs index 60d452ac62f..a14273dd4ff 100644 --- a/tests/compilers/imports.rs +++ b/tests/compilers/imports.rs @@ -40,7 +40,7 @@ fn get_module(store: &Store) -> Result { (start $foo) "#; - let module = Module::new(store, &wat)?; + let module = Module::new(store, wat)?; Ok(module) } @@ -339,7 +339,7 @@ fn static_function_that_fails(config: crate::Config) -> Result<()> { (start $foo) "#; - let module = Module::new(&store, &wat)?; + let module = Module::new(&store, wat)?; let f0 = Function::new_typed(&mut store, || -> Result { Err(RuntimeError::new("oops")) }); @@ -375,7 +375,7 @@ fn get_module2(store: &Store) -> Result { (call 0)) "#; - let module = Module::new(store, &wat)?; + let module = Module::new(store, wat)?; Ok(module) } diff --git a/tests/compilers/issues.rs b/tests/compilers/issues.rs index f8cd10f51e2..42e50c102ed 100644 --- a/tests/compilers/issues.rs +++ b/tests/compilers/issues.rs @@ -25,7 +25,7 @@ fn issue_2329(mut config: crate::Config) -> Result<()> { } pub fn read_memory(mut ctx: FunctionEnvMut, guest_ptr: u32) -> u32 { - dbg!(ctx.data_mut().memory.as_ref()); + dbg!(ctx.data().memory.as_ref()); dbg!(guest_ptr); 0 } @@ -101,7 +101,7 @@ fn call_with_static_data_pointers(mut config: crate::Config) -> Result<()> { ) -> u64 { println!("{:?}", (a, b, c, d, e, f, g, h)); let mut buf = vec![0; d as usize]; - let memory = ctx.data_mut().memory.as_ref().unwrap().clone(); + let memory = ctx.data().memory.as_ref().unwrap().clone(); memory.view(&ctx).read(e, &mut buf).unwrap(); let input_string = std::str::from_utf8(&buf).unwrap(); assert_eq!(input_string, "bananapeach"); diff --git a/tests/compilers/traps.rs b/tests/compilers/traps.rs index 5c0e6a20d08..6bf18c27f83 100644 --- a/tests/compilers/traps.rs +++ b/tests/compilers/traps.rs @@ -262,7 +262,7 @@ fn trap_start_function_import(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| Err(RuntimeError::new("user trap"))); let err = Instance::new( @@ -302,7 +302,7 @@ fn rust_panic_import(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| panic!("this is a panic")); let f0 = Function::new_typed(&mut store, || panic!("this is another panic")); @@ -347,7 +347,7 @@ fn rust_panic_start_function(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| panic!("this is a panic")); let err = panic::catch_unwind(AssertUnwindSafe(|| { @@ -393,7 +393,7 @@ fn mismatched_arguments(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let func: &Function = instance.exports.get("foo")?; assert_eq!( @@ -432,7 +432,7 @@ fn call_signature_mismatch(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let err = Instance::new(&mut store, &module, &imports! {}) .err() .expect("expected error"); diff --git a/tests/compilers/typed_functions.rs b/tests/compilers/typed_functions.rs index c6c09b07272..e4b48af1913 100644 --- a/tests/compilers/typed_functions.rs +++ b/tests/compilers/typed_functions.rs @@ -307,7 +307,7 @@ fn static_host_function_with_env(config: crate::Config) -> anyhow::Result<()> { let mut store = config.store(); fn f(mut env: FunctionEnvMut, a: i32, b: i64, c: f32, d: f64) -> (f64, f32, i64, i32) { - let mut guard = env.data_mut().0.lock().unwrap(); + let mut guard = env.data().0.lock().unwrap(); assert_eq!(*guard, 100); *guard = 101; @@ -321,7 +321,7 @@ fn static_host_function_with_env(config: crate::Config) -> anyhow::Result<()> { c: f32, d: f64, ) -> Result<(f64, f32, i64, i32), Infallible> { - let mut guard = env.data_mut().0.lock().unwrap(); + let mut guard = env.data().0.lock().unwrap(); assert_eq!(*guard, 100); *guard = 101; @@ -446,7 +446,7 @@ fn dynamic_host_function_with_env(config: crate::Config) -> anyhow::Result<()> { ], ), |mut env, values| { - let mut guard = env.data_mut().0.lock().unwrap(); + let mut guard = env.data().0.lock().unwrap(); assert_eq!(*guard, 100); *guard = 101; diff --git a/tests/lib/wast/src/wasi_wast.rs b/tests/lib/wast/src/wasi_wast.rs index 08195b64bd6..7b7bdf7e105 100644 --- a/tests/lib/wast/src/wasi_wast.rs +++ b/tests/lib/wast/src/wasi_wast.rs @@ -81,17 +81,17 @@ impl<'a> WasiTest<'a> { wasm_module.read_to_end(&mut out)?; out }; - let module = Module::new(store, &wasm_bytes)?; - let (env, _tempdirs, stdout_rx, stderr_rx) = + let module = Module::new(store, wasm_bytes)?; + let (mut env, _tempdirs, stdout_rx, stderr_rx) = self.create_wasi_env(store, filesystem_kind)?; let imports = self.get_imports(store, &env.env, &module)?; let instance = Instance::new(&mut store, &module, &imports)?; - let start = instance.exports.get_function("_start")?; - let memory = instance.exports.get_memory("memory")?; - let wasi_env = env.data_mut(&mut store); - wasi_env.set_memory(memory.clone()); + env.initialize(&mut store, &instance).unwrap(); + let wasi_env = env.data(&store); + let start = instance.exports.get_function("_start")?; + if let Some(stdin) = &self.stdin { let state = wasi_env.state(); let mut wasi_stdin = state.stdin().unwrap().unwrap();