From e902b0ca1f7afe712c08f91249fe1e3f565e69da Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 16 May 2026 10:22:02 +0900 Subject: [PATCH 1/3] Move host-facing logic into host_env crate --- .cspell.json | 3 +- .github/workflows/ci.yaml | 4 + Cargo.lock | 592 +++-- Cargo.toml | 1 - Lib/test/test_ctypes/test_dlerror.py | 2 - Lib/test/test_syslog.py | 2 - crates/host_env/Cargo.toml | 34 + crates/host_env/src/cert_store.rs | 134 + crates/host_env/src/crt_fd_unsupported.rs | 173 ++ crates/host_env/src/ctypes.rs | 2724 +++++++++++++++++++++ crates/host_env/src/errno.rs | 43 + crates/host_env/src/faulthandler.rs | 511 ++++ crates/host_env/src/fcntl.rs | 87 +- crates/host_env/src/grp.rs | 53 + crates/host_env/src/io.rs | 254 ++ crates/host_env/src/io_unsupported.rs | 225 ++ crates/host_env/src/lib.rs | 46 +- crates/host_env/src/locale.rs | 164 ++ crates/host_env/src/mmap.rs | 371 +++ crates/host_env/src/msvcrt.rs | 16 +- crates/host_env/src/multiprocessing.rs | 513 ++++ crates/host_env/src/nt.rs | 2204 ++++++++++++++++- crates/host_env/src/os.rs | 236 +- crates/host_env/src/overlapped.rs | 1239 ++++++++++ crates/host_env/src/posix.rs | 1611 +++++++++++- crates/host_env/src/posix_wasi.rs | 103 + crates/host_env/src/pwd.rs | 61 + crates/host_env/src/resource.rs | 87 + crates/host_env/src/select.rs | 148 +- crates/host_env/src/signal.rs | 352 +++ crates/host_env/src/socket.rs | 870 +++++++ crates/host_env/src/syslog.rs | 20 +- crates/host_env/src/termios.rs | 100 +- crates/host_env/src/testconsole.rs | 48 + crates/host_env/src/thread.rs | 42 + crates/host_env/src/time.rs | 557 ++++- crates/host_env/src/winapi.rs | 1381 ++++++++++- crates/host_env/src/windows.rs | 303 +++ crates/host_env/src/winreg.rs | 516 ++++ crates/host_env/src/wmi.rs | 676 +++++ crates/stdlib/Cargo.toml | 26 - crates/stdlib/src/_testconsole.rs | 45 +- crates/stdlib/src/faulthandler.rs | 442 +--- crates/stdlib/src/fcntl.rs | 39 +- crates/stdlib/src/grp.rs | 43 +- crates/stdlib/src/locale.rs | 247 +- crates/stdlib/src/mmap.rs | 496 ++-- crates/stdlib/src/multiprocessing.rs | 560 ++--- crates/stdlib/src/openssl.rs | 51 +- crates/stdlib/src/overlapped.rs | 1232 ++-------- crates/stdlib/src/posixsubprocess.rs | 328 +-- crates/stdlib/src/resource.rs | 31 +- crates/stdlib/src/select.rs | 98 +- crates/stdlib/src/socket.rs | 732 ++---- crates/stdlib/src/ssl.rs | 132 +- crates/stdlib/src/termios.rs | 151 +- crates/vm/Cargo.toml | 43 - crates/vm/src/exceptions.rs | 7 - crates/vm/src/getpath.rs | 9 +- crates/vm/src/readline.rs | 4 +- crates/vm/src/signal.rs | 1 + crates/vm/src/stdlib/_codecs.rs | 707 ++---- crates/vm/src/stdlib/_ctypes.rs | 430 +--- crates/vm/src/stdlib/_ctypes/array.rs | 389 +-- crates/vm/src/stdlib/_ctypes/base.rs | 743 ++---- crates/vm/src/stdlib/_ctypes/function.rs | 825 ++----- crates/vm/src/stdlib/_ctypes/library.rs | 150 -- crates/vm/src/stdlib/_ctypes/pointer.rs | 212 +- crates/vm/src/stdlib/_ctypes/simple.rs | 393 +-- crates/vm/src/stdlib/_io.rs | 836 +------ crates/vm/src/stdlib/_signal.rs | 347 +-- crates/vm/src/stdlib/_stat.rs | 127 +- crates/vm/src/stdlib/_thread.rs | 45 +- crates/vm/src/stdlib/_winapi.rs | 1732 +++---------- crates/vm/src/stdlib/_wmi.rs | 683 +----- crates/vm/src/stdlib/builtins.rs | 6 +- crates/vm/src/stdlib/errno.rs | 41 +- crates/vm/src/stdlib/msvcrt.rs | 5 +- crates/vm/src/stdlib/nt.rs | 1349 +--------- crates/vm/src/stdlib/os.rs | 589 +---- crates/vm/src/stdlib/posix.rs | 861 ++----- crates/vm/src/stdlib/pwd.rs | 55 +- crates/vm/src/stdlib/sys.rs | 130 +- crates/vm/src/stdlib/time.rs | 546 ++--- crates/vm/src/stdlib/winreg.rs | 413 +--- crates/vm/src/vm/mod.rs | 30 +- crates/vm/src/windows.rs | 478 +--- host_env_proposal.md | 494 ++++ 88 files changed, 20330 insertions(+), 13509 deletions(-) create mode 100644 crates/host_env/src/cert_store.rs create mode 100644 crates/host_env/src/crt_fd_unsupported.rs create mode 100644 crates/host_env/src/ctypes.rs create mode 100644 crates/host_env/src/errno.rs create mode 100644 crates/host_env/src/faulthandler.rs create mode 100644 crates/host_env/src/grp.rs create mode 100644 crates/host_env/src/io.rs create mode 100644 crates/host_env/src/io_unsupported.rs create mode 100644 crates/host_env/src/locale.rs create mode 100644 crates/host_env/src/mmap.rs create mode 100644 crates/host_env/src/multiprocessing.rs create mode 100644 crates/host_env/src/overlapped.rs create mode 100644 crates/host_env/src/posix_wasi.rs create mode 100644 crates/host_env/src/pwd.rs create mode 100644 crates/host_env/src/resource.rs create mode 100644 crates/host_env/src/socket.rs create mode 100644 crates/host_env/src/testconsole.rs create mode 100644 crates/host_env/src/thread.rs create mode 100644 crates/host_env/src/winreg.rs create mode 100644 crates/host_env/src/wmi.rs delete mode 100644 crates/vm/src/stdlib/_ctypes/library.rs create mode 100644 host_env_proposal.md diff --git a/.cspell.json b/.cspell.json index f05f2adcd65..067a2e323f0 100644 --- a/.cspell.json +++ b/.cspell.json @@ -49,7 +49,8 @@ "ignorePaths": [ "**/__pycache__/**", "target/**", - "Lib/**" + "Lib/**", + "crates/host_env/**" ], // words - list of words to be always considered correct // (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6261296c74b..65e690b19ea 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -558,6 +558,10 @@ jobs: key: prek-${{ hashFiles('.pre-commit-config.yaml') }} path: ~/.cache/prek + - name: restore git permissions + if: ${{ !cancelled() }} + run: sudo chown -R "$(id -u):$(id -g)" .git + - name: reviewdog if: ${{ !cancelled() }} uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # v1.24.0 diff --git a/Cargo.lock b/Cargo.lock index 4e9b54bb8aa..9828ee77105 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -249,9 +249,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-fips-sys" -version = "0.13.13" +version = "0.13.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bce4948d2520386c6d92a6ea2d472300257702242e5a1d01d6add52bd2e7c1" +checksum = "d3d619165468401dec3caa3366ebffbcb83f2f31883e5b3932f8e2dec2ddc568" dependencies = [ "bindgen 0.72.1", "cc", @@ -303,7 +303,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -323,7 +323,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -345,9 +345,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitflagset" @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "bytes" @@ -464,9 +464,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -558,18 +558,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -607,9 +607,9 @@ checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" @@ -1038,18 +1038,18 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", @@ -1143,9 +1143,9 @@ checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -1194,15 +1194,15 @@ checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flagset" @@ -1262,6 +1262,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1297,26 +1303,25 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] @@ -1393,11 +1398,24 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.33.0" @@ -1432,6 +1450,9 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] [[package]] name = "hashbrown" @@ -1439,7 +1460,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -1492,9 +1513,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1646,6 +1667,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "indexmap" version = "2.14.0" @@ -1654,6 +1681,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] @@ -1722,15 +1751,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -1741,9 +1770,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -1811,10 +1840,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1850,6 +1881,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lexical-parse-float" version = "1.0.6" @@ -1883,9 +1920,9 @@ checksum = "803ec87c9cfb29b9d2633f20cba1f488db3fd53f2158b1024cbefb47ba05d413" [[package]] name = "libbz2-rs-sys" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" [[package]] name = "libc" @@ -1960,11 +1997,10 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", "libc", ] @@ -1996,9 +2032,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2194,7 +2230,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -2207,7 +2243,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -2245,9 +2281,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2318,9 +2354,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -2340,7 +2376,7 @@ version = "0.10.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", @@ -2367,9 +2403,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] @@ -2395,9 +2431,9 @@ checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc" [[package]] name = "ordermap" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa78c92071bbd3628c22b1a964f7e0eb201dc1456555db072beb1662ecd6715" +checksum = "7f7476a5b122ff1fce7208e7ee9dccd0a516e835f5b8b19b8f3c98a34cf757c1" dependencies = [ "indexmap", ] @@ -2505,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -2551,15 +2587,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs5" @@ -2590,9 +2620,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plotters" @@ -2635,24 +2665,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "serde_core", "writeable", @@ -2823,6 +2853,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "1.1.1" @@ -2844,9 +2880,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2903,9 +2939,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2933,7 +2969,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2969,13 +3005,13 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "952ddbfc6f9f64d006c3efd8c9851a6ba2f2b944ba94730db255d55006e0ffda" +checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown 0.15.5", + "hashbrown 0.17.0", "log", "rustc-hash", "smallvec", @@ -2983,9 +3019,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2995,9 +3031,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3006,9 +3042,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "region" @@ -3059,9 +3095,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -3087,7 +3123,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -3096,9 +3132,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.39" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "once_cell", @@ -3131,9 +3167,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -3214,7 +3250,7 @@ name = "rustpython-codegen" version = "0.5.0" dependencies = [ "ahash", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "itertools 0.14.0", "log", @@ -3237,7 +3273,7 @@ name = "rustpython-common" version = "0.5.0" dependencies = [ "ascii", - "bitflags 2.11.0", + "bitflags 2.11.1", "getrandom 0.3.4", "itertools 0.14.0", "libc", @@ -3272,7 +3308,7 @@ dependencies = [ name = "rustpython-compiler-core" version = "0.5.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bitflagset", "itertools 0.14.0", "lz4_flex", @@ -3325,11 +3361,22 @@ dependencies = [ name = "rustpython-host_env" version = "0.5.0" dependencies = [ + "bitflags 2.11.1", + "junction", "libc", + "libffi", + "libloading 0.9.0", + "memmap2", "nix 0.31.2", "num-traits", + "num_cpus", + "parking_lot", + "paste", + "rustix", "rustpython-wtf8", + "schannel", "termios", + "uname", "widestring", "windows-sys 0.61.2", ] @@ -3379,7 +3426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f021ff72cabf5e2cd6d8ec8813d376a8445a228dc610ab56c27bd9054cda70d4" dependencies = [ "aho-corasick", - "bitflags 2.11.0", + "bitflags 2.11.1", "compact_str", "get-size2", "is-macro", @@ -3397,7 +3444,7 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e6ee78bd9671fb5766664b2695fe1f2a92a961f4d9101646c570d8acdb1e0b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bstr", "compact_str", "get-size2", @@ -3447,7 +3494,7 @@ dependencies = [ name = "rustpython-sre_engine" version = "0.5.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "criterion", "icu_properties", "num_enum", @@ -3495,9 +3542,7 @@ dependencies = [ "malachite-bigint", "md-5", "memchr", - "memmap2", "mt19937", - "nix 0.31.2", "num-complex", "num-traits", "num_enum", @@ -3514,7 +3559,6 @@ dependencies = [ "pkcs8", "pymath", "rand_core 0.9.5", - "rustix", "rustls", "rustls-native-certs", "rustls-pemfile", @@ -3528,14 +3572,12 @@ dependencies = [ "rustpython-ruff_source_file", "rustpython-ruff_text_size", "rustpython-vm", - "schannel", "sha-1", "sha2", "sha3", "socket2", "system-configuration", "tcl-sys", - "termios", "tk-sys", "ucd", "unic-ucd-age", @@ -3543,7 +3585,6 @@ dependencies = [ "uuid", "webpki-roots", "widestring", - "windows-sys 0.61.2", "x509-cert", "x509-parser", "xml", @@ -3559,13 +3600,12 @@ version = "0.5.0" dependencies = [ "ahash", "ascii", - "bitflags 2.11.0", + "bitflags 2.11.1", "bstr", "caseless", "chrono", "constant_time_eq", "crossbeam-utils", - "errno", "exitcode", "flame", "flamer", @@ -3579,18 +3619,13 @@ dependencies = [ "indexmap", "is-macro", "itertools 0.14.0", - "junction", "libc", - "libffi", - "libloading 0.9.0", "log", "malachite-bigint", "memchr", - "nix 0.31.2", "num-complex", "num-integer", "num-traits", - "num_cpus", "num_enum", "optional", "parking_lot", @@ -3621,7 +3656,6 @@ dependencies = [ "wasm-bindgen", "which", "widestring", - "windows-sys 0.61.2", "writeable", ] @@ -3664,7 +3698,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "clipboard-win", "home", @@ -3681,9 +3715,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe_arch" @@ -3740,11 +3774,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3753,9 +3787,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3823,9 +3857,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -3865,9 +3899,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest", "keccak", @@ -3898,9 +3932,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" @@ -3932,9 +3966,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -4037,7 +4071,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4054,9 +4088,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tcl-sys" @@ -4069,12 +4103,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4206,9 +4240,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4251,9 +4285,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", @@ -4266,27 +4300,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "twox-hash" @@ -4296,9 +4330,9 @@ checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd" @@ -4306,6 +4340,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4fa6e588762366f1eb4991ce59ad1b93651d0b769dfb4e4d1c5c4b943d1159" +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4349,9 +4392,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -4364,9 +4407,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -4374,6 +4417,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_names2" version = "1.3.0" @@ -4403,7 +4452,7 @@ dependencies = [ "getopts", "log", "phf_codegen", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -4413,7 +4462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1262662dc96937c71115228ce2e1d30f41db71a7a45d3459e98783ef94052214" dependencies = [ "phf_codegen", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -4487,18 +4536,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -4509,23 +4567,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4533,9 +4587,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -4546,13 +4600,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "wasmtime-internal-core" version = "44.0.1" @@ -4577,9 +4665,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -4587,9 +4675,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -4614,9 +4702,9 @@ dependencies = [ [[package]] name = "wide" -version = "1.1.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac11b009ebeae802ed758530b6496784ebfee7a87b9abfbcaf3bbe25b814eb25" +checksum = "c9479f84a757f819cfab37295955906479181395de83add28f74975fde083141" dependencies = [ "bytemuck", "safe_arch", @@ -4876,9 +4964,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "winresource" @@ -4895,6 +4983,94 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "write16" @@ -4904,9 +5080,9 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-cert" @@ -4970,18 +5146,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4990,18 +5166,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -5072,6 +5248,6 @@ checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 80976b1e1c7..7b8c070b9aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,6 @@ digest = "0.10.7" dns-lookup = "3.0" dyn-clone = "1.0.10" exitcode = "1.1.2" -errno = "0.3" flame = "0.2.2" flamer = "0.5" flate2 = { version = "1.1.9", default-features = false } diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py index ea2d97d9000..5658234f9ec 100644 --- a/Lib/test/test_ctypes/test_dlerror.py +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -55,8 +55,6 @@ class TestNullDlsym(unittest.TestCase): this 'dlsym returned NULL -> throw Error' rule. """ - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_null_dlsym(self): import subprocess import tempfile diff --git a/Lib/test/test_syslog.py b/Lib/test/test_syslog.py index b378d62e5cf..9ad2cc51819 100644 --- a/Lib/test/test_syslog.py +++ b/Lib/test/test_syslog.py @@ -43,8 +43,6 @@ def test_setlogmask(self): self.assertEqual(syslog.setlogmask(0), mask) self.assertEqual(syslog.setlogmask(oldmask), mask) - # TODO: RUSTPYTHON; AssertionError: 12 is not false - @unittest.expectedFailure def test_log_mask(self): mask = syslog.LOG_UPTO(syslog.LOG_WARNING) self.assertTrue(mask & syslog.LOG_MASK(syslog.LOG_WARNING)) diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index 039c925868c..9903aae026e 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -11,30 +11,64 @@ license.workspace = true [dependencies] rustpython-wtf8 = { workspace = true } +bitflags = { workspace = true } libc = { workspace = true } num-traits = { workspace = true } +parking_lot = { workspace = true } +paste = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } +uname = "0.1.1" + +[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))'.dependencies] +rustix = { workspace = true } + +[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] +num_cpus = "1.17.0" [target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "redox")))'.dependencies] termios = { workspace = true } +[target.'cfg(any(unix, windows))'.dependencies] +memmap2 = "0.9.10" +libloading = "0.9" + +[target.'cfg(all(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android"), not(any(target_env = "musl", target_env = "sgx"))))'.dependencies] +libffi = { workspace = true, features = ["system"] } + [target.'cfg(windows)'.dependencies] +junction = { workspace = true } +schannel = { workspace = true } widestring = { workspace = true } windows-sys = { workspace = true, features = [ "Win32_Foundation", "Win32_Globalization", + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", + "Win32_Security", + "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_Diagnostics_Debug", + "Win32_System_Environment", + "Win32_System_IO", "Win32_System_Ioctl", + "Win32_System_JobObjects", + "Win32_System_Kernel", "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Pipes", + "Win32_System_Performance", + "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", + "Win32_System_WindowsProgramming", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] } [lints] diff --git a/crates/host_env/src/cert_store.rs b/crates/host_env/src/cert_store.rs new file mode 100644 index 00000000000..f95716faa99 --- /dev/null +++ b/crates/host_env/src/cert_store.rs @@ -0,0 +1,134 @@ +use std::io; + +use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; +use windows_sys::Win32::{ + Foundation::{CRYPT_E_NOT_FOUND, GetLastError}, + Security::Cryptography::{ + CERT_CONTEXT, CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW, + PKCS_7_ASN_ENCODING, X509_ASN_ENCODING, + }, +}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EncodingType { + X509Asn, + Pkcs7Asn, + Other(u32), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CertificateUses { + All, + Oids(Vec), +} + +#[derive(Debug)] +pub struct CertificateEntry { + pub der: Vec, + pub encoding: EncodingType, + pub valid_uses: io::Result, +} + +#[derive(Debug)] +pub struct CertificateEntries { + pub had_open_store: bool, + pub entries: Vec, +} + +#[derive(Debug)] +pub struct CrlEntry { + pub der: Vec, + pub encoding: EncodingType, +} + +fn encoding_type(raw: u32) -> EncodingType { + if raw & X509_ASN_ENCODING != 0 { + EncodingType::X509Asn + } else if raw & PKCS_7_ASN_ENCODING != 0 { + EncodingType::Pkcs7Asn + } else { + EncodingType::Other(raw) + } +} + +pub fn enum_certificates(store_name: &str) -> CertificateEntries { + let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; + let mut had_open_store = false; + let mut entries = Vec::new(); + + for open in open_fns { + let Ok(store) = open(store_name) else { + continue; + }; + had_open_store = true; + + for cert in store.certs() { + let encoding = unsafe { + let ptr = cert.as_ptr() as *const CERT_CONTEXT; + encoding_type((*ptr).dwCertEncodingType) + }; + let valid_uses = cert.valid_uses().map_or_else( + |err| Err(io::Error::other(err)), + |uses| { + Ok(match uses { + ValidUses::All => CertificateUses::All, + ValidUses::Oids(oids) => CertificateUses::Oids(oids.into_iter().collect()), + }) + }, + ); + entries.push(CertificateEntry { + der: cert.to_der().to_owned(), + encoding, + valid_uses, + }); + } + } + + CertificateEntries { + had_open_store, + entries, + } +} + +pub fn enum_crls(store_name: &str) -> io::Result> { + let store_name_wide: Vec = store_name + .encode_utf16() + .chain(core::iter::once(0)) + .collect(); + + let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) }; + if store.is_null() { + return Err(io::Error::last_os_error()); + } + + let mut result = Vec::new(); + let mut crl_context: *const CRL_CONTEXT = core::ptr::null(); + loop { + crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) }; + if crl_context.is_null() { + let err = unsafe { GetLastError() }; + if err != CRYPT_E_NOT_FOUND as u32 { + unsafe { + CertCloseStore(store, 0); + } + return Err(io::Error::from_raw_os_error(err as i32)); + } + break; + } + + let crl = unsafe { &*crl_context }; + let der = + unsafe { core::slice::from_raw_parts(crl.pbCrlEncoded, crl.cbCrlEncoded as usize) } + .to_vec(); + result.push(CrlEntry { + der, + encoding: encoding_type(crl.dwCertEncodingType), + }); + } + + unsafe { + CertCloseStore(store, 0); + } + + Ok(result) +} diff --git a/crates/host_env/src/crt_fd_unsupported.rs b/crates/host_env/src/crt_fd_unsupported.rs new file mode 100644 index 00000000000..b6a7918b31c --- /dev/null +++ b/crates/host_env/src/crt_fd_unsupported.rs @@ -0,0 +1,173 @@ +use alloc::fmt; +use core::marker::PhantomData; +use std::{ffi, io}; + +pub type Offset = i64; +pub type Raw = i32; + +const EBADF: i32 = 9; + +#[repr(transparent)] +pub struct Owned { + fd: Raw, +} + +impl fmt::Debug for Owned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("crt_fd::Owned") + .field(&self.as_raw()) + .finish() + } +} + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct Borrowed<'fd> { + fd: Raw, + _marker: PhantomData<&'fd Owned>, +} + +impl PartialEq for Borrowed<'_> { + fn eq(&self, other: &Self) -> bool { + self.as_raw() == other.as_raw() + } +} + +impl Eq for Borrowed<'_> {} + +impl fmt::Debug for Borrowed<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("crt_fd::Borrowed") + .field(&self.as_raw()) + .finish() + } +} + +impl Owned { + /// Create a `crt_fd::Owned` from a raw file descriptor. + /// + /// # Safety + /// + /// `fd` must be a valid file descriptor for the embedding host. + #[inline] + pub const unsafe fn from_raw(fd: Raw) -> Self { + Self { fd } + } + + /// Create a `crt_fd::Owned` from a raw file descriptor. + /// + /// Returns an error if `fd` is negative. + /// + /// # Safety + /// + /// `fd` must be a valid file descriptor for the embedding host. + #[inline] + pub unsafe fn try_from_raw(fd: Raw) -> io::Result { + if fd < 0 { + Err(ebadf()) + } else { + Ok(unsafe { Self::from_raw(fd) }) + } + } + + #[inline] + pub const fn borrow(&self) -> Borrowed<'_> { + unsafe { Borrowed::borrow_raw(self.as_raw()) } + } + + #[inline] + pub const fn as_raw(&self) -> Raw { + self.fd + } + + #[inline] + pub fn into_raw(self) -> Raw { + let fd = self.fd; + core::mem::forget(self); + fd + } + + pub fn leak<'fd>(self) -> Borrowed<'fd> { + unsafe { Borrowed::borrow_raw(self.into_raw()) } + } +} + +impl Drop for Owned { + fn drop(&mut self) {} +} + +impl<'fd> Borrowed<'fd> { + /// Create a `crt_fd::Borrowed` from a raw file descriptor. + /// + /// # Safety + /// + /// `fd` must be a valid file descriptor for the embedding host. + #[inline] + pub const unsafe fn borrow_raw(fd: Raw) -> Self { + Self { + fd, + _marker: PhantomData, + } + } + + /// Create a `crt_fd::Borrowed` from a raw file descriptor. + /// + /// Returns an error if `fd` is negative. + /// + /// # Safety + /// + /// `fd` must be a valid file descriptor for the embedding host. + #[inline] + pub unsafe fn try_borrow_raw(fd: Raw) -> io::Result { + if fd < 0 { + Err(ebadf()) + } else { + Ok(unsafe { Self::borrow_raw(fd) }) + } + } + + #[inline] + pub const fn as_raw(self) -> Raw { + self.fd + } +} + +#[inline] +fn ebadf() -> io::Error { + io::Error::from_raw_os_error(EBADF) +} + +pub fn open(_path: &ffi::CStr, _flags: i32, _mode: i32) -> io::Result { + Err(unsupported()) +} + +pub fn openat(_dir: Borrowed<'_>, _path: &ffi::CStr, _flags: i32, _mode: i32) -> io::Result { + Err(unsupported()) +} + +pub fn fsync(_fd: Borrowed<'_>) -> io::Result<()> { + Err(ebadf()) +} + +pub fn close(_fd: Owned) -> io::Result<()> { + Err(ebadf()) +} + +pub fn ftruncate(_fd: Borrowed<'_>, _len: Offset) -> io::Result<()> { + Err(ebadf()) +} + +pub fn write(_fd: Borrowed<'_>, _buf: &[u8]) -> io::Result { + Err(ebadf()) +} + +pub fn read(_fd: Borrowed<'_>, _buf: &mut [u8]) -> io::Result { + Err(ebadf()) +} + +fn unsupported() -> io::Error { + io::Error::new( + io::ErrorKind::Unsupported, + "host file descriptors are unsupported on this platform", + ) +} diff --git a/crates/host_env/src/ctypes.rs b/crates/host_env/src/ctypes.rs new file mode 100644 index 00000000000..ae64b06cfe0 --- /dev/null +++ b/crates/host_env/src/ctypes.rs @@ -0,0 +1,2724 @@ +use alloc::borrow::Cow; +use core::ffi::{ + CStr, c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, + c_ulong, c_ulonglong, c_ushort, c_void, +}; +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +use libffi::middle::Type; +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +use libffi::{ + low, + middle::{Arg, Cif, Closure, CodePtr}, +}; +#[cfg(any(unix, windows))] +use libloading::Library; +#[cfg(unix)] +use libloading::os::unix::Library as UnixLibrary; +#[cfg(any(unix, windows))] +use parking_lot::{Mutex, RwLock}; +use rustpython_wtf8::Wtf8; +use rustpython_wtf8::Wtf8Buf; +#[cfg(any(unix, windows))] +use std::{collections::HashMap, ffi::OsStr, sync::OnceLock}; + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub type FfiType = Type; + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub type FfiArg<'a> = Arg<'a>; + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub type FfiCodePtr = CodePtr; + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub type FfiCif = low::ffi_cif; + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +type CallbackIntResult = low::ffi_arg; + +#[cfg(not(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +)))] +type CallbackIntResult = c_int; + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub type WChar = libc::wchar_t; +#[cfg(not(any(unix, windows, target_os = "wasi")))] +pub type WChar = u32; + +#[cfg(any(unix, windows, target_os = "wasi"))] +type TimeT = libc::time_t; +#[cfg(not(any(unix, windows, target_os = "wasi")))] +type TimeT = i64; + +std::thread_local! { + /// Thread-local ctypes errno, separate from the platform errno. + #[allow(clippy::missing_const_for_thread_local)] + static CTYPES_LOCAL_ERRNO: core::cell::Cell = const { core::cell::Cell::new(0) }; +} + +pub fn get_errno() -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| e.get()) +} + +pub fn set_errno(value: i32) -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +#[cfg(not(windows))] +pub fn with_swapped_errno(f: F) -> R +where + F: FnOnce() -> R, +{ + let saved_errno = crate::os::get_errno(); + let saved_ctypes_errno = CTYPES_LOCAL_ERRNO.with(|e| e.get()); + crate::os::set_errno(saved_ctypes_errno); + + let result = f(); + + let new_error = crate::os::get_errno(); + CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error)); + crate::os::set_errno(saved_errno); + + result +} + +pub fn with_callback_errno_preserved(use_errno: bool, f: F) -> R +where + F: FnOnce() -> R, +{ + if !use_errno { + return f(); + } + + let saved = crate::os::get_errno(); + let result = f(); + let _current = crate::os::get_errno(); + crate::os::set_errno(saved); + result +} + +#[cfg(windows)] +std::thread_local! { + /// Thread-local ctypes last_error, separate from the Windows last error. + static CTYPES_LOCAL_LAST_ERROR: core::cell::Cell = const { core::cell::Cell::new(0) }; +} + +#[cfg(windows)] +pub fn get_last_error() -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()) +} + +#[cfg(windows)] +pub fn set_last_error(value: u32) -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +#[cfg(windows)] +pub fn with_swapped_last_error(f: F) -> R +where + F: FnOnce() -> R, +{ + let saved_last_error = crate::windows::get_last_error(); + let saved_ctypes_last_error = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()); + crate::windows::set_last_error(saved_ctypes_last_error); + + let result = f(); + + let new_error = crate::windows::get_last_error(); + CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error)); + crate::windows::set_last_error(saved_last_error); + + result +} + +#[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") +))] +const LONG_DOUBLE_SIZE: usize = core::mem::size_of::(); + +#[cfg(target_os = "windows")] +const LONG_DOUBLE_SIZE: usize = core::mem::size_of::(); + +#[cfg(not(any( + all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") + ), + target_os = "windows" +)))] +const LONG_DOUBLE_SIZE: usize = core::mem::size_of::(); + +pub fn simple_type_size(ty: &str) -> Option { + match ty { + "c" | "b" => Some(core::mem::size_of::()), + "u" => Some(core::mem::size_of::()), + "B" | "?" => Some(core::mem::size_of::()), + "h" | "v" => Some(core::mem::size_of::()), + "H" => Some(core::mem::size_of::()), + "i" => Some(core::mem::size_of::()), + "I" => Some(core::mem::size_of::()), + "l" => Some(core::mem::size_of::()), + "L" => Some(core::mem::size_of::()), + "q" => Some(core::mem::size_of::()), + "Q" => Some(core::mem::size_of::()), + "f" => Some(core::mem::size_of::()), + "d" => Some(core::mem::size_of::()), + "g" => Some(LONG_DOUBLE_SIZE), + "z" | "Z" | "P" | "X" | "O" => Some(core::mem::size_of::()), + "void" => Some(0), + _ => None, + } +} + +pub fn simple_type_align(ty: &str) -> Option { + match ty { + "c" | "b" => Some(core::mem::align_of::()), + "u" => Some(core::mem::align_of::()), + "B" | "?" => Some(core::mem::align_of::()), + "h" | "v" => Some(core::mem::align_of::()), + "H" => Some(core::mem::align_of::()), + "i" => Some(core::mem::align_of::()), + "I" => Some(core::mem::align_of::()), + "l" => Some(core::mem::align_of::()), + "L" => Some(core::mem::align_of::()), + "q" => Some(core::mem::align_of::()), + "Q" => Some(core::mem::align_of::()), + "f" => Some(core::mem::align_of::()), + "d" => Some(core::mem::align_of::()), + "g" => { + #[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") + ))] + { + Some(core::mem::align_of::()) + } + #[cfg(not(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") + )))] + { + Some(core::mem::align_of::()) + } + } + "z" | "Z" | "P" | "X" | "O" => Some(core::mem::align_of::()), + "void" => Some(0), + _ => None, + } +} + +pub fn c_long_bytes_endian(value: i128, swapped: bool) -> Vec { + let value = value as c_long; + int_to_sized_bytes_endian(value as i64, core::mem::size_of::(), swapped) +} + +pub fn c_ulong_bytes_endian(value: i128, swapped: bool) -> Vec { + let value = value as c_ulong; + uint_to_sized_bytes_endian(value as u64, core::mem::size_of::(), swapped) +} + +pub fn simple_type_pep3118_code(code: char) -> char { + match code { + 'i' if core::mem::size_of::() == 2 => 'h', + 'i' if core::mem::size_of::() == 4 => 'i', + 'i' if core::mem::size_of::() == 8 => 'q', + 'I' if core::mem::size_of::() == 2 => 'H', + 'I' if core::mem::size_of::() == 4 => 'I', + 'I' if core::mem::size_of::() == 8 => 'Q', + 'l' if core::mem::size_of::() == 4 => 'l', + 'l' if core::mem::size_of::() == 8 => 'q', + 'L' if core::mem::size_of::() == 4 => 'L', + 'L' if core::mem::size_of::() == 8 => 'Q', + '?' if core::mem::size_of::() == 1 => '?', + '?' if core::mem::size_of::() == 2 => 'H', + '?' if core::mem::size_of::() == 4 => 'L', + '?' if core::mem::size_of::() == 8 => 'Q', + _ => code, + } +} + +pub enum StringAtError { + NullPointer, + TooLong, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RawMemoryViewError { + NullPointer, + NegativeSize, +} + +#[derive(Debug, Clone, Copy)] +pub struct RawMemoryView { + ptr: usize, + size: usize, + readonly: bool, +} + +impl RawMemoryView { + pub fn new(ptr: usize, size: isize, readonly: bool) -> Result { + if ptr == 0 { + return Err(RawMemoryViewError::NullPointer); + } + if size < 0 { + return Err(RawMemoryViewError::NegativeSize); + } + Ok(Self { + ptr, + size: size as usize, + readonly, + }) + } + + pub fn size(self) -> usize { + self.size + } + + pub fn readonly(self) -> bool { + self.readonly + } + + /// # Safety + /// + /// The stored pointer must remain valid for `self.size` bytes. + pub unsafe fn bytes(self) -> &'static [u8] { + unsafe { borrow_memory(self.ptr as *const u8, self.size) } + } + + /// # Safety + /// + /// The stored pointer must remain valid and uniquely writable for + /// `self.size` bytes. + pub unsafe fn bytes_mut(self) -> &'static mut [u8] { + unsafe { borrow_memory_mut(self.ptr as *mut u8, self.size) } + } +} + +// These match the current RustPython _ctypes surface exactly. +pub const RTLD_LOCAL: i32 = 0; +pub const RTLD_GLOBAL: i32 = 0; +pub const SIZEOF_TIME_T: usize = core::mem::size_of::(); + +#[cfg(all(unix, not(target_os = "wasi")))] +pub fn dlopen_mode(load_flags: Option) -> i32 { + load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW +} + +#[cfg(not(all(unix, not(target_os = "wasi"))))] +pub fn dlopen_mode(load_flags: Option) -> i32 { + load_flags.unwrap_or(0) +} + +#[cfg(target_os = "macos")] +pub fn dyld_shared_cache_contains_path(path: &str) -> Result { + let c_path = alloc::ffi::CString::new(path)?; + + unsafe extern "C" { + fn _dyld_shared_cache_contains_path(path: *const c_char) -> bool; + } + + Ok(unsafe { _dyld_shared_cache_contains_path(c_path.as_ptr()) }) +} + +/// # Safety +/// +/// `ptr` must be valid to read until the first NUL byte. +pub unsafe fn strlen(ptr: *const c_char) -> usize { + #[cfg(any(unix, windows, target_os = "wasi"))] + { + unsafe { libc::strlen(ptr) } + } + #[cfg(not(any(unix, windows, target_os = "wasi")))] + { + let mut len = 0; + while unsafe { *ptr.add(len) } != 0 { + len += 1; + } + len + } +} + +/// # Safety +/// +/// `ptr` must be valid to read until the first NUL wide character. +pub unsafe fn wcslen(ptr: *const WChar) -> usize { + let mut len = 0; + while unsafe { *ptr.add(len) } != 0 as WChar { + len += 1; + } + len +} + +/// # Safety +/// +/// `ptr` must be a valid NUL-terminated C string. +pub unsafe fn read_c_string_bytes(ptr: *const c_char) -> Vec { + unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec() +} + +#[inline] +pub fn read_pointer_from_buffer(buffer: &[u8]) -> usize { + const PTR_SIZE: usize = core::mem::size_of::(); + buffer + .first_chunk::() + .copied() + .map_or(0, usize::from_ne_bytes) +} + +pub const WCHAR_SIZE: usize = core::mem::size_of::(); + +#[inline] +pub fn wchar_from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < WCHAR_SIZE { + return None; + } + Some(if WCHAR_SIZE == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }) +} + +#[inline] +pub fn wchar_to_bytes(ch: u32, buffer: &mut [u8]) { + if WCHAR_SIZE == 2 { + if buffer.len() >= 2 { + buffer[..2].copy_from_slice(&(ch as u16).to_ne_bytes()); + } + } else if buffer.len() >= 4 { + buffer[..4].copy_from_slice(&ch.to_ne_bytes()); + } +} + +pub fn wstring_from_bytes(buffer: &[u8]) -> String { + let mut chars = Vec::new(); + for chunk in buffer.chunks(WCHAR_SIZE) { + if chunk.len() < WCHAR_SIZE { + break; + } + let Some(code) = wchar_from_bytes(chunk) else { + break; + }; + if code == 0 { + break; + } + if let Some(ch) = char::from_u32(code) { + chars.push(ch); + } + } + chars.into_iter().collect() +} + +pub fn wchar_array_field_value(buffer: &[u8]) -> String { + let wchars: Vec = buffer + .chunks(WCHAR_SIZE) + .filter_map(|chunk| wchar_from_bytes(chunk).filter(|&wchar| wchar != 0)) + .map(|wchar| wchar as WChar) + .collect(); + wide_chars_to_wtf8(&wchars).to_string() +} + +pub fn write_wchar_array_value(buffer: &mut [u8], s: &Wtf8) -> Result<(), WCharArrayWriteError> { + let wchar_count = buffer.len() / WCHAR_SIZE; + let char_count = s.code_points().count(); + + if char_count > wchar_count { + return Err(WCharArrayWriteError::TooLong); + } + + for (i, ch) in s.code_points().enumerate() { + let offset = i * WCHAR_SIZE; + wchar_to_bytes(ch.to_u32(), &mut buffer[offset..]); + } + + let terminator_offset = char_count * WCHAR_SIZE; + if terminator_offset + WCHAR_SIZE <= buffer.len() { + wchar_to_bytes(0, &mut buffer[terminator_offset..]); + } + Ok(()) +} + +pub fn encode_wtf8_to_wchar_padded(s: &Wtf8, size: usize) -> Vec { + let mut wchar_bytes = Vec::with_capacity(size); + for cp in s.code_points().take(size / WCHAR_SIZE) { + let mut bytes = [0u8; 4]; + wchar_to_bytes(cp.to_u32(), &mut bytes); + wchar_bytes.extend_from_slice(&bytes[..WCHAR_SIZE]); + } + while wchar_bytes.len() < size { + wchar_bytes.push(0); + } + wchar_bytes +} + +pub fn wchar_null_terminated_bytes(s: &Wtf8) -> Vec { + let wchars: Vec = s + .code_points() + .map(|cp| cp.to_u32() as WChar) + .chain(core::iter::once(0)) + .collect(); + vec_into_bytes(wchars) +} + +pub fn vec_into_bytes(vec: Vec) -> Vec { + let len = vec.len() * core::mem::size_of::(); + let cap = vec.capacity() * core::mem::size_of::(); + let ptr = vec.as_ptr() as *mut u8; + core::mem::forget(vec); + unsafe { Vec::from_raw_parts(ptr, len, cap) } +} + +pub enum IntegerValue { + Signed(i64), + Unsigned(u64), +} + +pub enum AddressValue { + ByteString(u8), + Integer(IntegerValue), + Float(f64), + Pointer(usize), + Bytes(Vec), +} + +pub enum AddressWriteValue<'a> { + Pointer(usize), + U8(u8), + I16(i16), + I32(i32), + I64(i64), + Float(f64), + Bytes(&'a [u8]), +} + +pub enum ArrayElementWriteValue<'a> { + Byte(u8), + Wchar(u32), + Pointer { value: usize, size: usize }, + Float { value: f64, size: usize }, + Bytes { bytes: &'a [u8], size: usize }, +} + +pub enum WCharArrayWriteError { + TooLong, +} + +pub enum SimpleStorageValue { + Byte(u8), + Wchar(u32), + Signed(i128), + Float(f64), + Bool(bool), + Pointer(usize), + ObjectId(usize), + Zero, +} + +pub enum DecodedValue { + Bytes(Vec), + Signed(i64), + Unsigned(u64), + Float(f64), + Bool(bool), + Pointer(usize), + String(String), + None, +} + +pub enum CallbackResultValue { + Signed(i64), + Unsigned(u64), + Float(f64), + Pointer(usize), + Bool(bool), +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub enum FfiArgRef<'a> { + U8(&'a u8), + I8(&'a i8), + U16(&'a u16), + I16(&'a i16), + U32(&'a u32), + I32(&'a i32), + U64(&'a u64), + I64(&'a i64), + F32(&'a f32), + F64(&'a f64), + Pointer(&'a usize), +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +#[derive(Debug, Clone, Copy)] +pub enum FfiValue { + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + F32(f32), + F64(f64), + Pointer(usize), +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub enum CallResult { + Void, + Pointer(usize), + Value(low::ffi_arg), +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub enum CdeclArgValue { + Pointer(isize), + Int(isize), +} + +pub const POINTER_SIZE: usize = core::mem::size_of::(); +pub const POINTER_FORMAT: &str = "X{}"; + +pub fn pointer_size() -> usize { + POINTER_SIZE +} + +pub fn pointer_format() -> &'static str { + POINTER_FORMAT +} + +pub fn has_pointer_width(buffer: &[u8]) -> bool { + buffer.len() >= POINTER_SIZE +} + +pub fn pointer_bytes(value: usize) -> Vec { + pointer_to_sized_bytes(value, POINTER_SIZE) +} + +pub fn null_pointer_bytes() -> Vec { + vec![0; POINTER_SIZE] +} + +pub fn zeroed_bytes(size: usize) -> Vec { + vec![0; size] +} + +pub fn copy_to_sized_bytes(src: &[u8], size: usize) -> Vec { + let mut result = zeroed_bytes(size); + let len = src.len().min(size); + result[..len].copy_from_slice(&src[..len]); + result +} + +pub fn char_array_assignment_bytes(src: &[u8]) -> &[u8] { + if let Some(null_pos) = src.iter().position(|&b| b == 0) { + &src[..=null_pos] + } else { + src + } +} + +pub fn char_array_field_value(buffer: &[u8]) -> &[u8] { + let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len()); + &buffer[..end] +} + +pub fn write_char_array_value(buffer: &mut [u8], src: &[u8]) { + buffer[..src.len()].copy_from_slice(src); + if src.len() < buffer.len() { + buffer[src.len()] = 0; + } +} + +pub fn write_char_array_raw(buffer: &mut [u8], src: &[u8]) { + buffer[..src.len()].copy_from_slice(src); +} + +pub fn write_prefix_limited(buffer: &mut [u8], src: &[u8], size: usize) { + let copy_size = size.min(buffer.len()).min(src.len()); + if copy_size > 0 { + buffer[..copy_size].copy_from_slice(&src[..copy_size]); + } +} + +pub fn pointer_to_sized_bytes_endian(value: usize, size: usize, swapped: bool) -> Vec { + let mut bytes = pointer_to_sized_bytes(value, size); + if swapped { + bytes.reverse(); + } + bytes +} + +pub fn write_pointer_to_buffer_at(buffer: &mut [u8], offset: usize, size: usize, value: usize) { + if offset + size <= buffer.len() { + let ptr_bytes = pointer_to_sized_bytes(value, size); + buffer[offset..offset + size].copy_from_slice(&ptr_bytes); + } +} + +pub fn write_array_element(buffer: &mut [u8], offset: usize, value: ArrayElementWriteValue<'_>) { + match value { + ArrayElementWriteValue::Byte(value) => { + if offset < buffer.len() { + buffer[offset] = value; + } + } + ArrayElementWriteValue::Wchar(value) => { + if offset + WCHAR_SIZE <= buffer.len() { + wchar_to_bytes(value, &mut buffer[offset..]); + } + } + ArrayElementWriteValue::Pointer { value, size } => { + write_pointer_to_buffer_at(buffer, offset, size, value); + } + ArrayElementWriteValue::Float { value, size } => { + if offset + size <= buffer.len() + && let Some(float_bytes) = float_to_sized_bytes(value, size) + { + buffer[offset..offset + size].copy_from_slice(&float_bytes); + } + } + ArrayElementWriteValue::Bytes { bytes, size } => { + let copy_len = bytes.len().min(size); + if offset + copy_len <= buffer.len() { + buffer[offset..offset + copy_len].copy_from_slice(&bytes[..copy_len]); + } + } + } +} + +pub fn read_array_element( + buffer: &[u8], + offset: usize, + element_size: usize, + type_code: Option<&str>, +) -> DecodedValue { + let Some(rest) = buffer.get(offset..) else { + return DecodedValue::Signed(0); + }; + match type_code { + Some("c") => DecodedValue::Bytes(vec![buffer.get(offset).copied().unwrap_or(0)]), + Some("u") => { + let value = wchar_from_bytes(rest) + .and_then(char::from_u32) + .map(|c| c.to_string()) + .unwrap_or_default(); + DecodedValue::String(value) + } + Some("z") => { + if offset + element_size > buffer.len() { + return DecodedValue::None; + } + let ptr_bytes = &buffer[offset..offset + element_size]; + let ptr_val = read_pointer_from_buffer(ptr_bytes); + unsafe { + match read_c_string_from_address(ptr_val) { + Some(bytes) => DecodedValue::Bytes(bytes), + None => DecodedValue::None, + } + } + } + Some("Z") => { + if offset + element_size > buffer.len() { + return DecodedValue::None; + } + let ptr_bytes = &buffer[offset..offset + element_size]; + let ptr_val = read_pointer_from_buffer(ptr_bytes); + unsafe { + match read_wide_string_from_address(ptr_val) { + Some(s) => DecodedValue::String(s.to_string()), + None => DecodedValue::None, + } + } + } + Some("f") => DecodedValue::Float( + rest.first_chunk::<4>() + .copied() + .map_or(0.0, f32::from_ne_bytes) as f64, + ), + Some("d" | "g") => DecodedValue::Float( + rest.first_chunk::<8>() + .copied() + .map_or(0.0, f64::from_ne_bytes), + ), + _ => { + if let Some(bytes) = rest.get(..element_size) { + let is_unsigned = matches!(type_code, Some("B" | "H" | "I" | "L" | "Q")); + match int_from_bytes(bytes, element_size, is_unsigned) { + IntegerValue::Signed(value) => DecodedValue::Signed(value), + IntegerValue::Unsigned(value) => DecodedValue::Unsigned(value), + } + } else { + DecodedValue::Signed(0) + } + } + } +} + +pub fn int_from_bytes(bytes: &[u8], size: usize, unsigned: bool) -> IntegerValue { + match (size, unsigned) { + (1, false) => IntegerValue::Signed(bytes[0] as i8 as i64), + (1, true) => IntegerValue::Unsigned(bytes[0].into()), + (2, false) => IntegerValue::Signed(i16::from_ne_bytes([bytes[0], bytes[1]]).into()), + (2, true) => IntegerValue::Unsigned(u16::from_ne_bytes([bytes[0], bytes[1]]).into()), + (4, false) => IntegerValue::Signed( + i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).into(), + ), + (4, true) => IntegerValue::Unsigned( + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).into(), + ), + (8, false) => IntegerValue::Signed(i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ])), + (8, true) => IntegerValue::Unsigned(u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ])), + _ => IntegerValue::Signed(0), + } +} + +pub fn int_to_sized_bytes(value: i64, size: usize) -> Vec { + match size { + 1 => (value as i8).to_ne_bytes().to_vec(), + 2 => (value as i16).to_ne_bytes().to_vec(), + 4 => (value as i32).to_ne_bytes().to_vec(), + 8 => value.to_ne_bytes().to_vec(), + _ => vec![0u8; size], + } +} + +pub fn uint_to_sized_bytes(value: u64, size: usize) -> Vec { + match size { + 1 => (value as u8).to_ne_bytes().to_vec(), + 2 => (value as u16).to_ne_bytes().to_vec(), + 4 => (value as u32).to_ne_bytes().to_vec(), + 8 => value.to_ne_bytes().to_vec(), + _ => vec![0u8; size], + } +} + +pub fn int_to_sized_bytes_endian(value: i64, size: usize, swapped: bool) -> Vec { + if swapped { + #[cfg(target_endian = "little")] + { + match size { + 1 => (value as i8).to_ne_bytes().to_vec(), + 2 => (value as i16).to_be_bytes().to_vec(), + 4 => (value as i32).to_be_bytes().to_vec(), + 8 => value.to_be_bytes().to_vec(), + _ => vec![0u8; size], + } + } + #[cfg(target_endian = "big")] + { + match size { + 1 => (value as i8).to_ne_bytes().to_vec(), + 2 => (value as i16).to_le_bytes().to_vec(), + 4 => (value as i32).to_le_bytes().to_vec(), + 8 => value.to_le_bytes().to_vec(), + _ => vec![0u8; size], + } + } + } else { + int_to_sized_bytes(value, size) + } +} + +pub fn uint_to_sized_bytes_endian(value: u64, size: usize, swapped: bool) -> Vec { + if swapped { + #[cfg(target_endian = "little")] + { + match size { + 1 => (value as u8).to_ne_bytes().to_vec(), + 2 => (value as u16).to_be_bytes().to_vec(), + 4 => (value as u32).to_be_bytes().to_vec(), + 8 => value.to_be_bytes().to_vec(), + _ => vec![0u8; size], + } + } + #[cfg(target_endian = "big")] + { + match size { + 1 => (value as u8).to_ne_bytes().to_vec(), + 2 => (value as u16).to_le_bytes().to_vec(), + 4 => (value as u32).to_le_bytes().to_vec(), + 8 => value.to_le_bytes().to_vec(), + _ => vec![0u8; size], + } + } + } else { + uint_to_sized_bytes(value, size) + } +} + +pub fn float_to_sized_bytes(value: f64, size: usize) -> Option> { + match size { + 4 => Some((value as f32).to_ne_bytes().to_vec()), + 8 => Some(value.to_ne_bytes().to_vec()), + _ => None, + } +} + +pub fn float_to_sized_bytes_endian(value: f64, size: usize, swapped: bool) -> Option> { + if swapped { + #[cfg(target_endian = "little")] + { + match size { + 4 => Some((value as f32).to_be_bytes().to_vec()), + 8 => Some(value.to_be_bytes().to_vec()), + _ => None, + } + } + #[cfg(target_endian = "big")] + { + match size { + 4 => Some((value as f32).to_le_bytes().to_vec()), + 8 => Some(value.to_le_bytes().to_vec()), + _ => None, + } + } + } else { + float_to_sized_bytes(value, size) + } +} + +pub fn pointer_to_sized_bytes(value: usize, size: usize) -> Vec { + let mut result = vec![0u8; size]; + let bytes = value.to_ne_bytes(); + let len = core::cmp::min(bytes.len(), size); + result[..len].copy_from_slice(&bytes[..len]); + result +} + +pub fn wchar_code_to_bytes_endian(ch: u32, swapped: bool) -> Vec { + let mut buffer = vec![0u8; WCHAR_SIZE]; + wchar_to_bytes(ch, &mut buffer); + if swapped { + buffer.reverse(); + } + buffer +} + +pub fn simple_storage_value_to_bytes_endian( + type_code: &str, + value: SimpleStorageValue, + swapped: bool, +) -> Vec { + match type_code { + "c" => match value { + SimpleStorageValue::Byte(value) => vec![value], + _ => vec![0], + }, + "u" => match value { + SimpleStorageValue::Wchar(value) => wchar_code_to_bytes_endian(value, swapped), + _ => vec![0; WCHAR_SIZE], + }, + "b" => match value { + SimpleStorageValue::Signed(value) => vec![(value as i8) as u8], + _ => vec![0], + }, + "B" => match value { + SimpleStorageValue::Signed(value) => vec![value as u8], + _ => vec![0], + }, + "h" => match value { + SimpleStorageValue::Signed(value) => { + int_to_sized_bytes_endian((value as i16).into(), 2, swapped) + } + _ => vec![0; 2], + }, + "H" => match value { + SimpleStorageValue::Signed(value) => { + uint_to_sized_bytes_endian((value as u16).into(), 2, swapped) + } + _ => vec![0; 2], + }, + "i" => match value { + SimpleStorageValue::Signed(value) => { + int_to_sized_bytes_endian((value as i32).into(), 4, swapped) + } + _ => vec![0; 4], + }, + "I" => match value { + SimpleStorageValue::Signed(value) => { + uint_to_sized_bytes_endian((value as u32).into(), 4, swapped) + } + _ => vec![0; 4], + }, + "l" => match value { + SimpleStorageValue::Signed(value) => c_long_bytes_endian(value, swapped), + _ => vec![0; simple_type_size("l").expect("invalid ctypes simple type")], + }, + "L" => match value { + SimpleStorageValue::Signed(value) => c_ulong_bytes_endian(value, swapped), + _ => vec![0; simple_type_size("L").expect("invalid ctypes simple type")], + }, + "q" => match value { + SimpleStorageValue::Signed(value) => { + int_to_sized_bytes_endian(value as i64, 8, swapped) + } + _ => vec![0; 8], + }, + "Q" => match value { + SimpleStorageValue::Signed(value) => { + uint_to_sized_bytes_endian(value as u64, 8, swapped) + } + _ => vec![0; 8], + }, + "f" => match value { + SimpleStorageValue::Float(value) => { + float_to_sized_bytes_endian(value, 4, swapped).expect("f32 size is fixed") + } + _ => vec![0; 4], + }, + "d" => match value { + SimpleStorageValue::Float(value) => { + float_to_sized_bytes_endian(value, 8, swapped).expect("f64 size is fixed") + } + _ => vec![0; 8], + }, + "g" => { + let value = match value { + SimpleStorageValue::Float(value) => value, + _ => 0.0, + }; + let mut result = + float_to_sized_bytes_endian(value, 8, swapped).expect("f64 size is fixed"); + result.resize( + simple_type_size("g").expect("invalid ctypes simple type"), + 0, + ); + result + } + "?" => match value { + SimpleStorageValue::Bool(value) => vec![if value { 1 } else { 0 }], + _ => vec![0], + }, + "v" => match value { + SimpleStorageValue::Bool(value) => { + let value: i16 = if value { -1 } else { 0 }; + int_to_sized_bytes_endian(value.into(), 2, swapped) + } + _ => vec![0; 2], + }, + "P" | "z" | "Z" => match value { + SimpleStorageValue::Pointer(value) => { + uint_to_sized_bytes_endian(value as u64, pointer_size(), swapped) + } + _ => null_pointer_bytes(), + }, + "O" => match value { + SimpleStorageValue::ObjectId(value) => { + uint_to_sized_bytes_endian(value as u64, pointer_size(), swapped) + } + _ => null_pointer_bytes(), + }, + _ => vec![0], + } +} + +pub fn utf16z_bytes(s: &Wtf8) -> Vec { + vec_into_bytes::(s.encode_wide().chain(core::iter::once(0)).collect()) +} + +pub fn null_terminated_bytes(bytes: &[u8]) -> Vec { + let mut buffer = bytes.to_vec(); + buffer.push(0); + buffer +} + +pub fn decode_type_code(type_code: &str, bytes: &[u8]) -> DecodedValue { + match type_code { + "c" => DecodedValue::Bytes(bytes.to_vec()), + "b" => DecodedValue::Signed(if !bytes.is_empty() { + bytes[0] as i8 as i64 + } else { + 0 + }), + "B" => DecodedValue::Unsigned(if !bytes.is_empty() { + bytes[0].into() + } else { + 0 + }), + "h" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Signed(if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into() + } else { + 0 + }) + } + "H" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Unsigned(if bytes.len() >= SIZE { + c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into() + } else { + 0 + }) + } + "i" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Signed(if bytes.len() >= SIZE { + c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into() + } else { + 0 + }) + } + "I" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Unsigned(if bytes.len() >= SIZE { + c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")).into() + } else { + 0 + }) + } + "l" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Signed(if bytes.len() >= SIZE { + #[allow( + clippy::unnecessary_cast, + clippy::useless_conversion, + reason = "c_long width is platform-dependent" + )] + let val: i64 = + c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as i64; + val + } else { + 0 + }) + } + "L" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Unsigned(if bytes.len() >= SIZE { + #[allow( + clippy::unnecessary_cast, + clippy::useless_conversion, + reason = "c_ulong width is platform-dependent" + )] + let val: u64 = + c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as u64; + val + } else { + 0 + }) + } + "q" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Signed(if bytes.len() >= SIZE { + c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }) + } + "Q" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Unsigned(if bytes.len() >= SIZE { + c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }) + } + "f" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Float(if bytes.len() >= SIZE { + c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) as f64 + } else { + 0.0 + }) + } + "d" | "g" => { + const SIZE: usize = core::mem::size_of::(); + DecodedValue::Float(if bytes.len() >= SIZE { + c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }) + } + "?" => DecodedValue::Bool(!bytes.is_empty() && bytes[0] != 0), + "v" => { + const SIZE: usize = core::mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + DecodedValue::Bool(val != 0) + } + "z" => unsafe { + match read_c_string_from_address(read_pointer_from_buffer(bytes)) { + Some(bytes) => DecodedValue::Bytes(bytes), + None => DecodedValue::None, + } + }, + "Z" => unsafe { + match read_wide_string_from_address(read_pointer_from_buffer(bytes)) { + Some(s) => DecodedValue::String(s.to_string()), + None => DecodedValue::None, + } + }, + "P" => DecodedValue::Pointer(read_pointer_from_buffer(bytes)), + "u" => { + let val = if bytes.len() >= core::mem::size_of::() { + let wc = if core::mem::size_of::() == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }; + char::from_u32(wc).unwrap_or('\0') + } else { + '\0' + }; + DecodedValue::String(val.to_string()) + } + _ => DecodedValue::None, + } +} + +/// # Safety +/// +/// `ptr` must point to a valid callback argument storage for the given ctypes +/// `type_code`. +pub unsafe fn callback_arg_value(type_code: Option<&str>, ptr: *const c_void) -> DecodedValue { + match type_code { + Some("b") => DecodedValue::Signed(unsafe { *(ptr as *const i8) as i64 }), + Some("B") => DecodedValue::Unsigned(unsafe { *(ptr as *const u8) as u64 }), + Some("c") => DecodedValue::Bytes(vec![unsafe { *(ptr as *const u8) }]), + Some("h") => DecodedValue::Signed(unsafe { *(ptr as *const i16) as i64 }), + Some("H") => DecodedValue::Unsigned(unsafe { *(ptr as *const u16) as u64 }), + Some("i") => DecodedValue::Signed(unsafe { *(ptr as *const i32) as i64 }), + Some("I") => DecodedValue::Unsigned(unsafe { *(ptr as *const u32) as u64 }), + Some("l") => DecodedValue::Signed({ + #[allow( + clippy::unnecessary_cast, + clippy::useless_conversion, + reason = "c_long width is platform-dependent" + )] + let val: i64 = unsafe { *(ptr as *const c_long) as i64 }; + val + }), + Some("L") => DecodedValue::Unsigned({ + #[allow( + clippy::unnecessary_cast, + clippy::useless_conversion, + reason = "c_ulong width is platform-dependent" + )] + let val: u64 = unsafe { *(ptr as *const c_ulong) as u64 }; + val + }), + Some("q") => DecodedValue::Signed(unsafe { *(ptr as *const c_longlong) }), + Some("Q") => DecodedValue::Unsigned(unsafe { *(ptr as *const c_ulonglong) }), + Some("f") => DecodedValue::Float(unsafe { *(ptr as *const f32) as f64 }), + Some("d") => DecodedValue::Float(unsafe { *(ptr as *const f64) }), + Some("z") => { + let cstr_ptr = unsafe { *(ptr as *const *const c_char) }; + if cstr_ptr.is_null() { + DecodedValue::None + } else { + DecodedValue::Bytes(unsafe { read_c_string_bytes(cstr_ptr) }) + } + } + Some("Z") => { + let wstr_ptr = unsafe { *(ptr as *const *const WChar) }; + if wstr_ptr.is_null() { + DecodedValue::None + } else { + DecodedValue::String(unsafe { read_wide_string(wstr_ptr) }.to_string()) + } + } + Some("P") => DecodedValue::Pointer(unsafe { *(ptr as *const usize) }), + Some("?") => DecodedValue::Bool(unsafe { *(ptr as *const u8) != 0 }), + _ => DecodedValue::None, + } +} + +/// # Safety +/// +/// `args` must point to a libffi callback argument array with a valid entry at +/// `index`, and that entry must be valid for the given ctypes `type_code`. +pub unsafe fn callback_arg_value_at( + type_code: Option<&str>, + args: *const *const c_void, + index: usize, +) -> DecodedValue { + let ptr = unsafe { *args.add(index) }; + unsafe { callback_arg_value(type_code, ptr) } +} + +/// # Safety +/// +/// `result` must point to valid callback result storage for the given ctypes +/// `type_code`. +pub unsafe fn write_callback_result( + type_code: Option<&str>, + result: *mut c_void, + value: CallbackResultValue, +) { + match (type_code, value) { + (Some("b"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i8) = v as i8 }, + (Some("B" | "c"), CallbackResultValue::Unsigned(v)) => unsafe { + *(result as *mut u8) = v as u8 + }, + (Some("h"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i16) = v as i16 }, + (Some("H"), CallbackResultValue::Unsigned(v)) => unsafe { + *(result as *mut u16) = v as u16 + }, + (Some("i"), CallbackResultValue::Signed(v)) => unsafe { + *(result as *mut CallbackIntResult) = v as i32 as CallbackIntResult + }, + (Some("I"), CallbackResultValue::Unsigned(v)) => unsafe { + *(result as *mut u32) = v as u32 + }, + (Some("l"), CallbackResultValue::Signed(v)) => unsafe { + *(result as *mut c_long) = v as c_long + }, + (Some("L"), CallbackResultValue::Unsigned(v)) => unsafe { + *(result as *mut c_ulong) = v as c_ulong + }, + (Some("q"), CallbackResultValue::Signed(v)) => unsafe { *(result as *mut i64) = v }, + (Some("Q"), CallbackResultValue::Unsigned(v)) => unsafe { *(result as *mut u64) = v }, + (Some("f"), CallbackResultValue::Float(v)) => unsafe { *(result as *mut f32) = v as f32 }, + (Some("d"), CallbackResultValue::Float(v)) => unsafe { *(result as *mut f64) = v }, + (Some("P" | "z" | "Z"), CallbackResultValue::Pointer(v)) => unsafe { + *(result as *mut usize) = v + }, + (Some("?"), CallbackResultValue::Bool(v)) => unsafe { *(result as *mut u8) = u8::from(v) }, + _ => {} + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_value_from_type_code(type_code: &str, buffer: &[u8]) -> FfiValue { + match type_code { + "c" | "b" => FfiValue::I8(buffer.first().map_or(0, |&b| b as i8)), + "B" => FfiValue::U8(buffer.first().copied().unwrap_or(0)), + "h" => FfiValue::I16(buffer.first_chunk().copied().map_or(0, i16::from_ne_bytes)), + "H" => FfiValue::U16(buffer.first_chunk().copied().map_or(0, u16::from_ne_bytes)), + "i" => FfiValue::I32(buffer.first_chunk().copied().map_or(0, i32::from_ne_bytes)), + "I" => FfiValue::U32(buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes)), + "l" | "q" => FfiValue::I64(if let Some(&bytes) = buffer.first_chunk::<8>() { + i64::from_ne_bytes(bytes) + } else if let Some(&bytes) = buffer.first_chunk::<4>() { + i32::from_ne_bytes(bytes).into() + } else { + 0 + }), + "L" | "Q" => FfiValue::U64(if let Some(&bytes) = buffer.first_chunk::<8>() { + u64::from_ne_bytes(bytes) + } else if let Some(&bytes) = buffer.first_chunk::<4>() { + u32::from_ne_bytes(bytes).into() + } else { + 0 + }), + "f" => FfiValue::F32( + buffer + .first_chunk::<4>() + .copied() + .map_or(0.0, f32::from_ne_bytes), + ), + "d" | "g" => FfiValue::F64( + buffer + .first_chunk::<8>() + .copied() + .map_or(0.0, f64::from_ne_bytes), + ), + "z" | "Z" | "P" | "O" => FfiValue::Pointer(read_pointer_from_buffer(buffer)), + "?" => FfiValue::U8(if buffer.first().is_some_and(|&b| b != 0) { + 1 + } else { + 0 + }), + "u" => FfiValue::U32(buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes)), + _ => FfiValue::Pointer(0), + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_value_from_type(buffer: &[u8], ty: Type) -> Option { + if core::ptr::eq(ty.as_raw_ptr(), Type::u8().as_raw_ptr()) { + Some(FfiValue::U8(*buffer.first()?)) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::i8().as_raw_ptr()) { + Some(FfiValue::I8(*buffer.first()? as i8)) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::u16().as_raw_ptr()) { + Some(FfiValue::U16(u16::from_ne_bytes( + *buffer.first_chunk::<2>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::i16().as_raw_ptr()) { + Some(FfiValue::I16(i16::from_ne_bytes( + *buffer.first_chunk::<2>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::u32().as_raw_ptr()) { + Some(FfiValue::U32(u32::from_ne_bytes( + *buffer.first_chunk::<4>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::i32().as_raw_ptr()) { + Some(FfiValue::I32(i32::from_ne_bytes( + *buffer.first_chunk::<4>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::u64().as_raw_ptr()) { + Some(FfiValue::U64(u64::from_ne_bytes( + *buffer.first_chunk::<8>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::i64().as_raw_ptr()) { + Some(FfiValue::I64(i64::from_ne_bytes( + *buffer.first_chunk::<8>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::f32().as_raw_ptr()) { + Some(FfiValue::F32(f32::from_ne_bytes( + *buffer.first_chunk::<4>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::f64().as_raw_ptr()) { + Some(FfiValue::F64(f64::from_ne_bytes( + *buffer.first_chunk::<8>()?, + ))) + } else if core::ptr::eq(ty.as_raw_ptr(), Type::pointer().as_raw_ptr()) { + Some(FfiValue::Pointer(read_pointer_from_buffer(buffer))) + } else { + None + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_type_from_code(ty: &str) -> Option { + match ty { + "c" => Some(Type::u8()), + "u" => Some(if core::mem::size_of::() == 2 { + Type::u16() + } else { + Type::u32() + }), + "b" => Some(Type::i8()), + "B" | "?" => Some(Type::u8()), + "h" | "v" => Some(Type::i16()), + "H" => Some(Type::u16()), + "i" => Some(Type::i32()), + "I" => Some(Type::u32()), + "l" => Some(if core::mem::size_of::() == 8 { + Type::i64() + } else { + Type::i32() + }), + "L" => Some(if core::mem::size_of::() == 8 { + Type::u64() + } else { + Type::u32() + }), + "q" => Some(Type::i64()), + "Q" => Some(Type::u64()), + "f" => Some(Type::f32()), + "d" | "g" => Some(Type::f64()), + "z" | "Z" | "P" | "X" | "O" => Some(Type::pointer()), + "void" => Some(Type::void()), + _ => None, + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_type_from_tag(tag: u8) -> Type { + match tag { + b'c' | b'b' => Type::i8(), + b'B' | b'?' => Type::u8(), + b'h' | b'v' => Type::i16(), + b'H' => Type::u16(), + b'i' => Type::i32(), + b'I' => Type::u32(), + b'l' => { + if core::mem::size_of::() == 8 { + Type::i64() + } else { + Type::i32() + } + } + b'L' => { + if core::mem::size_of::() == 8 { + Type::u64() + } else { + Type::u32() + } + } + b'q' => Type::i64(), + b'Q' => Type::u64(), + b'f' => Type::f32(), + b'd' | b'g' => Type::f64(), + b'u' => { + if core::mem::size_of::() == 2 { + Type::u16() + } else { + Type::u32() + } + } + _ => Type::pointer(), + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_type_from_format(fmt: &str) -> Type { + match fmt.trim_start_matches(['<', '>', '!', '@', '=']) { + "b" => Type::i8(), + "B" => Type::u8(), + "h" => Type::i16(), + "H" => Type::u16(), + "i" | "l" => Type::i32(), + "I" | "L" => Type::u32(), + "q" => Type::i64(), + "Q" => Type::u64(), + "f" => Type::f32(), + "d" => Type::f64(), + "P" | "z" | "Z" | "O" => Type::pointer(), + _ => Type::u8(), + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_repeat_type(elem_type: Type, len: usize) -> Type { + Type::structure(core::iter::repeat_n(elem_type, len)) +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_byte_struct(size: usize) -> Type { + ffi_repeat_type(Type::u8(), size) +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_pointer_type() -> Type { + Type::pointer() +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_i32_type() -> Type { + Type::i32() +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_f64_type() -> Type { + Type::f64() +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_void_type() -> Type { + Type::void() +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_type_for_return_size(size: usize) -> Type { + if size <= 4 { + Type::i32() + } else if size <= 8 { + Type::i64() + } else { + Type::pointer() + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CTypeParamKind { + Structure, + Union, + Array, + Pointer, + Simple, +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_type_for_layout( + kind: CTypeParamKind, + ffi_field_types: &[Type], + size: usize, + length: usize, + format: Option<&str>, +) -> Type { + const MAX_FFI_STRUCT_SIZE: usize = 1024 * 1024; + + match kind { + CTypeParamKind::Structure | CTypeParamKind::Union => { + if !ffi_field_types.is_empty() { + Type::structure(ffi_field_types.iter().cloned()) + } else if size <= MAX_FFI_STRUCT_SIZE { + ffi_byte_struct(size) + } else { + ffi_pointer_type() + } + } + CTypeParamKind::Array => { + if size > MAX_FFI_STRUCT_SIZE || length > MAX_FFI_STRUCT_SIZE { + ffi_pointer_type() + } else if let Some(fmt) = format { + ffi_repeat_type(ffi_type_from_format(fmt), length) + } else { + ffi_byte_struct(size) + } + } + CTypeParamKind::Pointer => ffi_pointer_type(), + CTypeParamKind::Simple => { + if let Some(fmt) = format { + ffi_type_from_format(fmt) + } else { + Type::u8() + } + } + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn callproc( + code_ptr: CodePtr, + ffi_arg_types: Vec, + ffi_return_type: Type, + ffi_args: &[Arg<'_>], + restype_is_none: bool, + is_pointer_return: bool, +) -> CallResult { + let cif = Cif::new(ffi_arg_types, ffi_return_type); + if restype_is_none { + unsafe { cif.call::<()>(code_ptr, ffi_args) }; + CallResult::Void + } else if is_pointer_return { + CallResult::Pointer(unsafe { cif.call::(code_ptr, ffi_args) }) + } else { + CallResult::Value(unsafe { cif.call::(code_ptr, ffi_args) }) + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn call_cdecl_i32(code_ptr: usize, arg_types: Vec, arg_values: &[isize]) -> c_int { + let ffi_args: Vec<_> = arg_values.iter().map(Arg::new).collect(); + let cif = Cif::new(arg_types, Type::c_int()); + let code_ptr = CodePtr::from_ptr(code_ptr as *const _); + unsafe { cif.call(code_ptr, &ffi_args) } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn call_cdecl_i32_values(code_ptr: usize, args: &[CdeclArgValue]) -> c_int { + let mut arg_values = Vec::with_capacity(args.len()); + let mut arg_types = Vec::with_capacity(args.len()); + for arg in args { + match *arg { + CdeclArgValue::Pointer(value) => { + arg_values.push(value); + arg_types.push(Type::pointer()); + } + CdeclArgValue::Int(value) => { + arg_values.push(value); + arg_types.push(Type::isize()); + } + } + } + call_cdecl_i32(code_ptr, arg_types, &arg_values) +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_arg(value: FfiArgRef<'_>) -> Arg<'_> { + match value { + FfiArgRef::U8(v) => Arg::new(v), + FfiArgRef::I8(v) => Arg::new(v), + FfiArgRef::U16(v) => Arg::new(v), + FfiArgRef::I16(v) => Arg::new(v), + FfiArgRef::U32(v) => Arg::new(v), + FfiArgRef::I32(v) => Arg::new(v), + FfiArgRef::U64(v) => Arg::new(v), + FfiArgRef::I64(v) => Arg::new(v), + FfiArgRef::F32(v) => Arg::new(v), + FfiArgRef::F64(v) => Arg::new(v), + FfiArgRef::Pointer(v) => Arg::new(v), + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn ffi_arg_from_value(value: &FfiValue) -> Arg<'_> { + match value { + FfiValue::U8(v) => ffi_arg(FfiArgRef::U8(v)), + FfiValue::I8(v) => ffi_arg(FfiArgRef::I8(v)), + FfiValue::U16(v) => ffi_arg(FfiArgRef::U16(v)), + FfiValue::I16(v) => ffi_arg(FfiArgRef::I16(v)), + FfiValue::U32(v) => ffi_arg(FfiArgRef::U32(v)), + FfiValue::I32(v) => ffi_arg(FfiArgRef::I32(v)), + FfiValue::U64(v) => ffi_arg(FfiArgRef::U64(v)), + FfiValue::I64(v) => ffi_arg(FfiArgRef::I64(v)), + FfiValue::F32(v) => ffi_arg(FfiArgRef::F32(v)), + FfiValue::F64(v) => ffi_arg(FfiArgRef::F64(v)), + FfiValue::Pointer(v) => ffi_arg(FfiArgRef::Pointer(v)), + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn code_ptr_from_addr(addr: usize) -> Option { + if addr == 0 { + None + } else { + Some(CodePtr(addr as *mut _)) + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn null_code_ptr() -> CodePtr { + CodePtr(core::ptr::null_mut()) +} + +#[cfg(windows)] +pub enum ComMethodError { + NullComPointer, + NullVtablePointer, + NullFunctionPointer, +} + +#[cfg(windows)] +pub const HRESULT_E_POINTER: i32 = crate::windows::HRESULT_E_POINTER; + +#[cfg(windows)] +pub const HRESULT_S_OK: i32 = crate::windows::HRESULT_S_OK; + +#[cfg(windows)] +pub fn format_error_message(code: Option) -> Option { + crate::windows::format_error_message(code) +} + +#[cfg(windows)] +pub fn resolve_com_vtable_entry(com_ptr: usize, idx: usize) -> Result { + if com_ptr == 0 { + return Err(ComMethodError::NullComPointer); + } + let vtable_ptr = unsafe { *(com_ptr as *const usize) }; + if vtable_ptr == 0 { + return Err(ComMethodError::NullVtablePointer); + } + let fptr = unsafe { + let vtable = vtable_ptr as *const usize; + *vtable.add(idx) + }; + if fptr == 0 { + return Err(ComMethodError::NullFunctionPointer); + } + Ok(CodePtr(fptr as *mut _)) +} + +#[cfg(windows)] +pub fn copy_com_pointer(src_ptr: usize, dst_addr: usize) -> i32 { + if dst_addr == 0 { + return HRESULT_E_POINTER; + } + + if src_ptr != 0 { + unsafe { + let iunknown = src_ptr as *mut *const usize; + let vtable = *iunknown; + if vtable.is_null() { + return HRESULT_E_POINTER; + } + let addref_fn: extern "system" fn(*mut c_void) -> u32 = + core::mem::transmute(*vtable.add(1)); + addref_fn(src_ptr as *mut c_void); + } + } + + unsafe { + *(dst_addr as *mut usize) = src_ptr; + } + + HRESULT_S_OK +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub struct CallbackThunk { + #[allow(dead_code)] + closure: Closure<'static>, + userdata_ptr: *mut U, + code_ptr: CodePtr, +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +impl CallbackThunk { + pub fn new( + ffi_arg_types: Vec, + ffi_res_type: Type, + userdata: Box, + callback: unsafe extern "C" fn(&low::ffi_cif, &mut c_void, *const *const c_void, &U), + ) -> Self { + let cif = Cif::new(ffi_arg_types, ffi_res_type); + let userdata_ptr = Box::into_raw(userdata); + let userdata_ref: &'static U = unsafe { &*userdata_ptr }; + let closure = Closure::new(cif, callback, userdata_ref); + let code_ptr = CodePtr(*closure.code_ptr() as *mut _); + Self { + closure, + userdata_ptr, + code_ptr, + } + } + + pub fn code_ptr(&self) -> CodePtr { + self.code_ptr + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +impl Drop for CallbackThunk { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.userdata_ptr)); + } + } +} + +#[cfg(all( + any( + target_os = "linux", + target_os = "macos", + target_os = "windows", + target_os = "android" + ), + not(any(target_env = "musl", target_env = "sgx")) +))] +pub fn call_result_bytes(raw_result: &CallResult) -> Option<(Vec, usize)> { + match raw_result { + CallResult::Void => None, + CallResult::Pointer(ptr) => { + let bytes = ptr.to_ne_bytes(); + Some((bytes.to_vec(), core::mem::size_of::())) + } + CallResult::Value(val) => { + let bytes = val.to_ne_bytes(); + Some((bytes.to_vec(), core::mem::size_of_val(val))) + } + } +} + +/// # Safety +/// +/// `ptr` must point to `len` readable bytes. +pub unsafe fn bytes_at(ptr: *const u8, len: usize) -> Vec { + unsafe { core::slice::from_raw_parts(ptr, len) }.to_vec() +} + +/// # Safety +/// +/// The caller must ensure `ptr..ptr+size` remains valid for the lifetime of the returned slice. +pub unsafe fn borrow_memory(ptr: *const u8, size: usize) -> &'static [u8] { + unsafe { core::slice::from_raw_parts(ptr, size) } +} + +/// # Safety +/// +/// The caller must ensure `ptr..ptr+size` remains valid and uniquely borrowed for the lifetime of the returned slice. +pub unsafe fn borrow_memory_mut(ptr: *mut u8, size: usize) -> &'static mut [u8] { + unsafe { core::slice::from_raw_parts_mut(ptr, size) } +} + +/// # Safety +/// +/// `slice` must point to memory that is valid and writable for its full length. +#[allow( + clippy::mut_from_ref, + reason = "ctypes borrowed buffers may wrap writable memory behind a shared slice" +)] +pub unsafe fn borrowed_slice_as_mut(slice: &[u8]) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(slice.as_ptr() as *mut u8, slice.len()) } +} + +pub fn wide_chars_to_wtf8(wchars: &[WChar]) -> Wtf8Buf { + #[cfg(windows)] + { + let wide: Vec = wchars.to_vec(); + Wtf8Buf::from_wide(&wide) + } + #[cfg(not(windows))] + { + #[allow( + clippy::useless_conversion, + reason = "wchar_t is i32 on some platforms and u32 on others" + )] + let s: String = wchars + .iter() + .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) + .collect(); + Wtf8Buf::from_string(s) + } +} + +/// # Safety +/// +/// `ptr` must be a valid NUL-terminated wide C string. +pub unsafe fn read_wide_string(ptr: *const WChar) -> Wtf8Buf { + let len = unsafe { wcslen(ptr) }; + let wchars = unsafe { core::slice::from_raw_parts(ptr, len) }; + wide_chars_to_wtf8(wchars) +} + +/// # Safety +/// +/// `addr` must either be zero or a valid NUL-terminated C string pointer. +pub unsafe fn read_c_string_from_address(addr: usize) -> Option> { + if addr == 0 { + None + } else { + Some(unsafe { read_c_string_bytes(addr as *const c_char) }) + } +} + +/// # Safety +/// +/// `addr` must either be zero or a valid NUL-terminated wide C string pointer. +pub unsafe fn read_wide_string_from_address(addr: usize) -> Option { + if addr == 0 { + None + } else { + Some(unsafe { read_wide_string(addr as *const WChar) }) + } +} + +/// # Safety +/// +/// `ptr` must point to `len` readable wide characters. +pub unsafe fn read_wide_string_with_len(ptr: *const WChar, len: usize) -> Wtf8Buf { + let wchars = unsafe { core::slice::from_raw_parts(ptr, len) }; + wide_chars_to_wtf8(wchars) +} + +pub fn string_at(ptr: usize, size: isize) -> Result, StringAtError> { + if ptr == 0 { + return Err(StringAtError::NullPointer); + } + if size < 0 { + // SAFETY: caller passed a non-null C string pointer; same precondition as previous VM path. + return Ok(unsafe { read_c_string_bytes(ptr as _) }); + } + let len = { + let size_usize = size as usize; + if size_usize > isize::MAX as usize / 2 { + return Err(StringAtError::TooLong); + } + size_usize + }; + // SAFETY: caller requested exactly `len` readable bytes from non-null pointer. + Ok(unsafe { bytes_at(ptr as *const u8, len) }) +} + +pub fn wstring_at(ptr: usize, size: isize) -> Result { + if ptr == 0 { + return Err(StringAtError::NullPointer); + } + let w_ptr = ptr as *const WChar; + if size < 0 { + // SAFETY: caller passed a non-null NUL-terminated wide string pointer. + return Ok(unsafe { read_wide_string(w_ptr) }); + } + let len = { + let size_usize = size as usize; + if size_usize > isize::MAX as usize / core::mem::size_of::() { + return Err(StringAtError::TooLong); + } + size_usize + }; + // SAFETY: caller requested exactly `len` readable wide characters from non-null pointer. + Ok(unsafe { read_wide_string_with_len(w_ptr, len) }) +} + +/// # Safety +/// +/// `start` must be valid to read `len` elements following `step`. +pub unsafe fn read_bytes_strided(start: *const u8, len: usize, step: isize) -> Vec { + if step == 1 { + return unsafe { bytes_at(start, len) }; + } + let mut result = Vec::with_capacity(len); + let mut cur = start; + for _ in 0..len { + result.push(unsafe { *cur }); + cur = unsafe { cur.offset(step) }; + } + result +} + +pub fn pointer_item_address(ptr_value: usize, index: isize, element_size: usize) -> usize { + let offset = index * element_size as isize; + (ptr_value as isize + offset) as usize +} + +pub fn offset_address(base: usize, offset: isize) -> usize { + (base as isize + offset) as usize +} + +/// # Safety +/// +/// `ptr_value + start * element_size` must be valid to read `len` bytes following +/// `step * element_size`. +pub unsafe fn read_pointer_char_slice( + ptr_value: usize, + start: isize, + len: usize, + step: isize, + element_size: usize, +) -> Vec { + let start_addr = pointer_item_address(ptr_value, start, element_size) as *const u8; + if step == 1 { + unsafe { bytes_at(start_addr, len) } + } else { + unsafe { read_bytes_strided(start_addr, len, step * element_size as isize) } + } +} + +/// # Safety +/// +/// `start` must be valid to read `len` wide characters following `step`. +pub unsafe fn read_wide_string_strided(start: *const WChar, len: usize, step: isize) -> Wtf8Buf { + if step == 1 { + return unsafe { read_wide_string_with_len(start, len) }; + } + let mut wchars = Vec::with_capacity(len); + let mut cur = start; + for _ in 0..len { + wchars.push(unsafe { *cur }); + cur = unsafe { cur.offset(step) }; + } + wide_chars_to_wtf8(&wchars) +} + +/// # Safety +/// +/// `ptr_value + start * sizeof(wchar_t)` must be valid to read `len` wide +/// characters following `step`. +pub unsafe fn read_pointer_wchar_slice( + ptr_value: usize, + start: isize, + len: usize, + step: isize, +) -> Wtf8Buf { + let wchar_size = core::mem::size_of::(); + let start_addr = (ptr_value as isize + start * wchar_size as isize) as *const WChar; + unsafe { read_wide_string_strided(start_addr, len, step) } +} + +/// # Safety +/// +/// `addr` must be readable for `size` bytes and match the alignment/validity +/// requirements implied by `type_code`. +pub unsafe fn read_value_at_address( + addr: usize, + size: usize, + type_code: Option<&str>, +) -> AddressValue { + let ptr = addr as *const u8; + match type_code { + Some("c") => AddressValue::ByteString(unsafe { *ptr }), + Some("b") => AddressValue::Integer(IntegerValue::Signed(unsafe { *ptr as i8 as i64 })), + Some("B") => AddressValue::Integer(IntegerValue::Unsigned(unsafe { (*ptr).into() })), + Some("h") => AddressValue::Integer(IntegerValue::Signed( + unsafe { core::ptr::read_unaligned(ptr as *const i16) }.into(), + )), + Some("H") => AddressValue::Integer(IntegerValue::Unsigned( + unsafe { core::ptr::read_unaligned(ptr as *const u16) }.into(), + )), + Some("i") => AddressValue::Integer(IntegerValue::Signed( + unsafe { core::ptr::read_unaligned(ptr as *const i32) }.into(), + )), + Some("I") => AddressValue::Integer(IntegerValue::Unsigned( + unsafe { core::ptr::read_unaligned(ptr as *const u32) }.into(), + )), + Some("l") => AddressValue::Integer(IntegerValue::Signed(unsafe { + core::ptr::read_unaligned(ptr as *const c_long) + } as i64)), + Some("L") => AddressValue::Integer(IntegerValue::Unsigned(unsafe { + core::ptr::read_unaligned(ptr as *const c_ulong) + } as u64)), + Some("q") => AddressValue::Integer(IntegerValue::Signed(unsafe { + core::ptr::read_unaligned(ptr as *const i64) + })), + Some("Q") => AddressValue::Integer(IntegerValue::Unsigned(unsafe { + core::ptr::read_unaligned(ptr as *const u64) + })), + Some("f") => { + AddressValue::Float(unsafe { core::ptr::read_unaligned(ptr as *const f32) as f64 }) + } + Some("d" | "g") => { + AddressValue::Float(unsafe { core::ptr::read_unaligned(ptr as *const f64) }) + } + Some("P" | "z" | "Z") => { + AddressValue::Pointer(unsafe { core::ptr::read_unaligned(ptr as *const usize) }) + } + _ => AddressValue::Bytes(unsafe { bytes_at(ptr, size) }), + } +} + +/// # Safety +/// +/// `addr` must be valid to write one `u8`. +pub unsafe fn write_u8_at_address(addr: usize, value: u8) { + unsafe { *(addr as *mut u8) = value }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `i16`. +pub unsafe fn write_i16_at_address(addr: usize, value: i16) { + unsafe { core::ptr::write_unaligned(addr as *mut i16, value) }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `i32`. +pub unsafe fn write_i32_at_address(addr: usize, value: i32) { + unsafe { core::ptr::write_unaligned(addr as *mut i32, value) }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `i64`. +pub unsafe fn write_i64_at_address(addr: usize, value: i64) { + unsafe { core::ptr::write_unaligned(addr as *mut i64, value) }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `usize`. +pub unsafe fn write_pointer_at_address(addr: usize, value: usize) { + unsafe { core::ptr::write_unaligned(addr as *mut usize, value) }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `f32`. +pub unsafe fn write_f32_at_address(addr: usize, value: f32) { + unsafe { core::ptr::write_unaligned(addr as *mut f32, value) }; +} + +/// # Safety +/// +/// `addr` must be valid to write one `f64`. +pub unsafe fn write_f64_at_address(addr: usize, value: f64) { + unsafe { core::ptr::write_unaligned(addr as *mut f64, value) }; +} + +/// # Safety +/// +/// `addr` must be valid for writing the storage required by `value`. +pub unsafe fn write_value_to_address(addr: usize, size: usize, value: AddressWriteValue<'_>) { + match value { + AddressWriteValue::Pointer(value) => unsafe { write_pointer_at_address(addr, value) }, + AddressWriteValue::U8(value) => unsafe { write_u8_at_address(addr, value) }, + AddressWriteValue::I16(value) => unsafe { write_i16_at_address(addr, value) }, + AddressWriteValue::I32(value) => unsafe { write_i32_at_address(addr, value) }, + AddressWriteValue::I64(value) => unsafe { write_i64_at_address(addr, value) }, + AddressWriteValue::Float(value) => match size { + 4 => unsafe { write_f32_at_address(addr, value as f32) }, + 8 => unsafe { write_f64_at_address(addr, value) }, + _ => {} + }, + AddressWriteValue::Bytes(bytes) => unsafe { copy_bytes_to_address(addr, bytes, size) }, + } +} + +/// # Safety +/// +/// `addr` must be valid to write `min(bytes.len(), size)` bytes. +pub unsafe fn copy_bytes_to_address(addr: usize, bytes: &[u8], size: usize) { + let copy_len = bytes.len().min(size); + unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), addr as *mut u8, copy_len) }; +} + +pub fn write_simple_storage_buffer(buffer: &mut Cow<'_, [u8]>, bytes: &[u8]) { + match buffer { + Cow::Borrowed(slice) => { + // SAFETY: ctypes borrowed buffers are created only from writable Python buffers. + unsafe { + copy_bytes_to_address(slice.as_ptr() as usize, bytes, slice.len()); + } + } + Cow::Owned(vec) => { + vec.copy_from_slice(bytes); + } + } +} + +pub fn write_cow_bytes_at_offset(buffer: &mut Cow<'_, [u8]>, offset: usize, bytes: &[u8]) { + if offset + bytes.len() > buffer.len() { + return; + } + + match buffer { + Cow::Borrowed(slice) => { + // SAFETY: callers only construct borrowed ctypes buffers for writable memory. + unsafe { + copy_bytes_to_address(slice.as_ptr() as usize + offset, bytes, bytes.len()); + } + } + Cow::Owned(vec) => { + vec[offset..offset + bytes.len()].copy_from_slice(bytes); + } + } +} + +pub fn resize_owned_bytes(old_data: &[u8], new_size: usize) -> Vec { + let mut new_data = vec![0u8; new_size]; + let copy_len = old_data.len().min(new_size); + new_data[..copy_len].copy_from_slice(&old_data[..copy_len]); + new_data +} + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub fn memmove_addr() -> usize { + libc::memmove as *const () as usize +} + +#[cfg(not(any(unix, windows, target_os = "wasi")))] +pub fn memmove_addr() -> usize { + 0 +} + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub fn memset_addr() -> usize { + libc::memset as *const () as usize +} + +#[cfg(not(any(unix, windows, target_os = "wasi")))] +pub fn memset_addr() -> usize { + 0 +} + +#[cfg(any(unix, windows))] +pub enum LookupSymbolError { + LibraryNotFound, + LibraryClosed, + Load(String), +} + +#[cfg(any(unix, windows))] +struct SharedLibrary { + lib: Mutex>, +} + +#[cfg(any(unix, windows))] +impl SharedLibrary { + #[cfg(windows)] + fn new(name: impl AsRef) -> Result { + Ok(Self { + lib: Mutex::new(unsafe { Some(Library::new(name.as_ref())?) }), + }) + } + + #[cfg(unix)] + fn new_with_mode(name: impl AsRef, mode: i32) -> Result { + Ok(Self { + lib: Mutex::new(Some(unsafe { + UnixLibrary::open(Some(name.as_ref()), mode)?.into() + })), + }) + } + + #[cfg(unix)] + fn from_raw_handle(handle: *mut c_void) -> Self { + Self { + lib: Mutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })), + } + } + + fn get_pointer(&self) -> usize { + let lib_lock = self.lib.lock(); + if let Some(l) = &*lib_lock { + unsafe { core::mem::transmute_copy::(l) } + } else { + 0 + } + } + + fn lookup_data_symbol_addr(&self, symbol_name: &[u8]) -> Result { + let lib_lock = self.lib.lock(); + let Some(lib) = &*lib_lock else { + return Err(LookupSymbolError::LibraryClosed); + }; + let pointer = unsafe { + lib.get::<*const u8>(symbol_name) + .map_err(|err| LookupSymbolError::Load(err.to_string()))? + }; + Ok(*pointer as usize) + } + + fn lookup_function_symbol_addr(&self, symbol_name: &[u8]) -> Result { + let lib_lock = self.lib.lock(); + let Some(lib) = &*lib_lock else { + return Err(LookupSymbolError::LibraryClosed); + }; + let pointer = unsafe { + lib.get::(symbol_name) + .map_err(|err| LookupSymbolError::Load(err.to_string()))? + }; + Ok(*pointer as *const () as usize) + } +} + +#[cfg(any(unix, windows))] +struct ExternalLibs { + libraries: HashMap, +} + +#[cfg(any(unix, windows))] +impl ExternalLibs { + fn new() -> Self { + Self { + libraries: HashMap::new(), + } + } + + fn get_lib(&self, key: usize) -> Option<&SharedLibrary> { + self.libraries.get(&key) + } + + #[cfg(windows)] + fn open_library( + &mut self, + library_path: impl AsRef, + ) -> Result { + let new_lib = SharedLibrary::new(library_path)?; + let key = new_lib.get_pointer(); + if self.libraries.contains_key(&key) { + drop(new_lib); + return Ok(key); + } + self.libraries.insert(key, new_lib); + Ok(key) + } + + #[cfg(unix)] + fn open_library_with_mode( + &mut self, + library_path: impl AsRef, + mode: i32, + ) -> Result { + let new_lib = SharedLibrary::new_with_mode(library_path, mode)?; + let key = new_lib.get_pointer(); + if self.libraries.contains_key(&key) { + drop(new_lib); + return Ok(key); + } + self.libraries.insert(key, new_lib); + Ok(key) + } + + #[cfg(unix)] + fn insert_raw_library_handle(&mut self, handle: *mut c_void) -> usize { + let key = handle as usize; + self.libraries + .insert(key, SharedLibrary::from_raw_handle(handle)); + key + } + + fn drop_library(&mut self, key: usize) { + self.libraries.remove(&key); + } +} + +#[cfg(any(unix, windows))] +fn libcache() -> &'static RwLock { + static LIBCACHE: OnceLock> = OnceLock::new(); + LIBCACHE.get_or_init(|| RwLock::new(ExternalLibs::new())) +} + +#[cfg(windows)] +pub fn open_library(name: impl AsRef) -> Result { + libcache().write().open_library(name) +} + +#[cfg(unix)] +pub fn open_library_with_mode( + name: impl AsRef, + mode: i32, +) -> Result { + libcache().write().open_library_with_mode(name, mode) +} + +#[cfg(not(unix))] +pub fn open_library_with_mode( + _name: impl AsRef, + _mode: i32, +) -> Result { + Err("dlopen() error".to_string()) +} + +#[cfg(unix)] +pub fn insert_raw_library_handle(handle: *mut c_void) -> usize { + libcache().write().insert_raw_library_handle(handle) +} + +#[cfg(not(unix))] +pub fn insert_raw_library_handle(_handle: *mut c_void) -> usize { + 0 +} + +#[cfg(any(unix, windows))] +pub fn drop_library(handle: usize) { + libcache().write().drop_library(handle); +} + +#[cfg(not(any(unix, windows)))] +pub fn drop_library(_handle: usize) {} + +#[cfg(any(unix, windows))] +pub fn lookup_data_symbol_addr( + handle: usize, + symbol_name: &[u8], +) -> Result { + let cache = libcache().read(); + cache + .get_lib(handle) + .ok_or(LookupSymbolError::LibraryNotFound)? + .lookup_data_symbol_addr(symbol_name) +} + +#[cfg(any(unix, windows))] +pub fn lookup_function_symbol_addr( + handle: usize, + symbol_name: &[u8], +) -> Result { + let cache = libcache().read(); + cache + .get_lib(handle) + .ok_or(LookupSymbolError::LibraryNotFound)? + .lookup_function_symbol_addr(symbol_name) +} + +#[cfg(all(unix, not(target_os = "wasi")))] +pub fn dlopen_self(mode: c_int) -> Result<*mut c_void, String> { + let handle = unsafe { libc::dlopen(core::ptr::null(), mode) }; + if handle.is_null() { + let err = unsafe { libc::dlerror() }; + Err(if err.is_null() { + "dlopen() error".to_string() + } else { + unsafe { CStr::from_ptr(err) } + .to_string_lossy() + .into_owned() + }) + } else { + Ok(handle) + } +} + +#[cfg(not(any(windows, all(unix, not(target_os = "wasi")))))] +pub fn dlopen_self(_mode: c_int) -> Result<*mut c_void, String> { + Err("dlopen() error".to_string()) +} + +#[cfg(all(unix, not(target_os = "wasi")))] +pub fn dlsym_checked(handle: usize, symbol_name: &CStr) -> Result<*mut c_void, String> { + unsafe { + libc::dlerror(); + } + + let ptr = unsafe { libc::dlsym(handle as *mut c_void, symbol_name.as_ptr()) }; + let err = unsafe { libc::dlerror() }; + if !err.is_null() { + return Err(unsafe { CStr::from_ptr(err) } + .to_string_lossy() + .into_owned()); + } + if ptr.is_null() { + return Err(format!( + "symbol '{}' not found", + symbol_name.to_string_lossy() + )); + } + Ok(ptr) +} + +#[cfg(not(any(windows, all(unix, not(target_os = "wasi")))))] +pub fn dlsym_checked(_handle: usize, symbol_name: &CStr) -> Result<*mut c_void, String> { + Err(format!( + "symbol '{}' not found", + symbol_name.to_string_lossy() + )) +} diff --git a/crates/host_env/src/errno.rs b/crates/host_env/src/errno.rs new file mode 100644 index 00000000000..da8edf3e41a --- /dev/null +++ b/crates/host_env/src/errno.rs @@ -0,0 +1,43 @@ +// spell-checker:disable + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod errors { + pub use libc::*; + #[cfg(windows)] + pub use windows_sys::Win32::{ + Foundation::*, + Networking::WinSock::{ + WSABASEERR, WSADESCRIPTION_LEN, WSAEACCES, WSAEADDRINUSE, WSAEADDRNOTAVAIL, + WSAEAFNOSUPPORT, WSAEALREADY, WSAEBADF, WSAECANCELLED, WSAECONNABORTED, + WSAECONNREFUSED, WSAECONNRESET, WSAEDESTADDRREQ, WSAEDISCON, WSAEDQUOT, WSAEFAULT, + WSAEHOSTDOWN, WSAEHOSTUNREACH, WSAEINPROGRESS, WSAEINTR, WSAEINVAL, + WSAEINVALIDPROCTABLE, WSAEINVALIDPROVIDER, WSAEISCONN, WSAELOOP, WSAEMFILE, + WSAEMSGSIZE, WSAENAMETOOLONG, WSAENETDOWN, WSAENETRESET, WSAENETUNREACH, WSAENOBUFS, + WSAENOMORE, WSAENOPROTOOPT, WSAENOTCONN, WSAENOTEMPTY, WSAENOTSOCK, WSAEOPNOTSUPP, + WSAEPFNOSUPPORT, WSAEPROCLIM, WSAEPROTONOSUPPORT, WSAEPROTOTYPE, + WSAEPROVIDERFAILEDINIT, WSAEREFUSED, WSAEREMOTE, WSAESHUTDOWN, WSAESOCKTNOSUPPORT, + WSAESTALE, WSAETIMEDOUT, WSAETOOMANYREFS, WSAEUSERS, WSAEWOULDBLOCK, WSAID_ACCEPTEX, + WSAID_CONNECTEX, WSAID_DISCONNECTEX, WSAID_GETACCEPTEXSOCKADDRS, WSAID_TRANSMITFILE, + WSAID_TRANSMITPACKETS, WSAID_WSAPOLL, WSAID_WSARECVMSG, WSANO_DATA, WSANO_RECOVERY, + WSANOTINITIALISED, WSAPROTOCOL_LEN, WSASERVICE_NOT_FOUND, WSASYS_STATUS_LEN, + WSASYSCALLFAILURE, WSASYSNOTREADY, WSATRY_AGAIN, WSATYPE_NOT_FOUND, WSAVERNOTSUPPORTED, + }, + }; + #[cfg(windows)] + macro_rules! reexport_wsa { + ($($errname:ident),*$(,)?) => { + paste::paste! { + $(pub const $errname: i32 = windows_sys::Win32::Networking::WinSock:: [] as i32;)* + } + } + } + #[cfg(windows)] + reexport_wsa! { + EADDRINUSE, EADDRNOTAVAIL, EAFNOSUPPORT, EALREADY, ECONNABORTED, ECONNREFUSED, ECONNRESET, + EDESTADDRREQ, EDQUOT, EHOSTDOWN, EHOSTUNREACH, EINPROGRESS, EISCONN, ELOOP, EMSGSIZE, + ENETDOWN, ENETRESET, ENETUNREACH, ENOBUFS, ENOPROTOOPT, ENOTCONN, ENOTSOCK, EOPNOTSUPP, + EPFNOSUPPORT, EPROTONOSUPPORT, EPROTOTYPE, EREMOTE, ESHUTDOWN, ESOCKTNOSUPPORT, ESTALE, + ETIMEDOUT, ETOOMANYREFS, EUSERS, EWOULDBLOCK, + // TODO: EBADF should be here once winerrs are translated to errnos but it messes up some things atm + } +} diff --git a/crates/host_env/src/faulthandler.rs b/crates/host_env/src/faulthandler.rs new file mode 100644 index 00000000000..3afbdebb42b --- /dev/null +++ b/crates/host_env/src/faulthandler.rs @@ -0,0 +1,511 @@ +#![allow( + clippy::missing_safety_doc, + reason = "These wrappers expose low-level fault handler hooks with raw OS ABI semantics." +)] +#![allow( + clippy::result_unit_err, + reason = "These helpers preserve the existing fault-handler error surface." +)] +#![allow(static_mut_refs)] + +#[cfg(unix)] +use alloc::vec::Vec; +#[cfg(unix)] +use parking_lot::Mutex; +#[cfg(windows)] +use windows_sys::Win32::System::{ + Diagnostics::Debug::{ + AddVectoredExceptionHandler, EXCEPTION_POINTERS, PVECTORED_EXCEPTION_HANDLER, + RaiseException, RemoveVectoredExceptionHandler, SEM_NOGPFAULTERRORBOX, SetErrorMode, + }, + Threading::GetCurrentThreadId, +}; + +#[cfg(windows)] +pub type ExceptionPointers = EXCEPTION_POINTERS; + +#[cfg(unix)] +struct FatalSignalHandler { + signum: libc::c_int, + enabled: bool, + name: &'static str, + previous: libc::sigaction, +} + +#[cfg(windows)] +struct FatalSignalHandler { + signum: libc::c_int, + enabled: bool, + name: &'static str, + previous: libc::sighandler_t, +} + +#[cfg(unix)] +impl FatalSignalHandler { + const fn new(signum: libc::c_int, name: &'static str) -> Self { + Self { + signum, + enabled: false, + name, + previous: unsafe { core::mem::zeroed() }, + } + } +} + +#[cfg(windows)] +impl FatalSignalHandler { + const fn new(signum: libc::c_int, name: &'static str) -> Self { + Self { + signum, + enabled: false, + name, + previous: 0, + } + } +} + +#[cfg(unix)] +const FATAL_SIGNAL_COUNT: usize = 5; +#[cfg(windows)] +const FATAL_SIGNAL_COUNT: usize = 4; + +#[cfg(unix)] +static mut FATAL_SIGNAL_HANDLERS: [FatalSignalHandler; FATAL_SIGNAL_COUNT] = [ + FatalSignalHandler::new(libc::SIGBUS, "Bus error"), + FatalSignalHandler::new(libc::SIGILL, "Illegal instruction"), + FatalSignalHandler::new(libc::SIGFPE, "Floating-point exception"), + FatalSignalHandler::new(libc::SIGABRT, "Aborted"), + FatalSignalHandler::new(libc::SIGSEGV, "Segmentation fault"), +]; + +#[cfg(windows)] +static mut FATAL_SIGNAL_HANDLERS: [FatalSignalHandler; FATAL_SIGNAL_COUNT] = [ + FatalSignalHandler::new(libc::SIGILL, "Illegal instruction"), + FatalSignalHandler::new(libc::SIGFPE, "Floating-point exception"), + FatalSignalHandler::new(libc::SIGABRT, "Aborted"), + FatalSignalHandler::new(libc::SIGSEGV, "Segmentation fault"), +]; + +#[cfg(unix)] +const USER_SIGNAL_CAPACITY: usize = 64; + +#[cfg(unix)] +#[derive(Clone, Copy)] +pub struct UserSignal { + pub fd: i32, + pub all_threads: bool, + pub chain: bool, +} + +#[cfg(unix)] +#[derive(Clone, Copy)] +struct RegisteredUserSignal { + enabled: bool, + fd: i32, + all_threads: bool, + chain: bool, + previous: libc::sigaction, +} + +#[cfg(unix)] +impl Default for RegisteredUserSignal { + fn default() -> Self { + Self { + enabled: false, + fd: 2, + all_threads: true, + chain: false, + previous: unsafe { core::mem::zeroed() }, + } + } +} + +#[cfg(unix)] +static USER_SIGNALS: Mutex>> = Mutex::new(None); + +pub fn write_fd(fd: i32, buf: &[u8]) { + let _ = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len() as _) }; +} + +#[cfg(any(unix, windows))] +pub fn is_fatal_signal(signum: libc::c_int) -> bool { + unsafe { + FATAL_SIGNAL_HANDLERS + .iter() + .any(|handler| handler.signum == signum) + } +} + +#[cfg(any(unix, windows))] +pub fn fatal_signal_name(signum: libc::c_int) -> Option<&'static str> { + unsafe { + FATAL_SIGNAL_HANDLERS + .iter() + .find(|handler| handler.signum == signum) + .map(|handler| handler.name) + } +} + +#[cfg(any(unix, windows))] +pub fn abort_process() -> ! { + unsafe { libc::abort() } +} + +#[cfg(any(unix, windows))] +pub fn raise_signal(signum: libc::c_int) { + unsafe { + libc::raise(signum); + } +} + +#[cfg(unix)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } +} + +#[cfg(windows)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { GetCurrentThreadId() as u64 } +} + +#[cfg(unix)] +pub fn install_sigaction( + signum: libc::c_int, + handler: extern "C" fn(libc::c_int), + flags: libc::c_int, + previous: &mut libc::sigaction, +) -> bool { + let mut action: libc::sigaction = unsafe { core::mem::zeroed() }; + action.sa_sigaction = handler as *const () as libc::sighandler_t; + action.sa_flags = flags; + unsafe { libc::sigaction(signum, &action, previous) == 0 } +} + +#[cfg(unix)] +unsafe fn disable_fatal_signal_handler(handler: &mut FatalSignalHandler) { + if !handler.enabled { + return; + } + handler.enabled = false; + restore_sigaction(handler.signum, &handler.previous); +} + +#[cfg(unix)] +pub fn enable_fatal_handlers(handler: extern "C" fn(libc::c_int), flags: libc::c_int) -> bool { + unsafe { + let mut installed = Vec::new(); + for entry in &mut FATAL_SIGNAL_HANDLERS { + if entry.enabled { + continue; + } + + if !install_sigaction(entry.signum, handler, flags, &mut entry.previous) { + for signum in installed { + disable_fatal_signal(signum); + } + return false; + } + entry.enabled = true; + installed.push(entry.signum); + } + } + true +} + +#[cfg(unix)] +pub fn disable_fatal_signal(signum: libc::c_int) { + unsafe { + if let Some(handler) = FATAL_SIGNAL_HANDLERS + .iter_mut() + .find(|handler| handler.signum == signum) + { + disable_fatal_signal_handler(handler); + } + } +} + +#[cfg(unix)] +pub fn disable_fatal_handlers() { + unsafe { + for handler in &mut FATAL_SIGNAL_HANDLERS { + disable_fatal_signal_handler(handler); + } + } +} + +#[cfg(unix)] +pub fn restore_sigaction(signum: libc::c_int, previous: &libc::sigaction) { + unsafe { + libc::sigaction(signum, previous, core::ptr::null_mut()); + } +} + +#[cfg(unix)] +pub fn signal_default_and_raise(signum: libc::c_int) { + unsafe { + libc::signal(signum, libc::SIG_DFL); + libc::raise(signum); + } +} + +#[cfg(unix)] +pub fn exit_immediately(code: libc::c_int) -> ! { + unsafe { libc::_exit(code) } +} + +#[cfg(unix)] +pub fn get_user_signal(signum: usize) -> Option { + let guard = USER_SIGNALS.lock(); + guard + .as_ref() + .and_then(|signals| signals.get(signum)) + .and_then(|signal| { + signal.enabled.then_some(UserSignal { + fd: signal.fd, + all_threads: signal.all_threads, + chain: signal.chain, + }) + }) +} + +#[cfg(unix)] +pub fn register_user_signal( + signum: libc::c_int, + fd: i32, + all_threads: bool, + chain: bool, + handler: extern "C" fn(libc::c_int), +) -> std::io::Result<()> { + if signum < 0 || signum as usize >= USER_SIGNAL_CAPACITY { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + let signum = signum as usize; + let mut guard = USER_SIGNALS.lock(); + if guard.is_none() { + *guard = Some(vec![RegisteredUserSignal::default(); USER_SIGNAL_CAPACITY]); + } + let signals = guard + .as_mut() + .expect("user signal table must be initialized"); + let entry = &mut signals[signum]; + + if !entry.enabled { + let mut previous = unsafe { core::mem::zeroed() }; + if !install_sigaction( + signum as libc::c_int, + handler, + if chain { + libc::SA_NODEFER + } else { + libc::SA_RESTART + }, + &mut previous, + ) { + return Err(std::io::Error::last_os_error()); + } + entry.previous = previous; + } + + entry.enabled = true; + entry.fd = fd; + entry.all_threads = all_threads; + entry.chain = chain; + Ok(()) +} + +#[cfg(unix)] +pub fn unregister_user_signal(signum: libc::c_int) -> bool { + if signum < 0 { + return false; + } + let signum = signum as usize; + let mut guard = USER_SIGNALS.lock(); + let Some(signals) = guard.as_mut() else { + return false; + }; + let Some(entry) = signals.get_mut(signum) else { + return false; + }; + if !entry.enabled { + return false; + } + + let previous = entry.previous; + *entry = RegisteredUserSignal::default(); + restore_sigaction(signum as libc::c_int, &previous); + true +} + +#[cfg(unix)] +pub fn reraise_user_signal(signum: libc::c_int, handler: extern "C" fn(libc::c_int)) -> bool { + if signum < 0 { + return false; + } + let signum_usize = signum as usize; + let previous = { + let guard = USER_SIGNALS.lock(); + let Some(signals) = guard.as_ref() else { + return false; + }; + let Some(entry) = signals.get(signum_usize) else { + return false; + }; + if !entry.enabled || !entry.chain { + return false; + } + entry.previous + }; + + let saved_errno = crate::os::get_errno(); + restore_sigaction(signum, &previous); + crate::os::set_errno(saved_errno); + raise_signal(signum); + + let mut ignored_previous = unsafe { core::mem::zeroed() }; + let _ = install_sigaction(signum, handler, libc::SA_NODEFER, &mut ignored_previous); + + crate::os::set_errno(saved_errno); + true +} + +#[cfg(windows)] +pub fn install_signal_handler( + signum: libc::c_int, + handler: extern "C" fn(libc::c_int), +) -> Result { + let previous = unsafe { libc::signal(signum, handler as *const () as libc::sighandler_t) }; + if previous == libc::SIG_ERR as libc::sighandler_t { + Err(()) + } else { + Ok(previous) + } +} + +#[cfg(windows)] +unsafe fn disable_fatal_signal_handler(handler: &mut FatalSignalHandler) { + if !handler.enabled { + return; + } + handler.enabled = false; + restore_signal_handler(handler.signum, handler.previous); +} + +#[cfg(windows)] +pub fn enable_fatal_handlers(handler: extern "C" fn(libc::c_int), _flags: libc::c_int) -> bool { + unsafe { + for entry in &mut FATAL_SIGNAL_HANDLERS { + if entry.enabled { + continue; + } + + let Ok(previous) = install_signal_handler(entry.signum, handler) else { + return false; + }; + entry.previous = previous; + entry.enabled = true; + } + } + true +} + +#[cfg(windows)] +pub fn disable_fatal_signal(signum: libc::c_int) { + unsafe { + if let Some(handler) = FATAL_SIGNAL_HANDLERS + .iter_mut() + .find(|handler| handler.signum == signum) + { + disable_fatal_signal_handler(handler); + } + } +} + +#[cfg(windows)] +pub fn disable_fatal_handlers() { + unsafe { + for handler in &mut FATAL_SIGNAL_HANDLERS { + disable_fatal_signal_handler(handler); + } + } +} + +#[cfg(windows)] +pub fn restore_signal_handler(signum: libc::c_int, previous: libc::sighandler_t) { + unsafe { + libc::signal(signum, previous); + } +} + +#[cfg(windows)] +pub fn signal_default_and_raise(signum: libc::c_int) { + unsafe { + libc::signal(signum, libc::SIG_DFL); + libc::raise(signum); + } +} + +#[cfg(windows)] +pub fn add_vectored_exception_handler(handler: PVECTORED_EXCEPTION_HANDLER) -> usize { + unsafe { AddVectoredExceptionHandler(1, handler) as usize } +} + +#[cfg(windows)] +pub fn remove_vectored_exception_handler(handle: usize) { + if handle != 0 { + unsafe { + RemoveVectoredExceptionHandler(handle as *mut core::ffi::c_void); + } + } +} + +#[cfg(windows)] +pub fn suppress_crash_report() { + unsafe { + let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); + } +} + +#[cfg(windows)] +pub fn raise_exception(code: u32, flags: u32) { + unsafe { + RaiseException(code, flags, 0, core::ptr::null()); + } +} + +#[cfg(windows)] +pub fn ignore_exception(code: u32) -> bool { + if (code & 0x8000_0000) == 0 { + return true; + } + code == 0xE06D7363 || code == 0xE0434352 +} + +#[cfg(windows)] +pub fn exception_description(code: u32) -> Option<&'static str> { + match code { + 0xC0000005 => Some("access violation"), + 0xC000008C => Some("float divide by zero"), + 0xC0000091 => Some("float overflow"), + 0xC0000094 => Some("int divide by zero"), + 0xC0000095 => Some("integer overflow"), + 0xC0000006 => Some("page error"), + 0xC00000FD => Some("stack overflow"), + 0xC000001D => Some("illegal instruction"), + _ => None, + } +} + +#[cfg(windows)] +pub unsafe fn exception_code(exc_info: *mut EXCEPTION_POINTERS) -> u32 { + let record = unsafe { &*(*exc_info).ExceptionRecord }; + record.ExceptionCode as u32 +} + +#[cfg(windows)] +#[inline] +pub fn is_access_violation(code: u32) -> bool { + code == 0xC0000005 +} diff --git a/crates/host_env/src/fcntl.rs b/crates/host_env/src/fcntl.rs index b4dba53fa3d..f8881a46456 100644 --- a/crates/host_env/src/fcntl.rs +++ b/crates/host_env/src/fcntl.rs @@ -1,5 +1,12 @@ use std::io; +#[cfg(unix)] +use std::os::fd::BorrowedFd; + +pub fn normalize_ioctl_request(request: i64) -> libc::c_ulong { + (request as u32) as libc::c_ulong +} + pub fn fcntl_int(fd: i32, cmd: i32, arg: i32) -> io::Result { let ret = unsafe { libc::fcntl(fd, cmd, arg) }; if ret < 0 { @@ -9,6 +16,45 @@ pub fn fcntl_int(fd: i32, cmd: i32, arg: i32) -> io::Result { } } +pub fn validate_fd(fd: i32) -> io::Result<()> { + fcntl_int(fd, libc::F_GETFD, 0).map(|_| ()) +} + +#[cfg(unix)] +pub fn get_inheritable(fd: BorrowedFd<'_>) -> io::Result { + use nix::fcntl as nix_fcntl; + + let flags = nix_fcntl::FdFlag::from_bits_truncate( + nix_fcntl::fcntl(fd, nix_fcntl::FcntlArg::F_GETFD).map_err(io::Error::from)?, + ); + Ok(!flags.contains(nix_fcntl::FdFlag::FD_CLOEXEC)) +} + +#[cfg(unix)] +pub fn get_blocking(fd: BorrowedFd<'_>) -> io::Result { + use nix::fcntl as nix_fcntl; + + let flags = nix_fcntl::OFlag::from_bits_truncate( + nix_fcntl::fcntl(fd, nix_fcntl::FcntlArg::F_GETFL).map_err(io::Error::from)?, + ); + Ok(!flags.contains(nix_fcntl::OFlag::O_NONBLOCK)) +} + +#[cfg(unix)] +pub fn set_blocking(fd: BorrowedFd<'_>, blocking: bool) -> io::Result<()> { + use nix::fcntl as nix_fcntl; + + let flags = nix_fcntl::OFlag::from_bits_truncate( + nix_fcntl::fcntl(fd, nix_fcntl::FcntlArg::F_GETFL).map_err(io::Error::from)?, + ); + let mut new_flags = flags; + new_flags.set(nix_fcntl::OFlag::O_NONBLOCK, !blocking); + if flags != new_flags { + nix_fcntl::fcntl(fd, nix_fcntl::FcntlArg::F_SETFL(new_flags)).map_err(io::Error::from)?; + } + Ok(()) +} + pub fn fcntl_with_bytes(fd: i32, cmd: i32, arg: &mut [u8]) -> io::Result { let ret = unsafe { libc::fcntl(fd, cmd, arg.as_mut_ptr()) }; if ret < 0 { @@ -55,7 +101,42 @@ pub fn flock(fd: i32, operation: i32) -> io::Result { } #[cfg(not(any(target_os = "wasi", target_os = "redox")))] -pub fn lockf(fd: i32, cmd: i32, lock: &libc::flock) -> io::Result { +pub enum LockfError { + InvalidCmd, + Overflow(String), + Io(io::Error), +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn lockf(fd: i32, cmd: i32, len: i64, start: i64, whence: i32) -> Result { + fn convert_field(value: T) -> Result + where + T: TryInto, + T::Error: core::fmt::Display, + { + value + .try_into() + .map_err(|err| LockfError::Overflow(err.to_string())) + } + + let l_type = if cmd == libc::LOCK_UN { + libc::F_UNLCK + } else if (cmd & libc::LOCK_SH) != 0 { + libc::F_RDLCK + } else if (cmd & libc::LOCK_EX) != 0 { + libc::F_WRLCK + } else { + return Err(LockfError::InvalidCmd); + }; + + let lock = libc::flock { + l_type: convert_field(l_type)?, + l_whence: convert_field(whence)?, + l_start: convert_field(start)?, + l_len: convert_field(len)?, + ..unsafe { core::mem::zeroed() } + }; + let ret = unsafe { libc::fcntl( fd, @@ -64,11 +145,11 @@ pub fn lockf(fd: i32, cmd: i32, lock: &libc::flock) -> io::Result { } else { libc::F_SETLKW }, - lock, + &lock, ) }; if ret < 0 { - Err(io::Error::last_os_error()) + Err(LockfError::Io(io::Error::last_os_error())) } else { Ok(ret) } diff --git a/crates/host_env/src/grp.rs b/crates/host_env/src/grp.rs new file mode 100644 index 00000000000..131369ce949 --- /dev/null +++ b/crates/host_env/src/grp.rs @@ -0,0 +1,53 @@ +use std::io; + +pub struct Group { + pub name: String, + pub passwd: String, + pub gid: u32, + pub mem: Vec, +} + +fn cstr_lossy(s: alloc::ffi::CString) -> String { + s.into_string() + .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) +} + +impl From for Group { + fn from(group: nix::unistd::Group) -> Self { + Self { + name: group.name, + passwd: cstr_lossy(group.passwd), + gid: group.gid.as_raw(), + mem: group.mem, + } + } +} + +pub fn getgrgid(gid: libc::gid_t) -> io::Result> { + nix::unistd::Group::from_gid(nix::unistd::Gid::from_raw(gid)) + .map(|group| group.map(Into::into)) + .map_err(io::Error::from) +} + +pub fn getgrnam(name: &str) -> io::Result> { + nix::unistd::Group::from_name(name) + .map(|group| group.map(Into::into)) + .map_err(io::Error::from) +} + +pub fn getgrall() -> Vec { + use core::ptr::NonNull; + + static GETGRALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); + let _guard = GETGRALL.lock(); + let mut list = Vec::new(); + + unsafe { libc::setgrent() }; + while let Some(ptr) = NonNull::new(unsafe { libc::getgrent() }) { + let group = nix::unistd::Group::from(unsafe { ptr.as_ref() }); + list.push(group.into()); + } + unsafe { libc::endgrent() }; + + list +} diff --git a/crates/host_env/src/io.rs b/crates/host_env/src/io.rs new file mode 100644 index 00000000000..49eaeddcb62 --- /dev/null +++ b/crates/host_env/src/io.rs @@ -0,0 +1,254 @@ +#[cfg(any(unix, target_os = "wasi"))] +use core::ffi::CStr; +use std::io; + +#[cfg(any(unix, target_os = "wasi"))] +use crate::fileutils; +use crate::{crt_fd, os}; + +bitflags::bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct FileMode: u8 { + const CREATED = 0b0001; + const READABLE = 0b0010; + const WRITABLE = 0b0100; + const APPENDING = 0b1000; + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FileModeError { + Invalid, + BadRwa, +} + +impl FileModeError { + pub fn error_msg(self, mode_str: &str) -> String { + match self { + Self::Invalid => format!("invalid mode: {mode_str}"), + Self::BadRwa => { + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_owned() + } + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct ParsedFileMode { + pub mode: FileMode, + pub flags: i32, +} + +impl FileMode { + pub const fn raw_mode(self) -> &'static str { + if self.contains(Self::CREATED) { + if self.contains(Self::READABLE) { + "xb+" + } else { + "xb" + } + } else if self.contains(Self::APPENDING) { + if self.contains(Self::READABLE) { + "ab+" + } else { + "ab" + } + } else if self.contains(Self::READABLE) { + if self.contains(Self::WRITABLE) { + "rb+" + } else { + "rb" + } + } else { + "wb" + } + } +} + +pub fn parse_fileio_mode(mode_str: &str) -> Result { + let mut flags = 0; + let mut plus = false; + let mut rwa = false; + let mut mode = FileMode::empty(); + for c in mode_str.bytes() { + match c { + b'x' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE | FileMode::CREATED); + flags |= libc::O_EXCL | libc::O_CREAT; + } + b'r' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::READABLE); + } + b'w' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE); + flags |= libc::O_CREAT | libc::O_TRUNC; + } + b'a' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE | FileMode::APPENDING); + flags |= libc::O_APPEND | libc::O_CREAT; + } + b'+' => { + if plus { + return Err(FileModeError::BadRwa); + } + plus = true; + mode.insert(FileMode::READABLE | FileMode::WRITABLE); + } + b'b' => {} + _ => return Err(FileModeError::Invalid), + } + } + + if !rwa { + return Err(FileModeError::BadRwa); + } + + if mode.contains(FileMode::READABLE | FileMode::WRITABLE) { + flags |= libc::O_RDWR; + } else if mode.contains(FileMode::READABLE) { + flags |= libc::O_RDONLY; + } else { + flags |= libc::O_WRONLY; + } + + #[cfg(windows)] + { + flags |= libc::O_BINARY | libc::O_NOINHERIT; + } + #[cfg(unix)] + { + flags |= libc::O_CLOEXEC; + } + + Ok(ParsedFileMode { mode, flags }) +} + +#[derive(Clone, Copy, Debug)] +pub struct FileTargetInfo { + pub blksize: Option, +} + +#[cfg(any(unix, target_os = "wasi"))] +pub fn inspect_file_target(fd: crt_fd::Borrowed<'_>) -> io::Result { + let status = fileutils::fstat(fd)?; + if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR { + return Err(io::Error::from_raw_os_error(libc::EISDIR)); + } + #[allow(clippy::useless_conversion, reason = "needed for 32-bit platforms")] + let blksize = (status.st_blksize > 1).then(|| i64::from(status.st_blksize)); + Ok(FileTargetInfo { blksize }) +} + +#[cfg(windows)] +pub fn inspect_file_target(fd: crt_fd::Borrowed<'_>) -> io::Result { + if !crate::nt::fd_exists(fd) { + return Err(io::Error::from_raw_os_error( + crate::nt::ERROR_INVALID_HANDLE_I32, + )); + } + Ok(FileTargetInfo { blksize: None }) +} + +#[cfg(any(unix, target_os = "wasi"))] +pub fn open_path(path: &CStr, flags: i32, mode: i32) -> io::Result { + crt_fd::open(path, flags, mode) +} + +#[cfg(windows)] +pub fn open_path(path: &widestring::WideCStr, flags: i32, mode: i32) -> io::Result { + crt_fd::wopen(path, flags, mode) +} + +#[cfg(windows)] +pub fn should_forget_fd_after_inspect_error(err: &io::Error, _fd_is_own: bool) -> bool { + err.raw_os_error() == Some(crate::nt::ERROR_INVALID_HANDLE_I32) +} + +#[cfg(any(unix, target_os = "wasi"))] +pub fn should_forget_fd_after_inspect_error(err: &io::Error, fd_is_own: bool) -> bool { + let errno = err.raw_os_error(); + (errno == Some(libc::EISDIR) || errno == Some(libc::EBADF)) + && (!fd_is_own || errno == Some(libc::EBADF)) +} + +pub fn seek_to_end(fd: crt_fd::Borrowed<'_>) -> io::Result { + os::seek_fd(fd, 0, libc::SEEK_END) +} + +pub fn is_seekable(fd: crt_fd::Borrowed<'_>) -> bool { + os::seek_fd(fd, 0, libc::SEEK_CUR).is_ok() +} + +pub fn validate_whence(whence: i32) -> bool { + let standard = (0..=2).contains(&whence); + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] + { + standard || matches!(whence, libc::SEEK_DATA | libc::SEEK_HOLE) + } + #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux")))] + { + standard + } +} + +pub fn is_interrupted_errno(errno: i32) -> bool { + errno == libc::EINTR +} + +pub fn is_interrupted_error(err: &io::Error) -> bool { + err.raw_os_error() == Some(libc::EINTR) +} + +pub fn is_would_block_error(err: &io::Error) -> bool { + err.kind() == io::ErrorKind::WouldBlock || err.raw_os_error() == Some(libc::EAGAIN) +} + +pub fn seek( + fd: crt_fd::Borrowed<'_>, + offset: crt_fd::Offset, + how: i32, +) -> io::Result { + os::seek_fd(fd, offset, how) +} + +pub fn tell(fd: crt_fd::Borrowed<'_>) -> io::Result { + os::seek_fd(fd, 0, libc::SEEK_CUR) +} + +pub fn isatty(fd: i32) -> bool { + os::isatty(fd) +} + +pub fn read_once(fd: crt_fd::Borrowed<'_>, buf: &mut [u8]) -> io::Result { + crt_fd::read(fd, buf) +} + +pub fn read_all(fd: crt_fd::Borrowed<'_>, out: &mut Vec) -> io::Result<()> { + let mut fd = fd; + std::io::Read::read_to_end(&mut fd, out).map(|_| ()) +} + +pub fn write_once(fd: crt_fd::Borrowed<'_>, buf: &[u8]) -> io::Result { + crt_fd::write(fd, buf) +} + +pub fn close_owned_fd(fd: crt_fd::Owned) -> io::Result<()> { + crt_fd::close(fd) +} diff --git a/crates/host_env/src/io_unsupported.rs b/crates/host_env/src/io_unsupported.rs new file mode 100644 index 00000000000..a9a138b7afc --- /dev/null +++ b/crates/host_env/src/io_unsupported.rs @@ -0,0 +1,225 @@ +use core::ffi::CStr; +use std::io; + +use crate::crt_fd; + +const EBADF: i32 = 9; +const EAGAIN: i32 = 11; +const EINTR: i32 = 4; +const EISDIR: i32 = 21; + +const O_RDONLY: i32 = 0; +const O_WRONLY: i32 = 1; +const O_RDWR: i32 = 2; +const O_APPEND: i32 = 0x0008; +const O_CREAT: i32 = 0x0200; +const O_TRUNC: i32 = 0x0400; +const O_EXCL: i32 = 0x0800; + +bitflags::bitflags! { + #[derive(Copy, Clone, Debug, PartialEq)] + pub struct FileMode: u8 { + const CREATED = 0b0001; + const READABLE = 0b0010; + const WRITABLE = 0b0100; + const APPENDING = 0b1000; + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FileModeError { + Invalid, + BadRwa, +} + +impl FileModeError { + pub fn error_msg(self, mode_str: &str) -> String { + match self { + Self::Invalid => format!("invalid mode: {mode_str}"), + Self::BadRwa => { + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_owned() + } + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct ParsedFileMode { + pub mode: FileMode, + pub flags: i32, +} + +impl FileMode { + pub const fn raw_mode(self) -> &'static str { + if self.contains(Self::CREATED) { + if self.contains(Self::READABLE) { + "xb+" + } else { + "xb" + } + } else if self.contains(Self::APPENDING) { + if self.contains(Self::READABLE) { + "ab+" + } else { + "ab" + } + } else if self.contains(Self::READABLE) { + if self.contains(Self::WRITABLE) { + "rb+" + } else { + "rb" + } + } else { + "wb" + } + } +} + +pub fn parse_fileio_mode(mode_str: &str) -> Result { + let mut flags = 0; + let mut plus = false; + let mut binary = false; + let mut rwa = false; + let mut mode = FileMode::empty(); + for c in mode_str.bytes() { + match c { + b'x' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE | FileMode::CREATED); + flags |= O_EXCL | O_CREAT; + } + b'r' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::READABLE); + } + b'w' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE); + flags |= O_CREAT | O_TRUNC; + } + b'a' => { + if rwa { + return Err(FileModeError::BadRwa); + } + rwa = true; + mode.insert(FileMode::WRITABLE | FileMode::APPENDING); + flags |= O_APPEND | O_CREAT; + } + b'+' => { + if plus { + return Err(FileModeError::BadRwa); + } + plus = true; + mode.insert(FileMode::READABLE | FileMode::WRITABLE); + } + b'b' => { + if binary { + return Err(FileModeError::Invalid); + } + binary = true; + } + _ => return Err(FileModeError::Invalid), + } + } + + if !rwa { + return Err(FileModeError::BadRwa); + } + + if mode.contains(FileMode::READABLE | FileMode::WRITABLE) { + flags |= O_RDWR; + } else if mode.contains(FileMode::READABLE) { + flags |= O_RDONLY; + } else { + flags |= O_WRONLY; + } + + Ok(ParsedFileMode { mode, flags }) +} + +#[derive(Clone, Copy, Debug)] +pub struct FileTargetInfo { + pub blksize: Option, +} + +pub fn inspect_file_target(_fd: crt_fd::Borrowed<'_>) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn open_path(_path: &CStr, _flags: i32, _mode: i32) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "host filesystem is unsupported on this platform", + )) +} + +pub fn should_forget_fd_after_inspect_error(err: &io::Error, fd_is_own: bool) -> bool { + let errno = err.raw_os_error(); + (errno == Some(EISDIR) || errno == Some(EBADF)) && (!fd_is_own || errno == Some(EBADF)) +} + +pub fn seek_to_end(_fd: crt_fd::Borrowed<'_>) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn is_seekable(_fd: crt_fd::Borrowed<'_>) -> bool { + false +} + +pub fn validate_whence(whence: i32) -> bool { + (0..=2).contains(&whence) +} + +pub fn is_interrupted_errno(errno: i32) -> bool { + errno == EINTR +} + +pub fn is_interrupted_error(err: &io::Error) -> bool { + err.raw_os_error() == Some(EINTR) +} + +pub fn is_would_block_error(err: &io::Error) -> bool { + err.raw_os_error() == Some(EAGAIN) +} + +pub fn seek( + _fd: crt_fd::Borrowed<'_>, + _offset: crt_fd::Offset, + _how: i32, +) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn tell(_fd: crt_fd::Borrowed<'_>) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn isatty(_fd: i32) -> bool { + false +} + +pub fn read_once(_fd: crt_fd::Borrowed<'_>, _buf: &mut [u8]) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn read_all(_fd: crt_fd::Borrowed<'_>, _out: &mut Vec) -> io::Result<()> { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn write_once(_fd: crt_fd::Borrowed<'_>, _buf: &[u8]) -> io::Result { + Err(io::Error::from_raw_os_error(EBADF)) +} + +pub fn close_owned_fd(_fd: crt_fd::Owned) -> io::Result<()> { + Err(io::Error::from_raw_os_error(EBADF)) +} diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index 80c2109a46f..66016345abd 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -1,18 +1,35 @@ +#![allow(clippy::must_use_candidate)] + extern crate alloc; #[macro_use] mod macros; pub use macros::*; +pub mod ctypes; +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod errno; +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod io; +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +#[path = "io_unsupported.rs"] +pub mod io; pub mod os; +#[cfg(any(unix, windows))] +pub mod thread; #[cfg(any(unix, windows, target_os = "wasi"))] pub mod crt_fd; +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +#[path = "crt_fd_unsupported.rs"] +pub mod crt_fd; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub mod fileutils; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub mod fs; +#[cfg(any(unix, windows))] +pub mod locale; #[cfg(windows)] pub mod windows; @@ -21,22 +38,49 @@ pub mod windows; pub mod fcntl; #[cfg(any(unix, windows, target_os = "wasi"))] pub mod select; +#[cfg(any(unix, windows))] +pub mod socket; #[cfg(unix)] pub mod syslog; #[cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))] pub mod termios; #[cfg(unix)] +pub mod grp; +#[cfg(unix)] +pub mod posix; +#[cfg(target_os = "wasi")] +#[path = "posix_wasi.rs"] pub mod posix; +#[cfg(unix)] +pub mod pwd; +#[cfg(unix)] +pub mod resource; #[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] pub mod shm; -#[cfg(unix)] +#[cfg(any(unix, windows))] pub mod signal; pub mod time; +#[cfg(windows)] +pub mod cert_store; +#[cfg(any(unix, windows))] +pub mod faulthandler; +#[cfg(any(unix, windows))] +pub mod mmap; #[cfg(windows)] pub mod msvcrt; +#[cfg(any(unix, windows))] +pub mod multiprocessing; #[cfg(windows)] pub mod nt; #[cfg(windows)] +pub mod overlapped; +#[cfg(windows)] +pub mod testconsole; +#[cfg(windows)] pub mod winapi; +#[cfg(windows)] +pub mod winreg; +#[cfg(windows)] +pub mod wmi; diff --git a/crates/host_env/src/locale.rs b/crates/host_env/src/locale.rs new file mode 100644 index 00000000000..52fa7904421 --- /dev/null +++ b/crates/host_env/src/locale.rs @@ -0,0 +1,164 @@ +use alloc::vec::Vec; +use core::{ffi::CStr, ptr}; + +#[cfg(windows)] +#[repr(C)] +struct RawLconv { + decimal_point: *mut libc::c_char, + thousands_sep: *mut libc::c_char, + grouping: *mut libc::c_char, + int_curr_symbol: *mut libc::c_char, + currency_symbol: *mut libc::c_char, + mon_decimal_point: *mut libc::c_char, + mon_thousands_sep: *mut libc::c_char, + mon_grouping: *mut libc::c_char, + positive_sign: *mut libc::c_char, + negative_sign: *mut libc::c_char, + int_frac_digits: libc::c_char, + frac_digits: libc::c_char, + p_cs_precedes: libc::c_char, + p_sep_by_space: libc::c_char, + n_cs_precedes: libc::c_char, + n_sep_by_space: libc::c_char, + p_sign_posn: libc::c_char, + n_sign_posn: libc::c_char, +} + +#[cfg(windows)] +unsafe extern "C" { + fn localeconv() -> *mut RawLconv; +} + +#[cfg(unix)] +use libc::localeconv; + +#[derive(Debug, Clone)] +pub struct LocaleConv { + pub decimal_point: Vec, + pub thousands_sep: Vec, + pub grouping: Vec, + pub int_curr_symbol: Vec, + pub currency_symbol: Vec, + pub mon_decimal_point: Vec, + pub mon_thousands_sep: Vec, + pub mon_grouping: Vec, + pub positive_sign: Vec, + pub negative_sign: Vec, + pub int_frac_digits: libc::c_char, + pub frac_digits: libc::c_char, + pub p_cs_precedes: libc::c_char, + pub p_sep_by_space: libc::c_char, + pub n_cs_precedes: libc::c_char, + pub n_sep_by_space: libc::c_char, + pub p_sign_posn: libc::c_char, + pub n_sign_posn: libc::c_char, +} + +fn copy_cstr(ptr: *const libc::c_char) -> Vec { + if ptr.is_null() { + Vec::new() + } else { + unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec() + } +} + +fn copy_grouping(ptr: *const libc::c_char) -> Vec { + if ptr.is_null() { + return Vec::new(); + } + let mut out = Vec::new(); + let mut cur = ptr; + unsafe { + while ![0, libc::c_char::MAX].contains(&*cur) { + out.push(*cur); + cur = cur.add(1); + } + } + out +} + +pub fn localeconv_data() -> LocaleConv { + let lc = unsafe { localeconv() }; + unsafe { + LocaleConv { + decimal_point: copy_cstr((*lc).decimal_point), + thousands_sep: copy_cstr((*lc).thousands_sep), + grouping: copy_grouping((*lc).grouping), + int_curr_symbol: copy_cstr((*lc).int_curr_symbol), + currency_symbol: copy_cstr((*lc).currency_symbol), + mon_decimal_point: copy_cstr((*lc).mon_decimal_point), + mon_thousands_sep: copy_cstr((*lc).mon_thousands_sep), + mon_grouping: copy_grouping((*lc).mon_grouping), + positive_sign: copy_cstr((*lc).positive_sign), + negative_sign: copy_cstr((*lc).negative_sign), + int_frac_digits: (*lc).int_frac_digits, + frac_digits: (*lc).frac_digits, + p_cs_precedes: (*lc).p_cs_precedes, + p_sep_by_space: (*lc).p_sep_by_space, + n_cs_precedes: (*lc).n_cs_precedes, + n_sep_by_space: (*lc).n_sep_by_space, + p_sign_posn: (*lc).p_sign_posn, + n_sign_posn: (*lc).n_sign_posn, + } + } +} + +pub fn strcoll(string1: &CStr, string2: &CStr) -> libc::c_int { + unsafe { libc::strcoll(string1.as_ptr(), string2.as_ptr()) } +} + +pub fn strxfrm(string: &CStr, _initial_len: usize) -> Vec { + let len = unsafe { libc::strxfrm(ptr::null_mut(), string.as_ptr(), 0) }; + let mut buff = vec![0u8; len + 1]; + unsafe { + libc::strxfrm(buff.as_mut_ptr() as _, string.as_ptr(), buff.len()); + } + buff.truncate(len); + buff +} + +pub fn setlocale(category: i32, locale: Option<&CStr>) -> Option> { + let result = unsafe { + match locale { + None => libc::setlocale(category, ptr::null()), + Some(locale) => libc::setlocale(category, locale.as_ptr()), + } + }; + (!result.is_null()).then(|| unsafe { CStr::from_ptr(result) }.to_bytes().to_vec()) +} + +#[cfg(windows)] +pub fn acp() -> u32 { + unsafe { windows_sys::Win32::Globalization::GetACP() } +} + +#[cfg(windows)] +pub fn decode_ansi_bytes(bytes: &[u8]) -> Option { + use core::ptr; + use windows_sys::Win32::Globalization::{CP_ACP, MultiByteToWideChar}; + + if bytes.is_empty() { + return Some(String::new()); + } + let len_i32 = i32::try_from(bytes.len()).ok()?; + + let len = + unsafe { MultiByteToWideChar(CP_ACP, 0, bytes.as_ptr(), len_i32, ptr::null_mut(), 0) }; + if len <= 0 { + return None; + } + let mut wide = vec![0u16; len as usize]; + unsafe { + MultiByteToWideChar(CP_ACP, 0, bytes.as_ptr(), len_i32, wide.as_mut_ptr(), len); + } + Some(String::from_utf16_lossy(&wide)) +} + +#[cfg(all( + unix, + not(any(target_os = "ios", target_os = "android", target_os = "redox")) +))] +pub fn nl_langinfo_codeset() -> Option> { + let codeset = unsafe { libc::nl_langinfo(libc::CODESET) }; + (!codeset.is_null()).then(|| unsafe { CStr::from_ptr(codeset) }.to_bytes().to_vec()) +} diff --git a/crates/host_env/src/mmap.rs b/crates/host_env/src/mmap.rs new file mode 100644 index 00000000000..ead802949db --- /dev/null +++ b/crates/host_env/src/mmap.rs @@ -0,0 +1,371 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "These helpers are thin wrappers around raw Windows mapping APIs." +)] + +use std::io; + +#[cfg(unix)] +use crate::{crt_fd, fileutils, posix}; +use memmap2::{Mmap, MmapMut, MmapOptions}; +#[cfg(windows)] +use windows_sys::Win32::{ + Foundation::{ + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, GetLastError, HANDLE, + INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, + System::{ + Memory::{ + CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile, + MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, + PAGE_WRITECOPY, UnmapViewOfFile, + }, + Threading::GetCurrentProcess, + }, +}; + +#[cfg(windows)] +pub type Handle = HANDLE; +#[cfg(windows)] +pub const INVALID_HANDLE: Handle = INVALID_HANDLE_VALUE; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AccessMode { + Default = 0, + Read = 1, + Write = 2, + Copy = 3, +} + +#[cfg(windows)] +#[derive(Debug)] +pub struct NamedMmap { + map_handle: Handle, + view_ptr: *mut u8, + len: usize, +} + +#[derive(Debug)] +pub enum MappedFile { + Read(Mmap), + Write(MmapMut), +} + +impl MappedFile { + pub fn as_slice(&self) -> &[u8] { + match self { + Self::Read(mmap) => &mmap[..], + Self::Write(mmap) => &mmap[..], + } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + match self { + Self::Read(_) => panic!("mmap can't modify a readonly memory map."), + Self::Write(mmap) => &mut mmap[..], + } + } + + pub fn as_ptr(&self) -> *const u8 { + match self { + Self::Read(mmap) => mmap.as_ptr(), + Self::Write(mmap) => mmap.as_ptr(), + } + } + + pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> { + match self { + Self::Read(_) => Ok(()), + Self::Write(mmap) => mmap.flush_range(offset, size), + } + } + + #[cfg(all(unix, not(target_os = "redox")))] + pub fn madvise_range(&self, start: usize, length: usize, advice: i32) -> io::Result<()> { + let ptr = unsafe { self.as_ptr().add(start) }; + posix::madvise(ptr as usize, length, advice) + } +} + +#[cfg(windows)] +unsafe impl Send for NamedMmap {} +#[cfg(windows)] +unsafe impl Sync for NamedMmap {} + +#[cfg(windows)] +impl NamedMmap { + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.view_ptr, self.len) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.view_ptr, self.len) } + } + + pub fn ptr_at(&self, offset: usize) -> *const core::ffi::c_void { + unsafe { self.view_ptr.add(offset) as *const _ } + } + + pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> { + flush_view(self.ptr_at(offset), size) + } +} + +#[cfg(windows)] +impl Drop for NamedMmap { + fn drop(&mut self) { + unsafe { + if !self.view_ptr.is_null() { + UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { + Value: self.view_ptr as *mut _, + }); + } + if !self.map_handle.is_null() { + CloseHandle(self.map_handle); + } + } + } +} + +#[cfg(windows)] +pub fn duplicate_handle(handle: Handle) -> io::Result { + let mut new_handle: Handle = INVALID_HANDLE; + let result = unsafe { + DuplicateHandle( + GetCurrentProcess(), + handle, + GetCurrentProcess(), + &mut new_handle, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + }; + if result == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(new_handle) + } +} + +#[cfg(windows)] +pub fn get_file_len(handle: Handle) -> io::Result { + let mut high: u32 = 0; + let low = unsafe { GetFileSize(handle, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + return Err(err); + } + } + Ok(((high as i64) << 32) | (low as i64)) +} + +#[cfg(unix)] +pub fn file_len(fd: crt_fd::Borrowed<'_>) -> io::Result { + #[allow(clippy::useless_conversion, reason = "needed for 32-bit platforms")] + Ok(fileutils::fstat(fd)?.st_size.into()) +} + +#[cfg(unix)] +pub fn prepare_file_mapping(fd: crt_fd::Borrowed<'_>) { + #[cfg(target_os = "macos")] + { + let _ = posix::full_fsync(fd.into()); + } + #[cfg(not(target_os = "macos"))] + { + let _ = fd; + } +} + +#[cfg(windows)] +pub fn is_invalid_handle_value(handle: isize) -> bool { + handle == INVALID_HANDLE as isize +} + +#[cfg(windows)] +pub fn extend_file(handle: Handle, size: i64) -> io::Result<()> { + if unsafe { SetFilePointerEx(handle, size, core::ptr::null_mut(), FILE_BEGIN) } == 0 { + return Err(io::Error::last_os_error()); + } + if unsafe { SetEndOfFile(handle) } == 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +#[cfg(unix)] +pub fn close_descriptor(fd: i32) { + if fd >= 0 { + let _ = crt_fd::close(unsafe { crt_fd::Owned::from_raw(fd) }); + } +} + +#[cfg(windows)] +pub fn close_handle(handle: Handle) { + unsafe { CloseHandle(handle) }; +} + +#[cfg(windows)] +pub fn flush_view(ptr: *const core::ffi::c_void, size: usize) -> io::Result<()> { + if unsafe { FlushViewOfFile(ptr, size) } == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn last_error() -> u32 { + unsafe { GetLastError() } +} + +#[cfg(windows)] +pub fn create_named_mapping( + file_handle: Handle, + tag: &str, + access: AccessMode, + offset: i64, + map_size: usize, +) -> io::Result { + let (fl_protect, desired_access) = match access { + AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE), + AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ), + AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY), + }; + + let total_size = (offset as u64) + .checked_add(map_size as u64) + .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; + let size_hi = (total_size >> 32) as u32; + let size_lo = total_size as u32; + let tag_wide: Vec = tag.encode_utf16().chain(core::iter::once(0)).collect(); + + let map_handle = unsafe { + CreateFileMappingW( + file_handle, + core::ptr::null(), + fl_protect, + size_hi, + size_lo, + tag_wide.as_ptr(), + ) + }; + if map_handle.is_null() { + return Err(io::Error::last_os_error()); + } + + let off_hi = (offset as u64 >> 32) as u32; + let off_lo = offset as u32; + let view = unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) }; + if view.Value.is_null() { + unsafe { CloseHandle(map_handle) }; + return Err(io::Error::last_os_error()); + } + + Ok(NamedMmap { + map_handle, + view_ptr: view.Value as *mut u8, + len: map_size, + }) +} + +#[cfg(unix)] +pub fn map_anon(size: usize) -> io::Result { + let mut mmap_opt = MmapOptions::new(); + mmap_opt.len(size).map_anon().map(MappedFile::Write) +} + +#[cfg(windows)] +pub fn map_anon(size: usize) -> io::Result { + let mut mmap_opt = MmapOptions::new(); + mmap_opt.len(size).map_anon().map(MappedFile::Write) +} + +#[cfg(unix)] +pub fn map_file( + fd: crt_fd::Borrowed<'_>, + offset: i64, + size: usize, + access: AccessMode, +) -> io::Result<(crt_fd::Owned, MappedFile)> { + let new_fd: crt_fd::Owned = posix::dup_noninheritable(fd.into())?.into(); + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(size); + + let mapped = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&new_fd) }.map(MappedFile::Write)? + } + AccessMode::Read => unsafe { mmap_opt.map(&new_fd) }.map(MappedFile::Read)?, + AccessMode::Copy => unsafe { mmap_opt.map_copy(&new_fd) }.map(MappedFile::Write)?, + }; + + Ok((new_fd, mapped)) +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn validate_advice(advice: i32) -> bool { + match advice { + libc::MADV_NORMAL + | libc::MADV_RANDOM + | libc::MADV_SEQUENTIAL + | libc::MADV_WILLNEED + | libc::MADV_DONTNEED => true, + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "freebsd" + ))] + libc::MADV_FREE => true, + #[cfg(target_os = "linux")] + libc::MADV_DONTFORK + | libc::MADV_DOFORK + | libc::MADV_MERGEABLE + | libc::MADV_UNMERGEABLE + | libc::MADV_HUGEPAGE + | libc::MADV_NOHUGEPAGE + | libc::MADV_REMOVE + | libc::MADV_DONTDUMP + | libc::MADV_DODUMP + | libc::MADV_HWPOISON => true, + #[cfg(target_os = "freebsd")] + libc::MADV_NOSYNC + | libc::MADV_AUTOSYNC + | libc::MADV_NOCORE + | libc::MADV_CORE + | libc::MADV_PROTECT => true, + _ => false, + } +} + +#[cfg(windows)] +pub fn map_handle( + handle: Handle, + offset: i64, + size: usize, + access: AccessMode, +) -> io::Result { + use std::{ + fs::File, + os::windows::io::{FromRawHandle, RawHandle}, + }; + + let file = unsafe { File::from_raw_handle(handle as RawHandle) }; + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(size); + + let result = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&file) }.map(MappedFile::Write) + } + AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MappedFile::Read), + AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MappedFile::Write), + }; + + core::mem::forget(file); + result +} diff --git a/crates/host_env/src/msvcrt.rs b/crates/host_env/src/msvcrt.rs index 6905c2d6556..47ada95c96f 100644 --- a/crates/host_env/src/msvcrt.rs +++ b/crates/host_env/src/msvcrt.rs @@ -4,11 +4,17 @@ use std::io; use crate::crt_fd; use windows_sys::Win32::System::Diagnostics::Debug; +pub type ErrorMode = u32; + pub const LK_UNLCK: i32 = 0; pub const LK_LOCK: i32 = 1; pub const LK_NBLCK: i32 = 2; pub const LK_RLCK: i32 = 3; pub const LK_NBRLCK: i32 = 4; +pub const SEM_FAILCRITICALERRORS: ErrorMode = Debug::SEM_FAILCRITICALERRORS; +pub const SEM_NOALIGNMENTFAULTEXCEPT: ErrorMode = Debug::SEM_NOALIGNMENTFAULTEXCEPT; +pub const SEM_NOGPFAULTERRORBOX: ErrorMode = Debug::SEM_NOGPFAULTERRORBOX; +pub const SEM_NOOPENFILEERRORBOX: ErrorMode = Debug::SEM_NOOPENFILEERRORBOX; unsafe extern "C" { fn _getch() -> i32; @@ -37,9 +43,7 @@ pub fn getch() -> Vec { #[must_use] pub fn getwch() -> String { let value = unsafe { _getwch() }; - char::from_u32(value) - .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwch")) - .to_string() + char::from_u32(value).unwrap().to_string() } #[must_use] @@ -50,9 +54,7 @@ pub fn getche() -> Vec { #[must_use] pub fn getwche() -> String { let value = unsafe { _getwche() }; - char::from_u32(value) - .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwche")) - .to_string() + char::from_u32(value).unwrap().to_string() } pub fn putch(c: u8) { @@ -126,6 +128,6 @@ pub fn get_error_mode() -> u32 { unsafe { suppress_iph!(Debug::GetErrorMode()) } } -pub fn set_error_mode(mode: Debug::THREAD_ERROR_MODE) -> u32 { +pub fn set_error_mode(mode: ErrorMode) -> u32 { unsafe { suppress_iph!(Debug::SetErrorMode(mode)) } } diff --git a/crates/host_env/src/multiprocessing.rs b/crates/host_env/src/multiprocessing.rs new file mode 100644 index 00000000000..32289c99bfd --- /dev/null +++ b/crates/host_env/src/multiprocessing.rs @@ -0,0 +1,513 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "Semaphore helpers intentionally mirror OS handle and pointer APIs." +)] +#![allow( + clippy::result_unit_err, + reason = "These helpers preserve the existing host-facing error surface." +)] + +#[cfg(unix)] +use alloc::ffi::CString; +#[cfg(windows)] +use std::io; + +#[cfg(unix)] +use libc::sem_t; +#[cfg(unix)] +use nix::errno::Errno; + +#[cfg(unix)] +#[derive(Debug)] +pub struct SemHandle { + raw: *mut sem_t, +} + +#[cfg(unix)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SemError { + WouldBlock, + TimedOut, + Interrupted, + AlreadyExists, + NotFound, + InvalidInput, + Other(i32), +} + +#[cfg(unix)] +impl SemError { + fn from_errno(err: Errno) -> Self { + match err { + Errno::EAGAIN => Self::WouldBlock, + Errno::ETIMEDOUT => Self::TimedOut, + Errno::EINTR => Self::Interrupted, + Errno::EEXIST => Self::AlreadyExists, + Errno::ENOENT => Self::NotFound, + Errno::EINVAL => Self::InvalidInput, + other => Self::Other(other as i32), + } + } + + pub fn raw_os_error(self) -> i32 { + match self { + Self::WouldBlock => Errno::EAGAIN as i32, + Self::TimedOut => Errno::ETIMEDOUT as i32, + Self::Interrupted => Errno::EINTR as i32, + Self::AlreadyExists => Errno::EEXIST as i32, + Self::NotFound => Errno::ENOENT as i32, + Self::InvalidInput => Errno::EINVAL as i32, + Self::Other(code) => code, + } + } + + pub fn description(self) -> String { + Errno::from_raw(self.raw_os_error()).desc().to_owned() + } +} + +#[cfg(unix)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TryAcquireStatus { + Acquired, + WouldBlock, + Interrupted, + Error(SemError), +} + +#[cfg(unix)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum WaitStatus { + Acquired, + TimedOut, + Interrupted, + Error(SemError), +} + +#[cfg(windows)] +use windows_sys::Win32::{ + Foundation::{ + CloseHandle, ERROR_TOO_MANY_POSTS, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_FAILED, + WAIT_OBJECT_0, WAIT_TIMEOUT, + }, + Networking::WinSock::{SOCKET, WSAGetLastError, closesocket, recv, send}, + System::Threading::{ + CreateSemaphoreW, GetCurrentThreadId, INFINITE, ReleaseSemaphore, WaitForSingleObjectEx, + }, +}; + +#[cfg(windows)] +pub type RawHandle = HANDLE; +#[cfg(windows)] +pub type RawSocket = SOCKET; +#[cfg(windows)] +pub const INFINITE_TIMEOUT: u32 = INFINITE; + +#[cfg(windows)] +#[derive(Debug)] +pub struct SemHandle { + raw: HANDLE, +} + +unsafe impl Send for SemHandle {} +unsafe impl Sync for SemHandle {} + +#[cfg(unix)] +impl SemHandle { + pub fn create( + name: &str, + value: u32, + unlink: bool, + ) -> Result<(Self, Option), SemError> { + let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?; + let raw = + unsafe { libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) }; + if raw == libc::SEM_FAILED { + return Err(SemError::from_errno(Errno::last())); + } + if unlink { + if unsafe { libc::sem_unlink(cname.as_ptr()) } != 0 { + let err = SemError::from_errno(Errno::last()); + unsafe { + libc::sem_close(raw); + } + Err(err) + } else { + Ok((Self { raw }, None)) + } + } else { + Ok((Self { raw }, Some(name.to_owned()))) + } + } + + pub fn open_existing(name: &str) -> Result { + let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?; + let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) }; + if raw == libc::SEM_FAILED { + Err(SemError::from_errno(Errno::last())) + } else { + Ok(Self { raw }) + } + } + + #[inline] + pub fn as_ptr(&self) -> *mut sem_t { + self.raw + } +} + +#[cfg(windows)] +impl SemHandle { + pub fn create(value: i32, maxvalue: i32) -> io::Result { + let handle = + unsafe { CreateSemaphoreW(core::ptr::null(), value, maxvalue, core::ptr::null()) }; + if handle == 0 as HANDLE { + Err(io::Error::last_os_error()) + } else { + Ok(Self { raw: handle }) + } + } + + #[inline] + pub fn from_raw(raw: HANDLE) -> Self { + Self { raw } + } + + #[inline] + pub fn as_raw(&self) -> HANDLE { + self.raw + } +} + +#[cfg(unix)] +impl Drop for SemHandle { + fn drop(&mut self) { + if !self.raw.is_null() { + unsafe { + libc::sem_close(self.raw); + } + } + } +} + +#[cfg(windows)] +impl Drop for SemHandle { + fn drop(&mut self) { + if self.raw != 0 as HANDLE && self.raw != INVALID_HANDLE_VALUE { + unsafe { + CloseHandle(self.raw); + } + } + } +} + +#[cfg(unix)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } +} + +#[cfg(windows)] +#[inline] +pub fn current_thread_id() -> u32 { + unsafe { GetCurrentThreadId() } +} + +#[cfg(windows)] +#[inline] +pub fn wait_for_single_object(handle: HANDLE, timeout_ms: u32) -> u32 { + unsafe { WaitForSingleObjectEx(handle, timeout_ms, 0) } +} + +#[cfg(windows)] +#[inline] +pub fn wait_object_0() -> u32 { + WAIT_OBJECT_0 +} + +#[cfg(windows)] +#[inline] +pub fn wait_timeout() -> u32 { + WAIT_TIMEOUT +} + +#[cfg(windows)] +#[inline] +pub fn close_socket(socket: SOCKET) -> io::Result<()> { + let res = unsafe { closesocket(socket) }; + if res != 0 { + Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() })) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn recv_socket(socket: SOCKET, size: usize) -> io::Result> { + let len = i32::try_from(size).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "socket receive size too large") + })?; + let mut buf = vec![0u8; size]; + let n_read = unsafe { recv(socket, buf.as_mut_ptr() as *mut _, len, 0) }; + if n_read < 0 { + Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() })) + } else { + buf.truncate(n_read as usize); + Ok(buf) + } +} + +#[cfg(windows)] +pub fn send_socket(socket: SOCKET, buf: &[u8]) -> io::Result { + let len = i32::try_from(buf.len()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "socket send buffer too large"))?; + let ret = unsafe { send(socket, buf.as_ptr() as *const _, len, 0) }; + if ret < 0 { + Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() })) + } else { + Ok(ret) + } +} + +#[cfg(windows)] +#[inline] +pub fn wait_failed() -> u32 { + WAIT_FAILED +} + +#[cfg(windows)] +pub fn release_semaphore(handle: HANDLE) -> Result<(), u32> { + if unsafe { ReleaseSemaphore(handle, 1, core::ptr::null_mut()) } == 0 { + Err(unsafe { GetLastError() }) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn get_semaphore_value(handle: HANDLE) -> Result { + match wait_for_single_object(handle, 0) { + WAIT_OBJECT_0 => { + let mut previous: i32 = 0; + if unsafe { ReleaseSemaphore(handle, 1, &mut previous) } == 0 { + Err(()) + } else { + Ok(previous + 1) + } + } + WAIT_TIMEOUT => Ok(0), + _ => Err(()), + } +} + +#[cfg(windows)] +#[inline] +pub fn is_too_many_posts(err: u32) -> bool { + err == ERROR_TOO_MANY_POSTS +} + +#[cfg(unix)] +pub fn semaphore_name(name: &str) -> Result { + let mut full = String::with_capacity(name.len() + 1); + if !name.starts_with('/') { + full.push('/'); + } + full.push_str(name); + CString::new(full) +} + +#[cfg(unix)] +pub fn sem_unlink(name: &str) -> Result<(), SemError> { + let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?; + let res = unsafe { libc::sem_unlink(cname.as_ptr()) }; + if res < 0 { + Err(SemError::from_errno(Errno::last())) + } else { + Ok(()) + } +} + +#[cfg(all(unix, not(target_vendor = "apple")))] +/// # Safety +/// +/// `handle` must point to a valid `sem_t` that remains alive for the duration +/// of this call and is valid to pass to `sem_getvalue`. +pub unsafe fn get_semaphore_value(handle: *mut sem_t) -> Result { + let mut sval: libc::c_int = 0; + let res = unsafe { libc::sem_getvalue(handle, &mut sval) }; + if res < 0 { + Err(SemError::from_errno(Errno::last())) + } else { + Ok(if sval < 0 { 0 } else { sval }) + } +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_trywait_status(handle: *mut sem_t) -> TryAcquireStatus { + if unsafe { libc::sem_trywait(handle) } == 0 { + TryAcquireStatus::Acquired + } else { + match Errno::last() { + Errno::EAGAIN => TryAcquireStatus::WouldBlock, + Errno::EINTR => TryAcquireStatus::Interrupted, + err => TryAcquireStatus::Error(SemError::from_errno(err)), + } + } +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_post(handle: *mut sem_t) -> Result<(), SemError> { + if unsafe { libc::sem_post(handle) } < 0 { + Err(SemError::from_errno(Errno::last())) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn sem_value_max() -> i32 { + let val = unsafe { libc::sysconf(libc::_SC_SEM_VALUE_MAX) }; + if val < 0 || val > i32::MAX as libc::c_long { + i32::MAX + } else { + val as i32 + } +} + +#[cfg(unix)] +pub fn gettimeofday() -> Result { + let mut tv = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + if unsafe { libc::gettimeofday(&mut tv, core::ptr::null_mut()) } < 0 { + Err(SemError::from_errno(Errno::last())) + } else { + Ok(tv) + } +} + +#[cfg(unix)] +pub fn deadline_from_timeout(timeout: f64) -> Result { + let timeout = if timeout < 0.0 { 0.0 } else { timeout }; + if !timeout.is_finite() { + return Err(SemError::InvalidInput); + } + let tv = gettimeofday()?; + let sec_f64 = timeout.floor(); + if sec_f64 > libc::time_t::MAX as f64 { + return Err(SemError::InvalidInput); + } + let sec = sec_f64 as libc::time_t; + let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long; + let tv_nsec = (tv.tv_usec as libc::c_long) + .checked_mul(1000) + .and_then(|base| base.checked_add(nsec)) + .ok_or(SemError::InvalidInput)?; + let mut deadline = libc::timespec { + tv_sec: tv.tv_sec.checked_add(sec).ok_or(SemError::InvalidInput)?, + tv_nsec: tv_nsec as _, + }; + deadline.tv_sec = deadline + .tv_sec + .checked_add((deadline.tv_nsec / 1_000_000_000) as libc::time_t) + .ok_or(SemError::InvalidInput)?; + deadline.tv_nsec %= 1_000_000_000; + Ok(deadline) +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_wait_status(handle: *mut sem_t, deadline: Option<&libc::timespec>) -> WaitStatus { + #[cfg(not(target_vendor = "apple"))] + if let Some(deadline) = deadline { + if unsafe { libc::sem_timedwait(handle, deadline) } == 0 { + WaitStatus::Acquired + } else { + match Errno::last() { + Errno::ETIMEDOUT => WaitStatus::TimedOut, + Errno::EINTR => WaitStatus::Interrupted, + err => WaitStatus::Error(SemError::from_errno(err)), + } + } + } else { + if unsafe { libc::sem_wait(handle) } == 0 { + WaitStatus::Acquired + } else { + match Errno::last() { + Errno::EINTR => WaitStatus::Interrupted, + err => WaitStatus::Error(SemError::from_errno(err)), + } + } + } + + #[cfg(target_vendor = "apple")] + { + debug_assert!(deadline.is_none()); + if unsafe { libc::sem_wait(handle) } == 0 { + WaitStatus::Acquired + } else { + match Errno::last() { + Errno::EINTR => WaitStatus::Interrupted, + err => WaitStatus::Error(SemError::from_errno(err)), + } + } + } +} + +#[cfg(target_vendor = "apple")] +pub enum PollWaitStep { + Acquired, + Timeout, + Continue(u64), +} + +#[cfg(target_vendor = "apple")] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_timedwait_poll_step( + handle: *mut sem_t, + deadline: &libc::timespec, + delay: u64, +) -> Result { + if unsafe { libc::sem_trywait(handle) } == 0 { + return Ok(PollWaitStep::Acquired); + } + let err = Errno::last(); + if err != Errno::EAGAIN { + return Err(SemError::from_errno(err)); + } + + let now = gettimeofday()?; + let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000; + #[allow(clippy::unnecessary_cast)] + let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64; + if now_usec >= deadline_usec { + return Ok(PollWaitStep::Timeout); + } + + let difference = (deadline_usec - now_usec) as u64; + let mut delay = delay + 1000; + if delay > 20000 { + delay = 20000; + } + if delay > difference { + delay = difference; + } + + let mut tv_delay = libc::timeval { + tv_sec: (delay / 1_000_000) as _, + tv_usec: (delay % 1_000_000) as _, + }; + unsafe { + libc::select( + 0, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut tv_delay, + ); + } + Ok(PollWaitStep::Continue(delay)) +} diff --git a/crates/host_env/src/nt.rs b/crates/host_env/src/nt.rs index c6771aad40a..078d588aea5 100644 --- a/crates/host_env/src/nt.rs +++ b/crates/host_env/src/nt.rs @@ -1,16 +1,353 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "This module mirrors raw Win32 path, handle, and CRT entry points." +)] + // cspell:ignore hchmod -use std::{ffi::OsStr, io, os::windows::io::AsRawHandle}; +use std::{ + ffi::{OsStr, OsString}, + io, + os::windows::{ffi::OsStringExt, io::AsRawHandle}, + path::Path, +}; -use crate::{crt_fd, windows::ToWideString}; +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::{ + crt_fd, + fileutils::{ + StatStruct, + windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, stat_basic_info_to_stat}, + }, + windows::ToWideString, +}; +use libc::intptr_t; use windows_sys::Win32::{ - Foundation::HANDLE, + Foundation::{ + CloseHandle, ERROR_INVALID_HANDLE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH, + }, + Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte}, Storage::FileSystem::{ - FILE_ATTRIBUTE_READONLY, FILE_BASIC_INFO, FileBasicInfo, GetFileAttributesW, - GetFileInformationByHandleEx, INVALID_FILE_ATTRIBUTES, SetFileAttributesW, - SetFileInformationByHandle, + CreateFileW, FILE_BASIC_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_READ_ATTRIBUTES, FILE_TYPE_UNKNOWN, FileBasicInfo, FindClose, FindFirstFileW, + GetFileAttributesW, GetFileInformationByHandleEx, GetFileType, GetFullPathNameW, + INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, SetFileAttributesW, SetFileInformationByHandle, + WIN32_FIND_DATAW, }, + System::{Console, Threading}, +}; + +pub type Handle = HANDLE; +pub const MAX_PATH_USIZE: usize = MAX_PATH as usize; +pub const ERROR_INVALID_HANDLE_I32: i32 = ERROR_INVALID_HANDLE as i32; +pub const LOAD_LIBRARY_SEARCH_APPLICATION_DIR: u32 = + windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_APPLICATION_DIR; +pub const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS: u32 = + windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; +pub const LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: u32 = + windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; +pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = + windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_SYSTEM32; +pub const LOAD_LIBRARY_SEARCH_USER_DIRS: u32 = + windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_USER_DIRS; + +pub use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM, + FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_VIRTUAL, }; +#[cfg(target_env = "msvc")] +unsafe extern "C" { + fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; + fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t; + fn _wexecve(cmdname: *const u16, argv: *const *const u16, envp: *const *const u16) -> intptr_t; + fn _wspawnv(mode: i32, cmdname: *const u16, argv: *const *const u16) -> intptr_t; + fn _wspawnve( + mode: i32, + cmdname: *const u16, + argv: *const *const u16, + envp: *const *const u16, + ) -> intptr_t; +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TestType { + RegularFile, + Directory, + Symlink, + Junction, + LinkReparsePoint, + RegularReparsePoint, +} + +const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; +const S_IFMT: u16 = libc::S_IFMT as u16; +const S_IFDIR_MODE: u16 = libc::S_IFDIR as u16; +const S_IFCHR_MODE: u16 = libc::S_IFCHR as u16; +const S_IFIFO_MODE: u16 = crate::fileutils::windows::S_IFIFO as u16; + +#[repr(C)] +#[derive(Default)] +struct FileAttributeTagInfo { + file_attributes: u32, + reparse_tag: u32, +} + +fn win32_large_integer_to_time(li: i64) -> (libc::time_t, i32) { + let nsec = ((li % 10_000_000) * 100) as i32; + let sec = (li / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) +} + +fn win32_filetime_to_time(ft_low: u32, ft_high: u32) -> (libc::time_t, i32) { + let ticks = ((ft_high as i64) << 32) | (ft_low as i64); + let nsec = ((ticks % 10_000_000) * 100) as i32; + let sec = (ticks / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) +} + +fn win32_attribute_data_to_stat( + info: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + reparse_tag: u32, + basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, + id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, +) -> StatStruct { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_REPARSE_POINT, + }; + + let mut st_mode: u16 = 0; + if info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 { + st_mode |= S_IFDIR_MODE | 0o111; + } else { + st_mode |= libc::S_IFREG as u16; + } + if info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0 { + st_mode |= 0o444; + } else { + st_mode |= 0o666; + } + + let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64); + let st_dev = id_info.map_or(info.dwVolumeSerialNumber, |id| id.VolumeSerialNumber as u32); + let st_nlink = info.nNumberOfLinks as i32; + + let (st_birthtime, st_birthtime_nsec, st_mtime, st_mtime_nsec, st_atime, st_atime_nsec) = + if let Some(bi) = basic_info { + let (birth, birth_nsec) = win32_large_integer_to_time(bi.CreationTime); + let (mtime, mtime_nsec) = win32_large_integer_to_time(bi.LastWriteTime); + let (atime, atime_nsec) = win32_large_integer_to_time(bi.LastAccessTime); + (birth, birth_nsec, mtime, mtime_nsec, atime, atime_nsec) + } else { + let (birth, birth_nsec) = win32_filetime_to_time( + info.ftCreationTime.dwLowDateTime, + info.ftCreationTime.dwHighDateTime, + ); + let (mtime, mtime_nsec) = win32_filetime_to_time( + info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime, + ); + let (atime, atime_nsec) = win32_filetime_to_time( + info.ftLastAccessTime.dwLowDateTime, + info.ftLastAccessTime.dwHighDateTime, + ); + (birth, birth_nsec, mtime, mtime_nsec, atime, atime_nsec) + }; + + let (st_ino, st_ino_high) = if let Some(id) = id_info { + let bytes = id.FileId.Identifier; + ( + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + ) + } else { + ( + ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64), + 0, + ) + }; + + if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && reparse_tag == IO_REPARSE_TAG_SYMLINK + { + st_mode = (st_mode & !S_IFMT) | crate::fileutils::windows::S_IFLNK as u16; + } + + StatStruct { + st_dev, + st_ino, + st_ino_high, + st_mode, + st_nlink, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + st_size, + st_atime, + st_atime_nsec, + st_mtime, + st_mtime_nsec, + st_ctime: 0, + st_ctime_nsec: 0, + st_birthtime, + st_birthtime_nsec, + st_file_attributes: info.dwFileAttributes, + st_reparse_tag: reparse_tag, + } +} + +pub fn visible_env_vars() -> impl Iterator { + crate::os::vars().filter(|(key, _)| !key.starts_with('=')) +} + +#[derive(Debug)] +pub enum ReadlinkError { + Io(io::Error), + NotSymbolicLink, + InvalidReparseData, +} + +#[derive(Debug)] +pub enum ReadConsoleError { + Io(io::Error), + BufferTooSmall { available: usize, required: usize }, +} + +pub fn access(path: &Path, mode: u8) -> bool { + let wide = path.as_os_str().to_wide_with_nul(); + let attr = unsafe { GetFileAttributesW(wide.as_ptr()) }; + attr != INVALID_FILE_ATTRIBUTES + && (mode & 2 == 0 + || attr & FILE_ATTRIBUTE_READONLY == 0 + || attr & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0) +} + +pub fn remove(path: &Path) -> io::Result<()> { + use windows_sys::Win32::Storage::FileSystem::{ + DeleteFileW, RemoveDirectoryW, WIN32_FIND_DATAW, + }; + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + let wide_path = path.as_os_str().to_wide_with_nul(); + let attrs = unsafe { GetFileAttributesW(wide_path.as_ptr()) }; + + let mut is_directory = false; + let mut is_link = false; + + if attrs != INVALID_FILE_ATTRIBUTES { + is_directory = + (attrs & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY) != 0; + + if is_directory + && (attrs & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) != 0 + { + let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; + let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; + if handle != INVALID_HANDLE_VALUE { + is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK + || find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT; + unsafe { FindClose(handle) }; + } + } + } + + let ok = if is_directory && is_link { + unsafe { RemoveDirectoryW(wide_path.as_ptr()) } + } else { + unsafe { DeleteFileW(wide_path.as_ptr()) } + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn supports_virtual_terminal() -> bool { + let mut mode = 0; + let handle = unsafe { Console::GetStdHandle(Console::STD_ERROR_HANDLE) }; + (unsafe { Console::GetConsoleMode(handle, &mut mode) }) != 0 + && mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 +} + +pub fn symlink( + src: &Path, + dst: &Path, + src_wide: &widestring::WideCStr, + dst_wide: &widestring::WideCStr, + target_is_directory: bool, +) -> io::Result<()> { + use windows_sys::Win32::Storage::FileSystem::WIN32_FILE_ATTRIBUTE_DATA; + use windows_sys::Win32::Storage::FileSystem::{ + CreateSymbolicLinkW, FILE_ATTRIBUTE_DIRECTORY, GetFileAttributesExW, + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE, SYMBOLIC_LINK_FLAG_DIRECTORY, + }; + + static HAS_UNPRIVILEGED_FLAG: AtomicBool = AtomicBool::new(true); + + fn check_dir(src: &Path, dst: &Path) -> bool { + use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard; + + let Some(dst_parent) = dst.parent() else { + return false; + }; + let resolved = if src.is_absolute() { + src.to_path_buf() + } else { + dst_parent.join(src) + }; + let wide = match widestring::WideCString::from_os_str(&resolved) { + Ok(wide) => wide, + Err(_) => return false, + }; + let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core::mem::zeroed() }; + let ok = unsafe { + GetFileAttributesExW( + wide.as_ptr(), + GetFileExInfoStandard, + (&mut info as *mut WIN32_FILE_ATTRIBUTE_DATA).cast(), + ) + }; + ok != 0 && (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 + } + + let mut flags = 0u32; + if HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) { + flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + } + if target_is_directory || check_dir(src, dst) { + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + } + + let mut result = unsafe { CreateSymbolicLinkW(dst_wide.as_ptr(), src_wide.as_ptr(), flags) }; + if !result + && HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) + && unsafe { windows_sys::Win32::Foundation::GetLastError() } + == windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER + { + let flags = flags & !SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + result = unsafe { CreateSymbolicLinkW(dst_wide.as_ptr(), src_wide.as_ptr(), flags) }; + if result + || unsafe { windows_sys::Win32::Foundation::GetLastError() } + != windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER + { + HAS_UNPRIVILEGED_FLAG.store(false, Ordering::Relaxed); + } + } + + if result { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn win32_hchmod(handle: HANDLE, mode: u32, write_bit: u32) -> io::Result<()> { let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; @@ -71,3 +408,1858 @@ pub fn win32_lchmod(path: &OsStr, mode: u32, write_bit: u32) -> io::Result<()> { Ok(()) } } + +pub fn chmod_follow(path: &widestring::WideCStr, mode: u32, write_bit: u32) -> io::Result<()> { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, + FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING, + }; + + let handle = unsafe { + CreateFileW( + path.as_ptr(), + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + let result = win32_hchmod(handle, mode, write_bit); + unsafe { CloseHandle(handle) }; + result +} + +pub fn find_first_file_name(path: &Path) -> io::Result { + let wide_path = path.as_os_str().to_wide_with_nul(); + let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + unsafe { FindClose(handle) }; + + let len = find_data + .cFileName + .iter() + .position(|&c| c == 0) + .unwrap_or(find_data.cFileName.len()); + Ok(OsString::from_wide(&find_data.cFileName[..len])) +} + +pub fn path_isdevdrive(path: &Path) -> io::Result { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_SHARE_READ, FILE_SHARE_WRITE, GetDriveTypeW, GetVolumePathNameW, + }; + use windows_sys::Win32::System::IO::DeviceIoControl; + use windows_sys::Win32::System::Ioctl::FSCTL_QUERY_PERSISTENT_VOLUME_STATE; + use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED; + + const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x0000_2000; + + #[repr(C)] + struct FileFsPersistentVolumeInformation { + volume_flags: u32, + flag_mask: u32, + version: u32, + reserved: u32, + } + + let wide_path = path.as_os_str().to_wide_with_nul(); + let mut volume = [0u16; MAX_PATH as usize]; + let ok = + unsafe { GetVolumePathNameW(wide_path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + if unsafe { GetDriveTypeW(volume.as_ptr()) } != DRIVE_FIXED { + return Ok(false); + } + + let handle = unsafe { + CreateFileW( + volume.as_ptr(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let mut volume_state = FileFsPersistentVolumeInformation { + volume_flags: 0, + flag_mask: PERSISTENT_VOLUME_STATE_DEV_VOLUME, + version: 1, + reserved: 0, + }; + let ok = unsafe { + DeviceIoControl( + handle, + FSCTL_QUERY_PERSISTENT_VOLUME_STATE, + (&volume_state as *const FileFsPersistentVolumeInformation).cast(), + core::mem::size_of::() as u32, + (&mut volume_state as *mut FileFsPersistentVolumeInformation).cast(), + core::mem::size_of::() as u32, + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + }; + unsafe { CloseHandle(handle) }; + + if ok == 0 { + let err = io::Error::last_os_error(); + if err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER as i32) + { + return Ok(false); + } + return Err(err); + } + + Ok((volume_state.volume_flags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) != 0) +} + +pub fn is_reparse_tag_name_surrogate(tag: u32) -> bool { + (tag & 0x20000000) != 0 +} + +pub fn file_info_error_is_trustworthy(error: u32) -> bool { + use windows_sys::Win32::Foundation; + matches!( + error, + Foundation::ERROR_FILE_NOT_FOUND + | Foundation::ERROR_PATH_NOT_FOUND + | Foundation::ERROR_NOT_READY + | Foundation::ERROR_BAD_NET_NAME + | Foundation::ERROR_BAD_NETPATH + | Foundation::ERROR_BAD_PATHNAME + | Foundation::ERROR_INVALID_NAME + | Foundation::ERROR_FILENAME_EXCED_RANGE + ) +} + +pub fn test_info( + attributes: u32, + reparse_tag: u32, + disk_device: bool, + tested_type: TestType, +) -> bool { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, + }; + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + match tested_type { + TestType::RegularFile => { + disk_device && attributes != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 + } + TestType::Directory => (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0, + TestType::Symlink => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && reparse_tag == IO_REPARSE_TAG_SYMLINK + } + TestType::Junction => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && reparse_tag == IO_REPARSE_TAG_MOUNT_POINT + } + TestType::LinkReparsePoint => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && is_reparse_tag_name_surrogate(reparse_tag) + } + TestType::RegularReparsePoint => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && reparse_tag != 0 + && !is_reparse_tag_name_surrogate(reparse_tag) + } + } +} + +pub fn test_file_type_by_handle(handle: HANDLE, tested_type: TestType, disk_only: bool) -> bool { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_TAG_INFO, FILE_TYPE_DISK, FileAttributeTagInfo as FileAttributeTagInfoClass, + }; + + let disk_device = unsafe { GetFileType(handle) } == FILE_TYPE_DISK; + if disk_only && !disk_device { + return false; + } + + if tested_type != TestType::RegularFile && tested_type != TestType::Directory { + let mut info: FILE_ATTRIBUTE_TAG_INFO = unsafe { core::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + handle, + FileAttributeTagInfoClass, + (&mut info as *mut FILE_ATTRIBUTE_TAG_INFO).cast(), + core::mem::size_of::() as u32, + ) + }; + if ret == 0 { + return false; + } + test_info( + info.FileAttributes, + info.ReparseTag, + disk_device, + tested_type, + ) + } else { + let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + handle, + FileBasicInfo, + (&mut info as *mut FILE_BASIC_INFO).cast(), + core::mem::size_of::() as u32, + ) + }; + if ret == 0 { + return false; + } + test_info(info.FileAttributes, 0, disk_device, tested_type) + } +} + +fn win32_xstat_attributes_from_dir( + path: &OsStr, +) -> io::Result<( + windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + u32, +)> { + use windows_sys::Win32::Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, + }; + + let wide: Vec = path.to_wide_with_nul(); + let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + unsafe { FindClose(handle) }; + + let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; + info.dwFileAttributes = find_data.dwFileAttributes; + info.ftCreationTime = find_data.ftCreationTime; + info.ftLastAccessTime = find_data.ftLastAccessTime; + info.ftLastWriteTime = find_data.ftLastWriteTime; + info.nFileSizeHigh = find_data.nFileSizeHigh; + info.nFileSizeLow = find_data.nFileSizeLow; + + let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + find_data.dwReserved0 + } else { + 0 + }; + + Ok((info, reparse_tag)) +} + +fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> io::Result { + use windows_sys::Win32::{ + Foundation::{ + ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, + ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ, + }, + Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, FILE_ID_INFO, FILE_SHARE_READ, + FILE_SHARE_WRITE, FILE_TYPE_CHAR, FILE_TYPE_PIPE, + FileAttributeTagInfo as FileAttributeTagInfoClass, FileBasicInfo, FileIdInfo, + GetFileAttributesW, GetFileInformationByHandle, + }, + }; + + let wide: Vec = path.to_wide_with_nul(); + let access = FILE_READ_ATTRIBUTES; + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !traverse { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + let mut h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + core::ptr::null(), + OPEN_EXISTING, + flags, + core::ptr::null_mut(), + ) + }; + + let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; + let mut tag_info = FileAttributeTagInfo::default(); + let mut is_unhandled_tag = false; + + if h_file == INVALID_HANDLE_VALUE { + let error = io::Error::last_os_error(); + match error.raw_os_error().unwrap_or(0) as u32 { + ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => { + let (info, reparse_tag) = win32_xstat_attributes_from_dir(path)?; + file_info = info; + tag_info.reparse_tag = reparse_tag; + + if file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && (traverse || !is_reparse_tag_name_surrogate(tag_info.reparse_tag)) + { + return Err(error); + } + } + ERROR_INVALID_PARAMETER => { + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + core::ptr::null(), + OPEN_EXISTING, + flags, + core::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + ERROR_CANT_ACCESS_FILE if traverse => { + is_unhandled_tag = true; + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + core::ptr::null(), + OPEN_EXISTING, + flags | FILE_FLAG_OPEN_REPARSE_POINT, + core::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + _ => return Err(error), + } + } + + let result = (|| -> io::Result { + if h_file != INVALID_HANDLE_VALUE { + let file_type = unsafe { GetFileType(h_file) }; + if file_type != windows_sys::Win32::Storage::FileSystem::FILE_TYPE_DISK { + if file_type == FILE_TYPE_UNKNOWN { + let err = io::Error::last_os_error(); + if err.raw_os_error().unwrap_or(0) != 0 { + return Err(err); + } + } + let file_attributes = unsafe { GetFileAttributesW(wide.as_ptr()) }; + let mut st_mode = 0; + if file_attributes != INVALID_FILE_ATTRIBUTES + && file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0 + { + st_mode = S_IFDIR_MODE; + } else if file_type == FILE_TYPE_CHAR { + st_mode = S_IFCHR_MODE; + } else if file_type == FILE_TYPE_PIPE { + st_mode = S_IFIFO_MODE; + } + return Ok(StatStruct { + st_mode, + ..Default::default() + }); + } + + if !traverse || is_unhandled_tag { + let mut local_tag_info: FileAttributeTagInfo = unsafe { core::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + h_file, + FileAttributeTagInfoClass, + (&mut local_tag_info as *mut FileAttributeTagInfo).cast(), + core::mem::size_of::() as u32, + ) + }; + if ret == 0 { + match io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32 { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + local_tag_info.file_attributes = FILE_ATTRIBUTE_NORMAL; + local_tag_info.reparse_tag = 0; + } + _ => return Err(io::Error::last_os_error()), + } + } else if local_tag_info.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + if is_reparse_tag_name_surrogate(local_tag_info.reparse_tag) { + if is_unhandled_tag { + return Err(io::Error::from_raw_os_error( + ERROR_CANT_ACCESS_FILE as i32, + )); + } + } else if !is_unhandled_tag { + unsafe { CloseHandle(h_file) }; + h_file = INVALID_HANDLE_VALUE; + return win32_xstat_slow_impl(path, true); + } + } + tag_info = local_tag_info; + } + + if unsafe { GetFileInformationByHandle(h_file, &mut file_info) } == 0 { + match io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32 { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + return Ok(StatStruct { + st_mode: 0x6000, + ..Default::default() + }); + } + _ => return Err(io::Error::last_os_error()), + } + } + + let mut basic_info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; + let has_basic_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileBasicInfo, + (&mut basic_info as *mut FILE_BASIC_INFO).cast(), + core::mem::size_of::() as u32, + ) + } != 0; + + let mut id_info: FILE_ID_INFO = unsafe { core::mem::zeroed() }; + let has_id_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileIdInfo, + (&mut id_info as *mut FILE_ID_INFO).cast(), + core::mem::size_of::() as u32, + ) + } != 0; + + let mut result = win32_attribute_data_to_stat( + &file_info, + tag_info.reparse_tag, + if has_basic_info { + Some(&basic_info) + } else { + None + }, + if has_id_info { Some(&id_info) } else { None }, + ); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } else { + let mut result = + win32_attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } + })(); + + if h_file != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(h_file) }; + } + result +} + +pub fn win32_xstat(path: &OsStr, traverse: bool) -> io::Result { + use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT}; + + match get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo) { + Ok(stat_info) => { + if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0) + || (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag)) + { + let mut result = stat_basic_info_to_stat(&stat_info); + if result.st_ino != 0 || result.st_ino_high != 0 { + result.update_st_mode_from_path(path, stat_info.FileAttributes); + result.st_ctime = result.st_birthtime; + result.st_ctime_nsec = result.st_birthtime_nsec; + return Ok(result); + } + } + } + Err(err) => { + if let Some(errno) = err.raw_os_error() + && matches!( + errno as u32, + Foundation::ERROR_FILE_NOT_FOUND + | Foundation::ERROR_PATH_NOT_FOUND + | Foundation::ERROR_NOT_READY + | Foundation::ERROR_BAD_NET_NAME + ) + { + return Err(err); + } + } + } + + let mut result = win32_xstat_slow_impl(path, traverse)?; + result.st_ctime = result.st_birthtime; + result.st_ctime_nsec = result.st_birthtime_nsec; + Ok(result) +} + +pub fn test_file_type_by_name(path: &Path, tested_type: TestType) -> bool { + match get_file_information_by_name( + path.as_os_str(), + FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, + ) { + Ok(info) => { + let disk_device = matches!( + info.DeviceType, + windows_sys::Win32::Storage::FileSystem::FILE_DEVICE_DISK + | windows_sys::Win32::System::Ioctl::FILE_DEVICE_VIRTUAL_DISK + | windows_sys::Win32::Storage::FileSystem::FILE_DEVICE_CD_ROM + ); + let result = test_info( + info.FileAttributes, + info.ReparseTag, + disk_device, + tested_type, + ); + if !result + || !matches!(tested_type, TestType::RegularFile | TestType::Directory) + || (info.FileAttributes + & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) + == 0 + { + return result; + } + } + Err(err) => { + if let Some(code) = err.raw_os_error() + && file_info_error_is_trustworthy(code as u32) + { + return false; + } + } + } + + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !matches!(tested_type, TestType::RegularFile | TestType::Directory) { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + let wide_path = path.as_os_str().to_wide_with_nul(); + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + 0, + core::ptr::null(), + OPEN_EXISTING, + flags, + core::ptr::null_mut(), + ) + }; + if handle != INVALID_HANDLE_VALUE { + let result = test_file_type_by_handle(handle, tested_type, false); + unsafe { CloseHandle(handle) }; + return result; + } + + match unsafe { GetLastError() } { + windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED + | windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION + | windows_sys::Win32::Foundation::ERROR_CANT_ACCESS_FILE + | windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER => { + let stat = win32_xstat( + path.as_os_str(), + matches!(tested_type, TestType::RegularFile | TestType::Directory), + ); + if let Ok(st) = stat { + let disk_device = (st.st_mode & libc::S_IFREG as u16) != 0; + return test_info( + st.st_file_attributes, + st.st_reparse_tag, + disk_device, + tested_type, + ); + } + } + _ => {} + } + + false +} + +pub fn test_file_exists_by_name(path: &Path, follow_links: bool) -> bool { + match get_file_information_by_name( + path.as_os_str(), + FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, + ) { + Ok(info) => { + if (info.FileAttributes + & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) + == 0 + || (!follow_links && is_reparse_tag_name_surrogate(info.ReparseTag)) + { + return true; + } + } + Err(err) => { + if let Some(code) = err.raw_os_error() + && file_info_error_is_trustworthy(code as u32) + { + return false; + } + } + } + + let wide_path = path.as_os_str().to_wide_with_nul(); + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !follow_links { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + 0, + core::ptr::null(), + OPEN_EXISTING, + flags, + core::ptr::null_mut(), + ) + }; + if handle != INVALID_HANDLE_VALUE { + if follow_links { + unsafe { CloseHandle(handle) }; + return true; + } + let is_regular_reparse_point = + test_file_type_by_handle(handle, TestType::RegularReparsePoint, false); + unsafe { CloseHandle(handle) }; + if !is_regular_reparse_point { + return true; + } + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + 0, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + core::ptr::null_mut(), + ) + }; + if handle != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(handle) }; + return true; + } + } + + match unsafe { GetLastError() } { + windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED + | windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION + | windows_sys::Win32::Foundation::ERROR_CANT_ACCESS_FILE + | windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER => { + return win32_xstat(path.as_os_str(), follow_links).is_ok(); + } + _ => {} + } + + false +} + +pub fn path_exists_via_open(path: &Path, follow_links: bool) -> bool { + let wide_path = path.as_os_str().to_wide_with_nul(); + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !follow_links { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + 0, + core::ptr::null(), + OPEN_EXISTING, + flags, + core::ptr::null_mut(), + ) + }; + if handle != INVALID_HANDLE_VALUE { + if follow_links { + unsafe { CloseHandle(handle) }; + return true; + } + let is_regular_reparse_point = + test_file_type_by_handle(handle, TestType::RegularReparsePoint, false); + unsafe { CloseHandle(handle) }; + if !is_regular_reparse_point { + return true; + } + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + 0, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + core::ptr::null_mut(), + ) + }; + if handle != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(handle) }; + return true; + } + } + false +} + +pub fn fd_exists(fd: crate::crt_fd::Borrowed<'_>) -> bool { + let handle = match crate::crt_fd::as_handle(fd) { + Ok(handle) => handle, + Err(_) => return false, + }; + let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) }; + if file_type != FILE_TYPE_UNKNOWN { + true + } else { + unsafe { GetLastError() == 0 } + } +} + +pub fn pipe() -> io::Result<(i32, i32)> { + use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; + use windows_sys::Win32::System::Pipes::CreatePipe; + + let mut attr = SECURITY_ATTRIBUTES { + nLength: core::mem::size_of::() as u32, + lpSecurityDescriptor: core::ptr::null_mut(), + bInheritHandle: 0, + }; + + let (read_handle, write_handle) = unsafe { + let mut read = core::mem::MaybeUninit::::uninit(); + let mut write = core::mem::MaybeUninit::::uninit(); + let ok = CreatePipe( + read.as_mut_ptr() as *mut _, + write.as_mut_ptr() as *mut _, + &mut attr as *mut _, + 0, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + (read.assume_init(), write.assume_init()) + }; + + const O_NOINHERIT: i32 = 0x80; + let read_fd = match crate::msvcrt::open_osfhandle(read_handle, O_NOINHERIT) { + Ok(fd) => fd, + Err(err) => { + unsafe { + CloseHandle(read_handle as _); + CloseHandle(write_handle as _); + } + return Err(err); + } + }; + let write_fd = match crate::msvcrt::open_osfhandle(write_handle, libc::O_WRONLY | O_NOINHERIT) { + Ok(fd) => fd, + Err(err) => { + let _ = unsafe { crt_fd::Owned::from_raw(read_fd) }; + unsafe { CloseHandle(write_handle as _) }; + return Err(err); + } + }; + + Ok((read_fd, write_fd)) +} + +pub fn mkdir(path: &widestring::WideCStr, mode: i32) -> io::Result<()> { + use windows_sys::Win32::Foundation::LocalFree; + use windows_sys::Win32::Security::Authorization::{ + ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, + }; + use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; + + let ok = if mode == 0o700 { + let mut sec_attr = SECURITY_ATTRIBUTES { + nLength: core::mem::size_of::() as u32, + lpSecurityDescriptor: core::ptr::null_mut(), + bInheritHandle: 0, + }; + let sddl: Vec = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0" + .encode_utf16() + .collect(); + let convert_ok = unsafe { + ConvertStringSecurityDescriptorToSecurityDescriptorW( + sddl.as_ptr(), + SDDL_REVISION_1, + &mut sec_attr.lpSecurityDescriptor, + core::ptr::null_mut(), + ) + }; + if convert_ok == 0 { + return Err(io::Error::last_os_error()); + } + let ok = unsafe { + windows_sys::Win32::Storage::FileSystem::CreateDirectoryW( + path.as_ptr(), + (&sec_attr as *const SECURITY_ATTRIBUTES).cast(), + ) + }; + unsafe { LocalFree(sec_attr.lpSecurityDescriptor) }; + ok + } else { + unsafe { + windows_sys::Win32::Storage::FileSystem::CreateDirectoryW( + path.as_ptr(), + core::ptr::null_mut(), + ) + } + }; + + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +unsafe extern "C" { + fn _umask(mask: i32) -> i32; +} + +pub fn umask(mask: i32) -> io::Result { + let result = unsafe { _umask(mask) }; + if result < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +fn set_fd_inheritable(fd: i32, inheritable: bool) -> io::Result<()> { + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; + let handle = crt_fd::as_handle(borrowed)?; + set_handle_inheritable(handle.as_raw_handle() as _, inheritable) +} + +pub fn dup(fd: i32) -> io::Result { + let fd2 = unsafe { crate::suppress_iph!(libc::dup(fd)) }; + if fd2 < 0 { + return Err(crate::os::errno_io_error()); + } + if let Err(err) = set_fd_inheritable(fd2, false) { + let _ = unsafe { crt_fd::Owned::from_raw(fd2) }; + return Err(err); + } + Ok(fd2) +} + +pub fn dup2(fd: i32, fd2: i32, inheritable: bool) -> io::Result { + let result = unsafe { crate::suppress_iph!(libc::dup2(fd, fd2)) }; + if result < 0 { + return Err(crate::os::errno_io_error()); + } + if !inheritable && let Err(err) = set_fd_inheritable(fd2, false) { + let _ = unsafe { crt_fd::Owned::from_raw(fd2) }; + return Err(err); + } + Ok(fd2) +} + +pub fn readlink(path: &Path) -> Result { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, + }; + use windows_sys::Win32::System::IO::DeviceIoControl; + use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + let wide_path = path.as_os_str().to_wide_with_nul(); + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + core::ptr::null_mut(), + ) + }; + + if handle == INVALID_HANDLE_VALUE { + return Err(ReadlinkError::Io(io::Error::last_os_error())); + } + + const BUFFER_SIZE: usize = 16384; + let mut buffer = vec![0u8; BUFFER_SIZE]; + let mut bytes_returned: u32 = 0; + let ok = unsafe { + DeviceIoControl( + handle, + FSCTL_GET_REPARSE_POINT, + core::ptr::null(), + 0, + buffer.as_mut_ptr() as *mut _, + BUFFER_SIZE as u32, + &mut bytes_returned, + core::ptr::null_mut(), + ) + }; + unsafe { CloseHandle(handle) }; + if ok == 0 { + return Err(ReadlinkError::Io(io::Error::last_os_error())); + } + + let reparse_tag = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); + let (substitute_offset, substitute_length, path_buffer_start) = + if reparse_tag == IO_REPARSE_TAG_SYMLINK { + ( + u16::from_le_bytes([buffer[8], buffer[9]]) as usize, + u16::from_le_bytes([buffer[10], buffer[11]]) as usize, + 20usize, + ) + } else if reparse_tag == IO_REPARSE_TAG_MOUNT_POINT { + ( + u16::from_le_bytes([buffer[8], buffer[9]]) as usize, + u16::from_le_bytes([buffer[10], buffer[11]]) as usize, + 16usize, + ) + } else { + return Err(ReadlinkError::NotSymbolicLink); + }; + + let path_start = path_buffer_start + substitute_offset; + let path_end = path_start + substitute_length; + if path_end > buffer.len() { + return Err(ReadlinkError::InvalidReparseData); + } + + let path_slice = &buffer[path_start..path_end]; + let mut wide_chars: Vec = path_slice + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + + if wide_chars.len() > 4 + && wide_chars[0] == b'\\' as u16 + && wide_chars[1] == b'?' as u16 + && wide_chars[2] == b'?' as u16 + && wide_chars[3] == b'\\' as u16 + { + wide_chars[1] = b'\\' as u16; + } + + Ok(OsString::from_wide(&wide_chars)) +} + +pub fn kill(pid: u32, sig: u32) -> io::Result<()> { + if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT { + let ok = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } else { + let handle = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) }; + if handle.is_null() { + return Err(io::Error::last_os_error()); + } + let ok = unsafe { Threading::TerminateProcess(handle, sig) }; + let err = if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + }; + unsafe { CloseHandle(handle) }; + err + } +} + +pub fn getfinalpathname(path: &Path) -> io::Result { + use windows_sys::Win32::Storage::FileSystem::{GetFinalPathNameByHandleW, VOLUME_NAME_DOS}; + + let wide = path.as_os_str().to_wide_with_nul(); + let handle = unsafe { + CreateFileW( + wide.as_ptr(), + 0, + 0, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let mut buffer = vec![0u16; MAX_PATH as usize]; + let result = loop { + let ret = unsafe { + GetFinalPathNameByHandleW( + handle, + buffer.as_mut_ptr(), + buffer.len() as u32, + VOLUME_NAME_DOS, + ) + }; + if ret == 0 { + break Err(io::Error::last_os_error()); + } + if ret as usize >= buffer.len() { + buffer.resize(ret as usize, 0); + continue; + } + break Ok(OsString::from_wide(&buffer[..ret as usize])); + }; + unsafe { CloseHandle(handle) }; + result +} + +pub fn getfullpathname(path: &Path) -> io::Result { + let wide = path.as_os_str().to_wide_with_nul(); + let mut buffer = vec![0u16; MAX_PATH as usize]; + let mut ret = unsafe { + windows_sys::Win32::Storage::FileSystem::GetFullPathNameW( + wide.as_ptr(), + buffer.len() as u32, + buffer.as_mut_ptr(), + core::ptr::null_mut(), + ) + }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + if ret as usize > buffer.len() { + buffer.resize(ret as usize, 0); + ret = unsafe { + windows_sys::Win32::Storage::FileSystem::GetFullPathNameW( + wide.as_ptr(), + buffer.len() as u32, + buffer.as_mut_ptr(), + core::ptr::null_mut(), + ) + }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(widestring::WideCString::from_vec_truncate(buffer).to_os_string()) +} + +pub fn getvolumepathname(path: &Path) -> io::Result { + let wide = path.as_os_str().to_wide_with_nul(); + let buflen = core::cmp::max(wide.len(), MAX_PATH as usize); + let mut buffer = vec![0u16; buflen]; + let ok = unsafe { + windows_sys::Win32::Storage::FileSystem::GetVolumePathNameW( + wide.as_ptr(), + buffer.as_mut_ptr(), + buflen as u32, + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(widestring::WideCString::from_vec_truncate(buffer).to_os_string()) + } +} + +pub fn getdiskusage(path: &Path) -> io::Result<(u64, u64)> { + use windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExW; + + let wide = path.as_os_str().to_wide_with_nul(); + let mut free_to_me = 0u64; + let mut total = 0u64; + let mut free = 0u64; + let ok = unsafe { GetDiskFreeSpaceExW(wide.as_ptr(), &mut free_to_me, &mut total, &mut free) }; + if ok != 0 { + return Ok((total, free)); + } + + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as i32) + && let Some(parent) = path.parent() + { + let parent = widestring::WideCString::from_os_str(parent).unwrap(); + let ok = + unsafe { GetDiskFreeSpaceExW(parent.as_ptr(), &mut free_to_me, &mut total, &mut free) }; + if ok != 0 { + return Ok((total, free)); + } + } + Err(err) +} + +pub fn get_handle_inheritable(handle: intptr_t) -> io::Result { + let mut flags = 0; + let ok = + unsafe { windows_sys::Win32::Foundation::GetHandleInformation(handle as _, &mut flags) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(flags & windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT != 0) + } +} + +pub fn set_handle_inheritable(handle: intptr_t, inheritable: bool) -> io::Result<()> { + let flags = if inheritable { + windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT + } else { + 0 + }; + let ok = unsafe { + windows_sys::Win32::Foundation::SetHandleInformation( + handle as _, + windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT, + flags, + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn getlogin() -> io::Result { + let mut buffer = [0u16; 257]; + let mut size = buffer.len() as u32; + let ok = unsafe { + windows_sys::Win32::System::WindowsProgramming::GetUserNameW(buffer.as_mut_ptr(), &mut size) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + Ok(OsString::from_wide(&buffer[..(size - 1) as usize]) + .to_str() + .unwrap() + .to_string()) +} + +pub fn listdrives() -> io::Result> { + let mut buffer = [0u16; 256]; + let len = unsafe { + windows_sys::Win32::Storage::FileSystem::GetLogicalDriveStringsW( + buffer.len() as u32, + buffer.as_mut_ptr(), + ) + }; + if len == 0 { + return Err(io::Error::last_os_error()); + } + if len as usize >= buffer.len() { + return Err(io::Error::from_raw_os_error( + windows_sys::Win32::Foundation::ERROR_MORE_DATA as i32, + )); + } + Ok(buffer[..(len - 1) as usize] + .split(|&c| c == 0) + .map(OsString::from_wide) + .collect()) +} + +pub fn listvolumes() -> io::Result> { + let mut result = Vec::new(); + let mut buffer = [0u16; MAX_PATH as usize + 1]; + + let find = unsafe { + windows_sys::Win32::Storage::FileSystem::FindFirstVolumeW( + buffer.as_mut_ptr(), + buffer.len() as u32, + ) + }; + if find == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + loop { + let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); + result.push(OsString::from_wide(&buffer[..len])); + + let ok = unsafe { + windows_sys::Win32::Storage::FileSystem::FindNextVolumeW( + find, + buffer.as_mut_ptr(), + buffer.len() as u32, + ) + }; + if ok == 0 { + let err = io::Error::last_os_error(); + unsafe { windows_sys::Win32::Storage::FileSystem::FindVolumeClose(find) }; + if err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES as i32) + { + break; + } + return Err(err); + } + } + + Ok(result) +} + +pub fn listmounts(volume: &Path) -> io::Result> { + let wide = volume.as_os_str().to_wide_with_nul(); + let mut buflen: u32 = MAX_PATH + 1; + let mut buffer = vec![0u16; buflen as usize]; + + loop { + let ok = unsafe { + windows_sys::Win32::Storage::FileSystem::GetVolumePathNamesForVolumeNameW( + wide.as_ptr(), + buffer.as_mut_ptr(), + buflen, + &mut buflen, + ) + }; + if ok != 0 { + break; + } + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_MORE_DATA as i32) { + buffer.resize(buflen as usize, 0); + continue; + } + return Err(err); + } + + let mut result = Vec::new(); + let mut start = 0; + for (i, &c) in buffer.iter().enumerate() { + if c == 0 { + if i > start { + result.push(OsString::from_wide(&buffer[start..i])); + } + start = i + 1; + if start < buffer.len() && buffer[start] == 0 { + break; + } + } + } + Ok(result) +} + +pub fn getppid() -> u32 { + use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION}; + + type NtQueryInformationProcessFn = unsafe extern "system" fn( + process_handle: isize, + process_information_class: u32, + process_information: *mut core::ffi::c_void, + process_information_length: u32, + return_length: *mut u32, + ) -> i32; + + let ntdll = unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(windows_sys::w!("ntdll.dll")) + }; + if ntdll.is_null() { + return 0; + } + + let func = unsafe { + windows_sys::Win32::System::LibraryLoader::GetProcAddress( + ntdll, + c"NtQueryInformationProcess".as_ptr() as *const u8, + ) + }; + let Some(func) = func else { + return 0; + }; + let nt_query: NtQueryInformationProcessFn = unsafe { core::mem::transmute(func) }; + + let mut info: PROCESS_BASIC_INFORMATION = unsafe { core::mem::zeroed() }; + let status = unsafe { + nt_query( + GetCurrentProcess() as isize, + 0, + (&mut info as *mut PROCESS_BASIC_INFORMATION).cast(), + core::mem::size_of::() as u32, + core::ptr::null_mut(), + ) + }; + + if status >= 0 + && info.InheritedFromUniqueProcessId != 0 + && info.InheritedFromUniqueProcessId < u32::MAX as usize + { + info.InheritedFromUniqueProcessId as u32 + } else { + 0 + } +} + +pub fn path_skip_root(path: *const u16) -> Option { + let mut end: *const u16 = core::ptr::null(); + let hr = unsafe { windows_sys::Win32::UI::Shell::PathCchSkipRoot(path, &mut end) }; + if hr >= 0 { + assert!(!end.is_null()); + Some( + unsafe { end.offset_from(path) } + .try_into() + .expect("len must be non-negative"), + ) + } else { + None + } +} + +pub fn get_terminal_size_handle(h: HANDLE) -> io::Result<(usize, usize)> { + let mut csbi = core::mem::MaybeUninit::uninit(); + let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) }; + if ret == 0 { + let err = unsafe { GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED { + return Err(io::Error::last_os_error()); + } + let conout: Vec = "CONOUT$\0".encode_utf16().collect(); + let console_handle = unsafe { + CreateFileW( + conout.as_ptr(), + windows_sys::Win32::Foundation::GENERIC_READ + | windows_sys::Win32::Foundation::GENERIC_WRITE, + windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ + | windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE, + core::ptr::null(), + windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING, + 0, + core::ptr::null_mut(), + ) + }; + if console_handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + let ret = unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) }; + unsafe { CloseHandle(console_handle) }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + } + let csbi = unsafe { csbi.assume_init() }; + let window = csbi.srWindow; + let columns = (window.Right - window.Left + 1) as usize; + let lines = (window.Bottom - window.Top + 1) as usize; + Ok((columns, lines)) +} + +pub fn handle_from_fd(fd: i32) -> HANDLE { + unsafe { crate::suppress_iph!(libc::get_osfhandle(fd)) as HANDLE } +} + +pub fn console_type(handle: HANDLE) -> char { + if is_invalid_handle(handle) { + return '\0'; + } + let mut mode: u32 = 0; + if unsafe { Console::GetConsoleMode(handle, &mut mode) } == 0 { + return '\0'; + } + let mut peek_count: u32 = 0; + if unsafe { Console::GetNumberOfConsoleInputEvents(handle, &mut peek_count) } != 0 { + 'r' + } else { + 'w' + } +} + +pub fn is_invalid_handle(handle: Handle) -> bool { + handle == INVALID_HANDLE_VALUE || handle.is_null() +} + +pub fn console_type_from_fd(fd: i32) -> char { + if fd < 0 { + '\0' + } else { + console_type(handle_from_fd(fd)) + } +} + +pub fn console_type_from_name(name: &str) -> char { + if name.eq_ignore_ascii_case("CONIN$") { + return 'r'; + } + if name.eq_ignore_ascii_case("CONOUT$") { + return 'w'; + } + if name.eq_ignore_ascii_case("CON") { + return 'x'; + } + + let wide: Vec = name.encode_utf16().chain(core::iter::once(0)).collect(); + let mut buf = [0u16; MAX_PATH as usize]; + let length = unsafe { + GetFullPathNameW( + wide.as_ptr(), + buf.len() as u32, + buf.as_mut_ptr(), + core::ptr::null_mut(), + ) + }; + if length == 0 || length as usize > buf.len() { + return '\0'; + } + + let full_path = &buf[..length as usize]; + let path_part = if full_path.len() >= 4 + && full_path[0] == b'\\' as u16 + && full_path[1] == b'\\' as u16 + && (full_path[2] == b'.' as u16 || full_path[2] == b'?' as u16) + && full_path[3] == b'\\' as u16 + { + &full_path[4..] + } else { + full_path + }; + + let path_str = String::from_utf16_lossy(path_part); + if path_str.eq_ignore_ascii_case("CONIN$") { + 'r' + } else if path_str.eq_ignore_ascii_case("CONOUT$") { + 'w' + } else if path_str.eq_ignore_ascii_case("CON") { + 'x' + } else { + '\0' + } +} + +fn copy_from_small_buf(buf: &mut [u8; 4], dest: &mut [u8]) -> usize { + let mut n = 0; + while buf[0] != 0 && n < dest.len() { + dest[n] = buf[0]; + n += 1; + for i in 1..buf.len() { + buf[i - 1] = buf[i]; + } + buf[buf.len() - 1] = 0; + } + n +} + +fn find_last_utf8_boundary(buf: &[u8], len: usize) -> usize { + let len = len.min(buf.len()); + for count in 1..=4.min(len) { + let c = buf[len - count]; + if c < 0x80 { + return len; + } + if c >= 0xc0 { + let expected = if c < 0xe0 { + 2 + } else if c < 0xf0 { + 3 + } else { + 4 + }; + if count < expected { + return len - count; + } + return len; + } + } + len +} + +fn wchar_to_utf8_count(data: &[u8], mut len: usize, mut n: u32) -> usize { + let mut start: usize = 0; + loop { + let mut mid = 0; + for i in (len / 2)..=len { + mid = find_last_utf8_boundary(data, i); + if mid != 0 { + break; + } + } + if mid == len { + return start + len; + } + if mid == 0 { + mid = if len > 1 { len - 1 } else { 1 }; + } + let wlen = unsafe { + MultiByteToWideChar( + CP_UTF8, + 0, + data[start..].as_ptr(), + mid as i32, + core::ptr::null_mut(), + 0, + ) + } as u32; + if wlen <= n { + start += mid; + len -= mid; + n -= wlen; + } else { + len = mid; + } + } +} + +pub fn read_console_into( + handle: HANDLE, + dest: &mut [u8], + smallbuf: &mut [u8; 4], +) -> Result { + if dest.is_empty() { + return Ok(0); + } + + let mut wlen = (dest.len() / 4) as u32; + if wlen == 0 { + wlen = 1; + } + + let mut read_len = copy_from_small_buf(smallbuf, dest); + if read_len > 0 { + wlen = wlen.saturating_sub(1); + } + if read_len >= dest.len() || wlen == 0 { + return Ok(read_len); + } + + let mut wbuf = vec![0u16; wlen as usize]; + let mut nread: u32 = 0; + if unsafe { + Console::ReadConsoleW( + handle, + wbuf.as_mut_ptr().cast(), + wlen, + &mut nread, + core::ptr::null(), + ) + } == 0 + { + return Err(ReadConsoleError::Io(io::Error::last_os_error())); + } + if nread == 0 || wbuf[0] == 0x1A { + return Ok(read_len); + } + + let remaining = dest.len() - read_len; + let u8n = if remaining < 4 { + let converted = unsafe { + WideCharToMultiByte( + CP_UTF8, + 0, + wbuf.as_ptr(), + nread as i32, + smallbuf.as_mut_ptr().cast(), + smallbuf.len() as i32, + core::ptr::null(), + core::ptr::null_mut(), + ) + }; + if converted > 0 { + copy_from_small_buf(smallbuf, &mut dest[read_len..]) as i32 + } else { + 0 + } + } else { + unsafe { + WideCharToMultiByte( + CP_UTF8, + 0, + wbuf.as_ptr(), + nread as i32, + dest[read_len..].as_mut_ptr().cast(), + remaining as i32, + core::ptr::null(), + core::ptr::null_mut(), + ) + } + }; + + if u8n > 0 { + read_len += u8n as usize; + return Ok(read_len); + } + + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER as i32) + { + let needed = unsafe { + WideCharToMultiByte( + CP_UTF8, + 0, + wbuf.as_ptr(), + nread as i32, + core::ptr::null_mut(), + 0, + core::ptr::null(), + core::ptr::null_mut(), + ) + }; + if needed > 0 { + return Err(ReadConsoleError::BufferTooSmall { + available: remaining, + required: needed as usize, + }); + } + } + Err(ReadConsoleError::Io(err)) +} + +pub fn read_console_all(handle: HANDLE, smallbuf: &mut [u8; 4]) -> io::Result> { + let mut result = Vec::new(); + let mut tmp = [0u8; 4]; + let n = copy_from_small_buf(smallbuf, &mut tmp); + result.extend_from_slice(&tmp[..n]); + + let mut wbuf = vec![0u16; 8192]; + loop { + let mut nread: u32 = 0; + if unsafe { + Console::ReadConsoleW( + handle, + wbuf.as_mut_ptr().cast(), + wbuf.len() as u32, + &mut nread, + core::ptr::null(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + if nread == 0 || wbuf[0] == 0x1A { + break; + } + + let needed = unsafe { + WideCharToMultiByte( + CP_UTF8, + 0, + wbuf.as_ptr(), + nread as i32, + core::ptr::null_mut(), + 0, + core::ptr::null(), + core::ptr::null_mut(), + ) + }; + if needed == 0 { + return Err(io::Error::last_os_error()); + } + let offset = result.len(); + result.resize(offset + needed as usize, 0); + if unsafe { + WideCharToMultiByte( + CP_UTF8, + 0, + wbuf.as_ptr(), + nread as i32, + result[offset..].as_mut_ptr().cast(), + needed, + core::ptr::null(), + core::ptr::null_mut(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + if nread < wbuf.len() as u32 { + break; + } + } + + Ok(result) +} + +pub fn write_console_utf8(handle: HANDLE, data: &[u8], max_bytes: usize) -> io::Result { + if data.is_empty() { + return Ok(0); + } + + let mut len = data.len().min(max_bytes); + let max_wlen: u32 = 32766 / 2; + len = len.min(max_wlen as usize * 3); + + let wlen = loop { + len = find_last_utf8_boundary(data, len); + let wlen = unsafe { + MultiByteToWideChar( + CP_UTF8, + 0, + data.as_ptr(), + len as i32, + core::ptr::null_mut(), + 0, + ) + }; + if wlen as u32 <= max_wlen { + break wlen; + } + len /= 2; + }; + if wlen == 0 { + return Ok(0); + } + + let mut wbuf = vec![0u16; wlen as usize]; + let wlen = unsafe { + MultiByteToWideChar( + CP_UTF8, + 0, + data.as_ptr(), + len as i32, + wbuf.as_mut_ptr(), + wlen, + ) + }; + if wlen == 0 { + return Err(io::Error::last_os_error()); + } + + let mut written: u32 = 0; + if unsafe { + Console::WriteConsoleW( + handle, + wbuf.as_ptr().cast(), + wlen as u32, + &mut written, + core::ptr::null(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + + if written < wlen as u32 { + len = wchar_to_utf8_count(data, len, written); + } + Ok(len) +} + +pub fn open_console_path_fd(path: *const u16, writable: bool) -> io::Result { + use windows_sys::Win32::{ + Foundation::{GENERIC_READ, GENERIC_WRITE}, + Storage::FileSystem::{FILE_SHARE_READ, FILE_SHARE_WRITE}, + }; + + let access = if writable { + GENERIC_WRITE + } else { + GENERIC_READ + }; + + let mut handle = unsafe { + CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + core::ptr::null(), + OPEN_EXISTING, + 0, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + handle = unsafe { + CreateFileW( + path, + access, + FILE_SHARE_READ | FILE_SHARE_WRITE, + core::ptr::null(), + OPEN_EXISTING, + 0, + core::ptr::null_mut(), + ) + }; + } + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let osf_flags = if writable { + libc::O_WRONLY | libc::O_BINARY | 0x80 + } else { + libc::O_RDONLY | libc::O_BINARY | 0x80 + }; + match crate::msvcrt::open_osfhandle(handle as isize, osf_flags) { + Ok(fd) => Ok(fd), + Err(err) => { + unsafe { CloseHandle(handle) }; + Err(err) + } + } +} + +#[cfg(target_env = "msvc")] +pub fn cwait(pid: intptr_t, opt: i32) -> io::Result<(intptr_t, i32)> { + let mut status = 0; + let pid = unsafe { crate::suppress_iph!(_cwait(&mut status, pid, opt)) }; + if pid == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok((pid, status)) + } +} + +#[cfg(target_env = "msvc")] +pub fn spawnv(mode: i32, path: *const u16, argv: *const *const u16) -> io::Result { + let result = unsafe { crate::suppress_iph!(_wspawnv(mode, path, argv)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +#[cfg(target_env = "msvc")] +pub fn spawnve( + mode: i32, + path: *const u16, + argv: *const *const u16, + envp: *const *const u16, +) -> io::Result { + let result = unsafe { crate::suppress_iph!(_wspawnve(mode, path, argv, envp)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +#[cfg(target_env = "msvc")] +pub fn execv(path: *const u16, argv: *const *const u16) -> io::Result<()> { + let result = unsafe { crate::suppress_iph!(_wexecv(path, argv)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(target_env = "msvc")] +pub fn execve( + path: *const u16, + argv: *const *const u16, + envp: *const *const u16, +) -> io::Result<()> { + let result = unsafe { crate::suppress_iph!(_wexecve(path, argv, envp)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index c6dec6bbfeb..77849895052 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -1,7 +1,15 @@ // spell-checker:disable // TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here +#[cfg(any(unix, windows, target_os = "wasi"))] +use crate::crt_fd; +#[cfg(windows)] +use crate::fs; +#[cfg(any(unix, windows))] +use core::ffi::CStr; use core::str::Utf8Error; +#[cfg(windows)] +use core::time::Duration; use std::{ env, ffi::{OsStr, OsString}, @@ -9,6 +17,16 @@ use std::{ path::PathBuf, process::ExitCode, }; +#[cfg(windows)] +use { + std::{os::windows::io::AsRawHandle, path::Path}, + windows_sys::Win32::{ + Foundation::FILETIME, + Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, INVALID_SET_FILE_POINTER, SetFilePointer, SetFileTime, + }, + }, +}; /// Convert exit code to std::process::ExitCode /// @@ -71,7 +89,32 @@ pub unsafe fn remove_var(key: impl AsRef) { } pub fn set_current_dir(path: impl AsRef) -> io::Result<()> { - env::set_current_dir(path) + env::set_current_dir(&path)?; + + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; + + if let Ok(cwd) = env::current_dir() { + let cwd_str = cwd.as_os_str(); + let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); + + let is_unc_like_path = cwd_wide.len() >= 2 + && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) + || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); + + if !is_unc_like_path { + let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; + cwd_wide.push(0); + unsafe { + SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); + } + } + } + } + + Ok(()) } #[must_use] @@ -79,10 +122,187 @@ pub fn process_id() -> u32 { std::process::id() } +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +pub fn cpu_count() -> usize { + num_cpus::get() +} + +#[cfg(not(any(not(target_arch = "wasm32"), target_os = "wasi")))] +pub fn cpu_count() -> usize { + 1 +} + +pub fn device_encoding(_fd: i32) -> Option { + #[cfg(any( + target_os = "android", + target_os = "redox", + all(target_arch = "wasm32", not(target_os = "wasi")) + ))] + { + Some("UTF-8".to_owned()) + } + + #[cfg(windows)] + { + use windows_sys::Win32::System::Console; + let cp = match _fd { + 0 => unsafe { Console::GetConsoleCP() }, + 1 | 2 => unsafe { Console::GetConsoleOutputCP() }, + _ => 0, + }; + + Some(format!("cp{cp}")) + } + + #[cfg(not(any( + target_os = "android", + target_os = "redox", + windows, + all(target_arch = "wasm32", not(target_os = "wasi")) + )))] + { + let encoding = unsafe { + let encoding = libc::nl_langinfo(libc::CODESET); + if encoding.is_null() || encoding.read() == b'\0' as libc::c_char { + "UTF-8".to_owned() + } else { + core::ffi::CStr::from_ptr(encoding) + .to_string_lossy() + .into_owned() + } + }; + + Some(encoding) + } +} + pub fn exit(code: i32) -> ! { std::process::exit(code) } +#[cfg(any(unix, windows, target_os = "wasi"))] +pub fn isatty(fd: i32) -> bool { + unsafe { suppress_iph!(libc::isatty(fd)) != 0 } +} + +#[cfg(not(any(unix, windows, target_os = "wasi")))] +pub fn isatty(_fd: i32) -> bool { + false +} + +#[cfg(any(unix, windows))] +pub fn system(command: &CStr) -> libc::c_int { + unsafe { libc::system(command.as_ptr()) } +} + +#[cfg(target_os = "linux")] +pub fn copy_file_range( + src: crt_fd::Borrowed<'_>, + offset_src: Option<&mut crt_fd::Offset>, + dst: crt_fd::Borrowed<'_>, + offset_dst: Option<&mut crt_fd::Offset>, + count: usize, +) -> io::Result { + #[allow(clippy::unnecessary_option_map_or_else)] + let p_offset_src = offset_src.map_or_else(core::ptr::null_mut, |x| x as *mut _); + #[allow(clippy::unnecessary_option_map_or_else)] + let p_offset_dst = offset_dst.map_or_else(core::ptr::null_mut, |x| x as *mut _); + + // Why not use `libc::copy_file_range`: On musl, the libc wrapper may be missing. + let ret = unsafe { + libc::syscall( + libc::SYS_copy_file_range, + src.as_raw(), + p_offset_src, + dst.as_raw(), + p_offset_dst, + count, + 0u32, + ) + }; + + usize::try_from(ret).map_err(|_| io::Error::last_os_error()) +} + +pub fn rename( + from: impl AsRef, + to: impl AsRef, +) -> io::Result<()> { + std::fs::rename(from, to) +} + +#[cfg(windows)] +pub fn seek_fd( + fd: crt_fd::Borrowed<'_>, + position: crt_fd::Offset, + how: i32, +) -> io::Result { + let handle = crt_fd::as_handle(fd)?; + let mut distance_to_move: [i32; 2] = unsafe { core::mem::transmute(position) }; + let ret = unsafe { + SetFilePointer( + handle.as_raw_handle(), + distance_to_move[0], + &mut distance_to_move[1], + how as _, + ) + }; + if ret == INVALID_SET_FILE_POINTER { + Err(io::Error::last_os_error()) + } else { + distance_to_move[0] = ret as _; + Ok(unsafe { core::mem::transmute::<[i32; 2], i64>(distance_to_move) }) + } +} + +#[cfg(any(unix, target_os = "wasi"))] +pub fn seek_fd( + fd: crt_fd::Borrowed<'_>, + position: crt_fd::Offset, + how: i32, +) -> io::Result { + let ret = unsafe { suppress_iph!(libc::lseek(fd.as_raw(), position, how)) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[cfg(windows)] +fn filetime_from_duration(duration: Duration) -> FILETIME { + let intervals = ((duration.as_secs() as i64 + 11644473600) * 10_000_000) + + (duration.subsec_nanos() as i64 / 100); + FILETIME { + dwLowDateTime: intervals as u32, + dwHighDateTime: (intervals >> 32) as u32, + } +} + +#[cfg(windows)] +pub fn set_file_times( + path: impl AsRef, + access: Duration, + modified: Duration, +) -> io::Result<()> { + let access = filetime_from_duration(access); + let modified = filetime_from_duration(modified); + let file = fs::open_write_with_custom_flags(path, FILE_FLAG_BACKUP_SEMANTICS)?; + let ret = unsafe { + SetFileTime( + file.as_raw_handle() as _, + core::ptr::null(), + &access, + &modified, + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + pub trait ErrorExt { fn posix_errno(&self) -> i32; } @@ -131,6 +351,10 @@ pub fn get_errno() -> i32 { std::io::Error::last_os_error().posix_errno() } +pub fn clear_errno() { + set_errno(0); +} + /// Set errno to the specified value. #[cfg(windows)] pub fn set_errno(value: i32) { @@ -145,6 +369,16 @@ pub fn set_errno(value: i32) { nix::errno::Errno::from_raw(value).set(); } +#[cfg(target_os = "wasi")] +pub fn set_errno(value: i32) { + unsafe { + *libc::__errno_location() = value; + } +} + +#[cfg(not(any(unix, windows, target_os = "wasi")))] +pub fn set_errno(_value: i32) {} + #[cfg(unix)] pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { use std::os::unix::ffi::OsStrExt; diff --git a/crates/host_env/src/overlapped.rs b/crates/host_env/src/overlapped.rs new file mode 100644 index 00000000000..3ee3da80170 --- /dev/null +++ b/crates/host_env/src/overlapped.rs @@ -0,0 +1,1239 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "This module exposes raw overlapped I/O wrappers over Win32 and Winsock APIs." +)] +#![allow( + clippy::too_many_arguments, + reason = "These helpers preserve the underlying Win32 and Winsock call shapes." +)] + +use alloc::sync::Arc; +use core::sync::atomic::{AtomicBool, Ordering}; +use std::{ + collections::HashMap, + io, + sync::{Mutex, OnceLock}, +}; +use windows_sys::Win32::{ + Foundation::{CloseHandle, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, HANDLE}, + Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, + System::{ + Diagnostics::Debug::{ + FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, + }, + IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED}, + Pipes::ConnectNamedPipe, + Threading::{CreateEventW, SetEvent}, + }, +}; + +pub type Handle = HANDLE; +pub type OverlappedIo = OVERLAPPED; +pub type SocketAddrRaw = SOCKADDR; +pub type SocketAddrV4 = SOCKADDR_IN; +pub type SocketAddrV6 = SOCKADDR_IN6; +pub const AF_INET_FAMILY: i32 = AF_INET as i32; +pub const AF_INET6_FAMILY: i32 = AF_INET6 as i32; +pub const INVALID_HANDLE_VALUE_ISIZE: isize = -1; +pub const SO_UPDATE_ACCEPT_CONTEXT_VALUE: i32 = + windows_sys::Win32::Networking::WinSock::SO_UPDATE_ACCEPT_CONTEXT; +pub const SO_UPDATE_CONNECT_CONTEXT_VALUE: i32 = + windows_sys::Win32::Networking::WinSock::SO_UPDATE_CONNECT_CONTEXT; +pub const TF_REUSE_SOCKET_FLAG: u32 = windows_sys::Win32::Networking::WinSock::TF_REUSE_SOCKET; + +pub struct TransferResult { + pub transferred: u32, + pub error: u32, +} + +pub struct OverlappedResult { + pub transferred: u32, + pub error: u32, +} + +pub struct Operation { + overlapped: Box, + handle: HANDLE, + pending: bool, + completed: bool, + read_buffer: Option>, + write_buffer: Option>, +} + +impl core::fmt::Debug for Operation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Operation") + .field("handle", &self.handle) + .field("pending", &self.pending) + .field("completed", &self.completed) + .finish() + } +} + +unsafe impl Sync for Operation {} +unsafe impl Send for Operation {} + +impl Operation { + pub fn new(handle: HANDLE) -> io::Result { + let event = unsafe { CreateEventW(core::ptr::null(), 1, 0, core::ptr::null()) }; + if event.is_null() { + return Err(io::Error::last_os_error()); + } + + let mut overlapped: OVERLAPPED = unsafe { core::mem::zeroed() }; + overlapped.hEvent = event; + Ok(Self { + overlapped: Box::new(overlapped), + handle, + pending: false, + completed: false, + read_buffer: None, + write_buffer: None, + }) + } + + pub fn event(&self) -> HANDLE { + self.overlapped.hEvent + } + + pub fn is_completed(&self) -> bool { + self.completed + } + + pub fn read_buffer(&self) -> Option<&[u8]> { + self.read_buffer.as_deref() + } + + pub fn get_result(&mut self, wait: bool) -> io::Result { + use windows_sys::Win32::Foundation::{ + ERROR_IO_INCOMPLETE, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, GetLastError, + }; + + let mut transferred = 0; + let ret = unsafe { + GetOverlappedResult( + self.handle, + &*self.overlapped, + &mut transferred, + i32::from(wait), + ) + }; + + let err = if ret == 0 { + unsafe { GetLastError() } + } else { + ERROR_SUCCESS + }; + + match err { + ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_OPERATION_ABORTED => { + self.completed = true; + self.pending = false; + } + ERROR_IO_INCOMPLETE => {} + _ => { + self.pending = false; + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + + if self.completed + && let Some(read_buffer) = &mut self.read_buffer + && transferred != read_buffer.len() as u32 + { + read_buffer.truncate(transferred as usize); + } + + Ok(TransferResult { + transferred, + error: err, + }) + } + + pub fn cancel(&mut self) -> io::Result<()> { + let ret = if self.pending { + unsafe { CancelIoEx(self.handle, &*self.overlapped) } + } else { + 1 + }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + self.pending = false; + Ok(()) + } + + pub fn connect_named_pipe(&mut self) -> io::Result<()> { + use windows_sys::Win32::Foundation::ERROR_PIPE_CONNECTED; + + self.completed = false; + let err = start_connect_named_pipe(self.handle, &mut *self.overlapped); + match err { + ERROR_IO_PENDING => { + self.pending = true; + } + ERROR_PIPE_CONNECTED => { + if unsafe { SetEvent(self.overlapped.hEvent) } == 0 { + return Err(io::Error::last_os_error()); + } + } + _ => return Err(io::Error::from_raw_os_error(err as i32)), + } + Ok(()) + } + + pub fn write(&mut self, buffer: &[u8]) -> io::Result { + if self.pending { + return Err(io::Error::new( + io::ErrorKind::WouldBlock, + "overlapped operation is pending", + )); + } + let len = core::cmp::min(buffer.len(), u32::MAX as usize) as u32; + self.write_buffer = Some(buffer[..len as usize].to_vec()); + let write_buf = self + .write_buffer + .as_ref() + .expect("write buffer initialized"); + self.completed = false; + let err = start_write_file(self.handle, write_buf.as_ptr(), len, &mut *self.overlapped); + + if err != ERROR_SUCCESS && err != ERROR_IO_PENDING { + return Err(io::Error::from_raw_os_error(err as i32)); + } + if err == ERROR_IO_PENDING { + self.pending = true; + } + + Ok(err) + } + + pub fn read(&mut self, size: u32) -> io::Result { + if self.pending { + return Err(io::Error::new( + io::ErrorKind::WouldBlock, + "overlapped operation is pending", + )); + } + self.read_buffer = Some(vec![0u8; size as usize]); + let read_buf = self.read_buffer.as_mut().expect("read buffer initialized"); + self.completed = false; + let err = start_read_file( + self.handle, + read_buf.as_mut_ptr(), + size, + &mut *self.overlapped, + ); + + if err != ERROR_SUCCESS && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA { + return Err(io::Error::from_raw_os_error(err as i32)); + } + if err == ERROR_IO_PENDING { + self.pending = true; + } + + Ok(err) + } +} + +impl Drop for Operation { + fn drop(&mut self) { + if self.pending { + let _ = unsafe { CancelIoEx(self.handle, &*self.overlapped) }; + let mut transferred = 0; + let _ = + unsafe { GetOverlappedResult(self.handle, &*self.overlapped, &mut transferred, 1) }; + self.pending = false; + } + if !self.overlapped.hEvent.is_null() { + unsafe { CloseHandle(self.overlapped.hEvent) }; + } + } +} + +pub struct QueuedCompletionStatus { + pub error: u32, + pub bytes_transferred: u32, + pub completion_key: usize, + pub overlapped: usize, +} + +pub struct WaitCallbackData { + completion_port: HANDLE, + overlapped: *mut OVERLAPPED, + fired: AtomicBool, +} + +struct WaitCallbackEntry { + data: Arc, + raw_ptr: usize, +} + +pub enum WaitResult { + Timeout, + Queued(QueuedCompletionStatus), +} + +pub enum SocketAddress { + V4 { + host: String, + port: u16, + }, + V6 { + host: String, + port: u16, + flowinfo: u32, + scope_id: u32, + }, +} + +static ACCEPT_EX: OnceLock = OnceLock::new(); +static CONNECT_EX: OnceLock = OnceLock::new(); +static DISCONNECT_EX: OnceLock = OnceLock::new(); +static TRANSMIT_FILE: OnceLock = OnceLock::new(); +static WAIT_CALLBACK_REGISTRY: OnceLock>> = OnceLock::new(); + +fn wait_callback_registry() -> &'static Mutex> { + WAIT_CALLBACK_REGISTRY.get_or_init(|| Mutex::new(HashMap::new())) +} + +fn winsock_extension_or_error(lock: &OnceLock) -> Result { + use windows_sys::Win32::Networking::WinSock::WSAEOPNOTSUPP; + + if let Some(func) = lock.get() { + return Ok(*func); + } + if initialize_winsock_extensions().is_ok() + && let Some(func) = lock.get() + { + return Ok(*func); + } + Err(WSAEOPNOTSUPP as u32) +} + +pub fn initialize_winsock_extensions() -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{ + INVALID_SOCKET, IPPROTO_TCP, SIO_GET_EXTENSION_FUNCTION_POINTER, SOCK_STREAM, SOCKET_ERROR, + WSAGetLastError, WSAIoctl, closesocket, socket, + }; + + const WSAID_ACCEPTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0xb5367df1, + data2: 0xcbac, + data3: 0x11cf, + data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], + }; + const WSAID_CONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0x25a207b9, + data2: 0xddf3, + data3: 0x4660, + data4: [0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e], + }; + const WSAID_DISCONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0x7fda2e11, + data2: 0x8630, + data3: 0x436f, + data4: [0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57], + }; + const WSAID_TRANSMITFILE: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0xb5367df0, + data2: 0xcbac, + data3: 0x11cf, + data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], + }; + + if ACCEPT_EX.get().is_some() + && CONNECT_EX.get().is_some() + && DISCONNECT_EX.get().is_some() + && TRANSMIT_FILE.get().is_some() + { + return Ok(()); + } + + let s = unsafe { socket(AF_INET as i32, SOCK_STREAM, IPPROTO_TCP) }; + if s == INVALID_SOCKET { + return Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )); + } + + let mut dw_bytes = 0; + + macro_rules! get_extension { + ($guid:expr, $lock:expr) => {{ + let mut func_ptr: usize = 0; + let ret = unsafe { + WSAIoctl( + s, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &$guid as *const _ as *const _, + core::mem::size_of_val(&$guid) as u32, + &mut func_ptr as *mut _ as *mut _, + core::mem::size_of::() as u32, + &mut dw_bytes, + core::ptr::null_mut(), + None, + ) + }; + if ret == SOCKET_ERROR { + let err = unsafe { WSAGetLastError() }; + unsafe { closesocket(s) }; + return Err(io::Error::from_raw_os_error(err as i32)); + } + let _ = $lock.set(func_ptr); + }}; + } + + get_extension!(WSAID_ACCEPTEX, ACCEPT_EX); + get_extension!(WSAID_CONNECTEX, CONNECT_EX); + get_extension!(WSAID_DISCONNECTEX, DISCONNECT_EX); + get_extension!(WSAID_TRANSMITFILE, TRANSMIT_FILE); + + unsafe { closesocket(s) }; + Ok(()) +} + +pub fn mark_as_completed(ov: &mut OVERLAPPED) { + ov.Internal = 0; + if !ov.hEvent.is_null() { + unsafe { + let _ = SetEvent(ov.hEvent); + } + } +} + +pub fn has_overlapped_io_completed(overlapped: &OVERLAPPED) -> bool { + overlapped.Internal != (windows_sys::Win32::Foundation::STATUS_PENDING as usize) +} + +pub fn cancel_overlapped(handle: HANDLE, overlapped: *const OVERLAPPED) -> io::Result<()> { + let ret = unsafe { CancelIoEx(handle, overlapped) }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + Ok(()) +} + +pub fn get_overlapped_result( + handle: HANDLE, + overlapped: *const OVERLAPPED, + wait: bool, +) -> OverlappedResult { + let mut transferred = 0; + let ret = unsafe { GetOverlappedResult(handle, overlapped, &mut transferred, i32::from(wait)) }; + let error = if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + }; + OverlappedResult { transferred, error } +} + +pub fn cancel_overlapped_for_drop( + handle: HANDLE, + overlapped: *const OVERLAPPED, +) -> OverlappedResult { + let cancelled = unsafe { CancelIoEx(handle, overlapped) } != 0; + get_overlapped_result(handle, overlapped, cancelled) +} + +pub fn start_read_file( + handle: HANDLE, + buffer: *mut u8, + len: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + let mut transferred = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::ReadFile( + handle, + buffer.cast(), + len, + &mut transferred, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_write_file( + handle: HANDLE, + buffer: *const u8, + len: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + let mut transferred = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::WriteFile( + handle, + buffer.cast(), + len, + &mut transferred, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_wsa_recv( + handle: usize, + buffer: *mut u8, + len: u32, + flags: *mut u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; + + let wsabuf = WSABUF { + buf: buffer.cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSARecv( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_wsa_send( + handle: usize, + buffer: *const u8, + len: u32, + flags: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASend}; + + let wsabuf = WSABUF { + buf: buffer.cast_mut().cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSASend( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_accept_ex( + listen_socket: usize, + accept_socket: usize, + buffer: *mut u8, + address_size: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type AcceptExFn = unsafe extern "system" fn( + s_listen_socket: usize, + s_accept_socket: usize, + lp_output_buffer: *mut core::ffi::c_void, + dw_receive_data_length: u32, + dw_local_address_length: u32, + dw_remote_address_length: u32, + lpdw_bytes_received: *mut u32, + lp_overlapped: *mut OVERLAPPED, + ) -> i32; + + let accept_ex = match winsock_extension_or_error(&ACCEPT_EX) { + Ok(func) => unsafe { core::mem::transmute::(func) }, + Err(err) => return err, + }; + let mut bytes_received = 0; + let ret = unsafe { + accept_ex( + listen_socket, + accept_socket, + buffer.cast(), + 0, + address_size, + address_size, + &mut bytes_received, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_connect_ex( + socket: usize, + address: *const SOCKADDR, + address_len: i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type ConnectExFn = unsafe extern "system" fn( + s: usize, + name: *const SOCKADDR, + namelen: i32, + lp_send_buffer: *const core::ffi::c_void, + dw_send_data_length: u32, + lpdw_bytes_sent: *mut u32, + lp_overlapped: *mut OVERLAPPED, + ) -> i32; + + let connect_ex = match winsock_extension_or_error(&CONNECT_EX) { + Ok(func) => unsafe { core::mem::transmute::(func) }, + Err(err) => return err, + }; + let ret = unsafe { + connect_ex( + socket, + address, + address_len, + core::ptr::null(), + 0, + core::ptr::null_mut(), + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_disconnect_ex(socket: usize, flags: u32, overlapped: *mut OVERLAPPED) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type DisconnectExFn = unsafe extern "system" fn( + s: usize, + lp_overlapped: *mut OVERLAPPED, + dw_flags: u32, + dw_reserved: u32, + ) -> i32; + + let disconnect_ex = match winsock_extension_or_error(&DISCONNECT_EX) { + Ok(func) => unsafe { core::mem::transmute::(func) }, + Err(err) => return err, + }; + let ret = unsafe { disconnect_ex(socket, overlapped, flags, 0) }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_transmit_file( + socket: usize, + file: HANDLE, + count_to_write: u32, + count_per_send: u32, + flags: u32, + offset: u32, + offset_high: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type TransmitFileFn = unsafe extern "system" fn( + h_socket: usize, + h_file: HANDLE, + n_number_of_bytes_to_write: u32, + n_number_of_bytes_per_send: u32, + lp_overlapped: *mut OVERLAPPED, + lp_transmit_buffers: *const core::ffi::c_void, + dw_reserved: u32, + ) -> i32; + + unsafe { + (*overlapped).Anonymous.Anonymous.Offset = offset; + (*overlapped).Anonymous.Anonymous.OffsetHigh = offset_high; + } + + let transmit_file = match winsock_extension_or_error(&TRANSMIT_FILE) { + Ok(func) => unsafe { core::mem::transmute::(func) }, + Err(err) => return err, + }; + let ret = unsafe { + transmit_file( + socket, + file, + count_to_write, + count_per_send, + overlapped, + core::ptr::null(), + flags, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_connect_named_pipe(pipe: HANDLE, overlapped: *mut OVERLAPPED) -> u32 { + let ret = unsafe { ConnectNamedPipe(pipe, overlapped) }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_wsa_send_to( + handle: usize, + buffer: *const u8, + len: u32, + flags: u32, + address: *const SOCKADDR, + address_len: i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASendTo}; + + let wsabuf = WSABUF { + buf: buffer.cast_mut().cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSASendTo( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + address, + address_len, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_wsa_recv_from( + handle: usize, + buffer: *mut u8, + len: u32, + flags: *mut u32, + address: *mut SOCKADDR, + address_len: *mut i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; + + let wsabuf = WSABUF { + buf: buffer.cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSARecvFrom( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + address, + address_len, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn connect_pipe(address: &str) -> io::Result { + use windows_sys::Win32::{ + Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, + Storage::FileSystem::{CreateFileW, FILE_FLAG_OVERLAPPED, OPEN_EXISTING}, + }; + + let address_wide: Vec = address.encode_utf16().chain(core::iter::once(0)).collect(); + let handle = unsafe { + CreateFileW( + address_wide.as_ptr(), + GENERIC_READ | GENERIC_WRITE, + 0, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle as isize) + } +} + +pub fn create_io_completion_port( + handle: isize, + port: isize, + key: usize, + concurrency: u32, +) -> io::Result { + let r = unsafe { + windows_sys::Win32::System::IO::CreateIoCompletionPort( + handle as HANDLE, + port as HANDLE, + key, + concurrency, + ) as isize + }; + if r == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(r) + } +} + +pub fn get_queued_completion_status(port: isize, msecs: u32) -> io::Result { + let mut bytes_transferred = 0; + let mut completion_key = 0; + let mut overlapped: *mut OVERLAPPED = core::ptr::null_mut(); + let ret = unsafe { + windows_sys::Win32::System::IO::GetQueuedCompletionStatus( + port as HANDLE, + &mut bytes_transferred, + &mut completion_key, + &mut overlapped, + msecs, + ) + }; + let err = if ret != 0 { + windows_sys::Win32::Foundation::ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + }; + if overlapped.is_null() { + if err == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + Ok(WaitResult::Timeout) + } else { + Err(io::Error::from_raw_os_error(err as i32)) + } + } else { + Ok(WaitResult::Queued(QueuedCompletionStatus { + error: err, + bytes_transferred, + completion_key, + overlapped: overlapped as usize, + })) + } +} + +pub fn post_queued_completion_status( + port: isize, + bytes: u32, + key: usize, + address: usize, +) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::IO::PostQueuedCompletionStatus( + port as HANDLE, + bytes, + key, + address as *mut OVERLAPPED, + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +unsafe impl Send for WaitCallbackData {} +unsafe impl Sync for WaitCallbackData {} + +unsafe extern "system" fn post_to_queue_callback( + parameter: *mut core::ffi::c_void, + timer_or_wait_fired: bool, +) { + let raw_ptr = parameter as *const WaitCallbackData; + let data = unsafe { Arc::from_raw(raw_ptr) }; + data.fired.store(true, Ordering::Release); + unsafe { + let _ = windows_sys::Win32::System::IO::PostQueuedCompletionStatus( + data.completion_port, + if timer_or_wait_fired { 1 } else { 0 }, + 0, + data.overlapped, + ); + } +} + +pub fn register_wait_with_queue( + object: isize, + completion_port: isize, + overlapped: usize, + timeout: u32, +) -> io::Result { + use windows_sys::Win32::System::Threading::{ + RegisterWaitForSingleObject, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE, + }; + + let data = Arc::new(WaitCallbackData { + completion_port: completion_port as HANDLE, + overlapped: overlapped as *mut OVERLAPPED, + fired: AtomicBool::new(false), + }); + let data_ptr = Arc::into_raw(data.clone()); + + let mut new_wait_object: HANDLE = core::ptr::null_mut(); + let ret = unsafe { + RegisterWaitForSingleObject( + &mut new_wait_object, + object as HANDLE, + Some(post_to_queue_callback), + data_ptr as *mut _, + timeout, + WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, + ) + }; + if ret == 0 { + unsafe { + let _ = Arc::from_raw(data_ptr); + } + return Err(io::Error::last_os_error()); + } + + let wait_handle = new_wait_object as isize; + if let Ok(mut registry) = wait_callback_registry().lock() { + registry.insert( + wait_handle, + WaitCallbackEntry { + data, + raw_ptr: data_ptr as usize, + }, + ); + } + Ok(wait_handle) +} + +fn cleanup_wait_callback_data(wait_handle: isize) { + if let Ok(mut registry) = wait_callback_registry().lock() + && let Some(entry) = registry.remove(&wait_handle) + && !entry.data.fired.load(Ordering::Acquire) + { + unsafe { + let _ = Arc::from_raw(entry.raw_ptr as *const WaitCallbackData); + } + } +} + +pub fn unregister_wait(wait_handle: isize) -> io::Result<()> { + let ret = + unsafe { windows_sys::Win32::System::Threading::UnregisterWait(wait_handle as HANDLE) }; + cleanup_wait_callback_data(wait_handle); + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn unregister_wait_ex(wait_handle: isize, event: isize) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::Threading::UnregisterWaitEx( + wait_handle as HANDLE, + event as HANDLE, + ) + }; + cleanup_wait_callback_data(wait_handle); + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn bind_local(socket: isize, family: i32) -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{ + INADDR_ANY, SOCKET_ERROR, WSAGetLastError, bind, + }; + + let ret = if family == AF_INET as i32 { + let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.S_un.S_addr = INADDR_ANY; + unsafe { + bind( + socket as _, + &addr as *const _ as *const SOCKADDR, + core::mem::size_of::() as i32, + ) + } + } else if family == AF_INET6 as i32 { + let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; + addr.sin6_family = AF_INET6; + addr.sin6_port = 0; + unsafe { + bind( + socket as _, + &addr as *const _ as *const SOCKADDR, + core::mem::size_of::() as i32, + ) + } + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "expected tuple of length 2 or 4", + )); + }; + + if ret == SOCKET_ERROR { + Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )) + } else { + Ok(()) + } +} + +pub fn parse_address_v4_wide(host_wide: &[u16], port: u16) -> io::Result<(Vec, i32)> { + use windows_sys::Win32::Networking::WinSock::{WSAGetLastError, WSAStringToAddressW}; + + let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; + addr.sin_family = AF_INET; + + let mut addr_len = core::mem::size_of::() as i32; + + let ret = unsafe { + WSAStringToAddressW( + host_wide.as_ptr(), + AF_INET as i32, + core::ptr::null(), + &mut addr as *mut _ as *mut SOCKADDR, + &mut addr_len, + ) + }; + if ret < 0 { + return Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )); + } + + // WSAStringToAddressW overwrites the port field. + addr.sin_port = port.to_be(); + + let bytes = unsafe { + core::slice::from_raw_parts( + &addr as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + Ok((bytes.to_vec(), addr_len)) +} + +pub fn parse_address_v4(host: &str, port: u16) -> io::Result<(Vec, i32)> { + let host_wide: Vec = host.encode_utf16().chain([0]).collect(); + parse_address_v4_wide(&host_wide, port) +} + +pub fn parse_address_v6( + host: &str, + port: u16, + flowinfo: u32, + scope_id: u32, +) -> io::Result<(Vec, i32)> { + let host_wide: Vec = host.encode_utf16().chain([0]).collect(); + parse_address_v6_wide(&host_wide, port, flowinfo, scope_id) +} + +pub fn parse_address_v6_wide( + host_wide: &[u16], + port: u16, + flowinfo: u32, + scope_id: u32, +) -> io::Result<(Vec, i32)> { + use windows_sys::Win32::Networking::WinSock::{WSAGetLastError, WSAStringToAddressW}; + + let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; + addr.sin6_family = AF_INET6; + + let mut addr_len = core::mem::size_of::() as i32; + + let ret = unsafe { + WSAStringToAddressW( + host_wide.as_ptr(), + AF_INET6 as i32, + core::ptr::null(), + &mut addr as *mut _ as *mut SOCKADDR, + &mut addr_len, + ) + }; + if ret < 0 { + return Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )); + } + + // WSAStringToAddressW may overwrite these fields. + addr.sin6_port = port.to_be(); + addr.sin6_flowinfo = flowinfo; + addr.Anonymous.sin6_scope_id = scope_id; + + let bytes = unsafe { + core::slice::from_raw_parts( + &addr as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + Ok((bytes.to_vec(), addr_len)) +} + +pub fn unparse_address(addr: *const SOCKADDR, addr_len: i32) -> io::Result { + use core::net::{Ipv4Addr, Ipv6Addr}; + + if addr.is_null() || addr_len < core::mem::size_of::() as i32 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "address buffer too small", + )); + } + + let family = unsafe { (*addr).sa_family }; + if family == AF_INET { + if addr_len < core::mem::size_of::() as i32 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "address buffer too small for AF_INET", + )); + } + let addr_in = unsafe { &*(addr as *const SOCKADDR_IN) }; + let ip_bytes = unsafe { addr_in.sin_addr.S_un.S_un_b }; + Ok(SocketAddress::V4 { + host: Ipv4Addr::new(ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4) + .to_string(), + port: u16::from_be(addr_in.sin_port), + }) + } else if family == AF_INET6 { + if addr_len < core::mem::size_of::() as i32 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "address buffer too small for AF_INET6", + )); + } + let addr = unsafe { &*(addr as *const SOCKADDR_IN6) }; + let ip_bytes = unsafe { addr.sin6_addr.u.Byte }; + let scope_id = unsafe { addr.Anonymous.sin6_scope_id }; + Ok(SocketAddress::V6 { + host: Ipv6Addr::from(ip_bytes).to_string(), + port: u16::from_be(addr.sin6_port), + flowinfo: u32::from_be(addr.sin6_flowinfo), + scope_id, + }) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "recvfrom returned unsupported address family", + )) + } +} + +pub fn format_message(error_code: u32) -> String { + use windows_sys::Win32::Foundation::LocalFree; + + const LANG_NEUTRAL: u32 = 0; + const SUBLANG_DEFAULT: u32 = 1; + + let mut buffer: *mut u16 = core::ptr::null_mut(); + let len = unsafe { + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + core::ptr::null(), + error_code, + (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL, + &mut buffer as *mut _ as *mut u16, + 0, + core::ptr::null(), + ) + }; + + if len == 0 || buffer.is_null() { + if !buffer.is_null() { + unsafe { LocalFree(buffer as *mut _) }; + } + return format!("unknown error code {error_code}"); + } + + let slice = unsafe { core::slice::from_raw_parts(buffer, len as usize) }; + let msg = String::from_utf16_lossy(slice).trim_end().to_string(); + unsafe { LocalFree(buffer as *mut _) }; + msg +} + +pub fn wsa_connect(socket: isize, addr_ptr: *const SOCKADDR, addr_len: i32) -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{SOCKET_ERROR, WSAConnect, WSAGetLastError}; + + let ret = unsafe { + WSAConnect( + socket as _, + addr_ptr, + addr_len, + core::ptr::null(), + core::ptr::null_mut(), + core::ptr::null(), + core::ptr::null(), + ) + }; + if ret == SOCKET_ERROR { + Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index 36f2e1df966..f4788c10937 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -1,17 +1,505 @@ -use std::os::fd::BorrowedFd; +use alloc::ffi::CString; +#[cfg(all(unix, not(target_os = "redox")))] +use alloc::vec::Vec; +use core::ffi::CStr; +#[cfg(all(unix, not(target_os = "redox")))] +use core::ptr::NonNull; +use std::ffi::{OsStr, OsString}; +#[cfg(target_os = "linux")] +use std::os::fd::FromRawFd; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd}; +use std::path::Path; + +pub struct UnameInfo { + pub sysname: String, + pub nodename: String, + pub release: String, + pub version: String, + pub machine: String, +} + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Clone, Copy, Debug)] +pub struct StatVfsInfo { + pub f_bsize: libc::c_ulong, + pub f_frsize: libc::c_ulong, + pub f_blocks: libc::fsblkcnt_t, + pub f_bfree: libc::fsblkcnt_t, + pub f_bavail: libc::fsblkcnt_t, + pub f_files: libc::fsfilcnt_t, + pub f_ffree: libc::fsfilcnt_t, + pub f_favail: libc::fsfilcnt_t, + pub f_flag: libc::c_ulong, + pub f_namemax: libc::c_ulong, + pub f_fsid: libc::c_ulong, +} + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Clone, Debug)] +pub struct RawDirEntry { + pub name: Vec, + pub d_type: Option, + pub ino: u64, +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub struct FdDirStream(NonNull); -pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub type PriorityWhichType = libc::__priority_which_t; +#[cfg(not(all(target_os = "linux", target_env = "gnu")))] +pub type PriorityWhichType = libc::c_int; + +#[cfg(target_os = "freebsd")] +pub type PriorityWhoType = i32; +#[cfg(not(target_os = "freebsd"))] +pub type PriorityWhoType = u32; + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +#[derive(Clone, Debug)] +pub enum PosixSpawnFileAction { + Open { + fd: i32, + path: CString, + oflag: i32, + mode: u32, + }, + Close { + fd: i32, + }, + Dup2 { + fd: i32, + newfd: i32, + }, +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +pub struct PosixSpawnConfig<'a> { + pub path: &'a CStr, + pub args: &'a [CString], + pub env: &'a [CString], + pub file_actions: &'a [PosixSpawnFileAction], + pub setsigdef: Option<&'a [i32]>, + pub setpgroup: Option, + pub resetids: bool, + pub setsid: bool, + pub setsigmask: Option<&'a [i32]>, + pub spawnp: bool, +} + +pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> std::io::Result<()> { use nix::fcntl; - let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); + let flags = fcntl::FdFlag::from_bits_truncate( + fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD).map_err(std::io::Error::from)?, + ); let mut new_flags = flags; new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); if flags != new_flags { - fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; + fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags)).map_err(std::io::Error::from)?; } Ok(()) } +pub fn is_session_leader() -> bool { + unsafe { libc::getsid(0) == libc::getpid() } +} + +pub fn getpid() -> libc::pid_t { + unsafe { libc::getpid() } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn dup_fd(fd: BorrowedFd<'_>) -> std::io::Result { + nix::unistd::dup(fd).map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn symlinkat(src: &CStr, dir_fd: BorrowedFd<'_>, dst: &CStr) -> std::io::Result<()> { + nix::unistd::symlinkat(src, dir_fd, dst).map_err(std::io::Error::from) +} + +#[cfg(target_os = "redox")] +pub fn symlink(src: &CStr, dst: &CStr) -> std::io::Result<()> { + let ret = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn chroot(path: &Path) -> std::io::Result<()> { + nix::unistd::chroot(path).map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn unlinkat(dir_fd: i32, path: &CStr) -> std::io::Result<()> { + let ret = unsafe { libc::unlinkat(dir_fd, path.as_ptr(), 0) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(not(windows))] +pub fn make_dir(path: &CStr, mode: u32) -> std::io::Result<()> { + let ret = unsafe { libc::mkdir(path.as_ptr(), mode as _) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(not(windows), not(target_os = "redox")))] +pub fn make_dir_at(dir_fd: i32, path: &CStr, mode: u32) -> std::io::Result<()> { + let ret = unsafe { libc::mkdirat(dir_fd, path.as_ptr(), mode as _) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn link_paths(src: &CStr, dst: &CStr, follow_symlinks: bool) -> std::io::Result<()> { + let flags = if follow_symlinks { + libc::AT_SYMLINK_FOLLOW + } else { + 0 + }; + let ret = unsafe { + libc::linkat( + libc::AT_FDCWD, + src.as_ptr(), + libc::AT_FDCWD, + dst.as_ptr(), + flags, + ) + }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(not(windows), not(target_os = "redox")))] +pub fn remove_dir_at(dir_fd: i32, path: &CStr) -> std::io::Result<()> { + let ret = unsafe { libc::unlinkat(dir_fd, path.as_ptr(), libc::AT_REMOVEDIR) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +fn statvfs_info_from_raw(st: libc::statvfs) -> StatVfsInfo { + let f_fsid = { + let ptr = core::ptr::addr_of!(st.f_fsid) as *const u8; + let size = core::mem::size_of_val(&st.f_fsid); + if size >= 8 { + let bytes = unsafe { core::slice::from_raw_parts(ptr, 8) }; + u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]) as libc::c_ulong + } else if size >= 4 { + let bytes = unsafe { core::slice::from_raw_parts(ptr, 4) }; + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as libc::c_ulong + } else { + 0 + } + }; + + StatVfsInfo { + f_bsize: st.f_bsize, + f_frsize: st.f_frsize, + f_blocks: st.f_blocks, + f_bfree: st.f_bfree, + f_bavail: st.f_bavail, + f_files: st.f_files, + f_ffree: st.f_ffree, + f_favail: st.f_favail, + f_flag: st.f_flag, + f_namemax: st.f_namemax, + f_fsid, + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn statvfs_path(path: &CStr) -> std::io::Result { + let mut st: libc::statvfs = unsafe { core::mem::zeroed() }; + let ret = unsafe { libc::statvfs(path.as_ptr(), &mut st) }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(statvfs_info_from_raw(st)) + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn statvfs_fd(fd: i32) -> std::io::Result { + let mut st: libc::statvfs = unsafe { core::mem::zeroed() }; + let ret = unsafe { libc::fstatvfs(fd, &mut st) }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(statvfs_info_from_raw(st)) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn mknod(path: &CStr, mode: libc::mode_t, device: libc::dev_t) -> std::io::Result<()> { + let ret = unsafe { libc::mknod(path.as_ptr(), mode, device) }; + if ret == 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + +#[cfg(all(not(target_os = "redox"), not(target_vendor = "apple")))] +pub fn mknodat( + dir_fd: i32, + path: &CStr, + mode: libc::mode_t, + device: libc::dev_t, +) -> std::io::Result<()> { + let ret = unsafe { libc::mknodat(dir_fd, path.as_ptr(), mode, device) }; + if ret == 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + +fn uid_from_raw(uid: u32) -> nix::unistd::Uid { + nix::unistd::Uid::from_raw(uid) +} + +fn gid_from_raw(gid: u32) -> nix::unistd::Gid { + nix::unistd::Gid::from_raw(gid) +} + +pub fn fchown(fd: BorrowedFd<'_>, uid: Option, gid: Option) -> std::io::Result<()> { + nix::unistd::fchown(fd, uid.map(uid_from_raw), gid.map(gid_from_raw)) + .map_err(std::io::Error::from) +} + +#[cfg(not(windows))] +pub fn stat_path( + path: &OsStr, + dir_fd: Option, + follow_symlinks: bool, +) -> std::io::Result> { + use crate::os::ffi::OsStrExt; + + let path = match CString::new(path.as_bytes()) { + Ok(path) => path, + Err(_) => return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)), + }; + + let mut stat = core::mem::MaybeUninit::uninit(); + #[cfg(not(target_os = "redox"))] + if let Some(dir_fd) = dir_fd { + let flags = if follow_symlinks { + 0 + } else { + libc::AT_SYMLINK_NOFOLLOW + }; + let ret = unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + return Ok(Some(unsafe { stat.assume_init() })); + } + + let ret = if follow_symlinks { + unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) } + } else { + unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) } + }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(Some(unsafe { stat.assume_init() })) + } +} + +#[cfg(not(windows))] +pub fn stat_fd(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result { + crate::fileutils::fstat(fd) +} + +#[cfg(not(target_os = "redox"))] +pub fn fchdir(fd: i32) -> std::io::Result<()> { + let ret = unsafe { libc::fchdir(fd) }; + if ret == 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + +pub fn fork() -> std::io::Result { + let pid = unsafe { libc::fork() }; + if pid == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(pid) + } +} + +pub fn write_fd(fd: BorrowedFd<'_>, buf: &[u8]) -> std::io::Result { + nix::unistd::write(fd, buf).map_err(std::io::Error::from) +} + +pub fn fchownat( + dir_fd: BorrowedFd<'_>, + path: &OsStr, + uid: Option, + gid: Option, + follow_symlinks: bool, +) -> std::io::Result<()> { + let flag = if follow_symlinks { + nix::fcntl::AtFlags::empty() + } else { + nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW + }; + nix::unistd::fchownat( + dir_fd, + path, + uid.map(uid_from_raw), + gid.map(gid_from_raw), + flag, + ) + .map_err(std::io::Error::from) +} + +pub fn uname_info() -> std::io::Result { + let info = uname::uname()?; + Ok(UnameInfo { + sysname: info.sysname, + nodename: info.nodename, + release: info.release, + version: info.version, + machine: info.machine, + }) +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "openbsd" +))] +pub fn pipe2(flags: libc::c_int) -> std::io::Result<(std::os::fd::OwnedFd, std::os::fd::OwnedFd)> { + nix::unistd::pipe2(nix::fcntl::OFlag::from_bits_truncate(flags)).map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn pipe() -> std::io::Result<(OwnedFd, OwnedFd)> { + let (rfd, wfd) = nix::unistd::pipe().map_err(std::io::Error::from)?; + set_inheritable(rfd.as_fd(), false)?; + set_inheritable(wfd.as_fd(), false)?; + Ok((rfd, wfd)) +} + +pub fn sched_yield() -> std::io::Result<()> { + nix::sched::sched_yield().map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn nice(increment: i32) -> std::io::Result { + crate::os::clear_errno(); + let res = unsafe { libc::nice(increment) }; + if res == -1 && crate::os::get_errno() != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn sched_get_priority_max(policy: i32) -> std::io::Result { + let max = unsafe { libc::sched_get_priority_max(policy) }; + if max == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(max) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn sched_get_priority_min(policy: i32) -> std::io::Result { + let min = unsafe { libc::sched_get_priority_min(policy) }; + if min == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(min) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn fchmod(fd: BorrowedFd<'_>, mode: u32) -> std::io::Result<()> { + nix::sys::stat::fchmod( + fd, + nix::sys::stat::Mode::from_bits_truncate(mode as libc::mode_t), + ) + .map_err(std::io::Error::from) +} + +#[cfg(target_os = "redox")] +pub fn utimes( + path: &Path, + acc: core::time::Duration, + modif: core::time::Duration, +) -> std::io::Result<()> { + let tv = |d: core::time::Duration| libc::timeval { + tv_sec: d.as_secs() as _, + tv_usec: d.subsec_micros() as _, + }; + nix::sys::stat::utimes(path, &tv(acc).into(), &tv(modif).into()).map_err(std::io::Error::from) +} + +#[cfg(all(any(target_os = "wasi", unix), not(target_os = "redox")))] +pub fn set_file_times_at( + dir_fd: i32, + path: &CStr, + access: core::time::Duration, + modified: core::time::Duration, + follow_symlinks: bool, +) -> std::io::Result<()> { + let ts = |d: core::time::Duration| libc::timespec { + tv_sec: d.as_secs() as _, + tv_nsec: d.subsec_nanos() as _, + }; + let times = [ts(access), ts(modified)]; + let ret = unsafe { + libc::utimensat( + dir_fd, + path.as_ptr(), + times.as_ptr(), + if follow_symlinks { + 0 + } else { + libc::AT_SYMLINK_NOFOLLOW + }, + ) + }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + #[cfg(target_os = "macos")] #[must_use] pub fn get_number_of_os_threads() -> isize { @@ -89,3 +577,1118 @@ pub fn get_number_of_os_threads() -> isize { pub fn get_number_of_os_threads() -> isize { 0 } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Permissions { + pub is_readable: bool, + pub is_writable: bool, + pub is_executable: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AccessError { + InvalidMode, + Os(i32), +} + +impl From for AccessError { + fn from(value: std::io::Error) -> Self { + Self::Os(value.raw_os_error().unwrap_or(0)) + } +} + +const F_OK: u8 = 0; +const R_OK: u8 = 4; +const W_OK: u8 = 2; +const X_OK: u8 = 1; + +fn get_permissions(mode: u32) -> Permissions { + Permissions { + is_readable: mode & 4 != 0, + is_writable: mode & 2 != 0, + is_executable: mode & 1 != 0, + } +} + +pub fn get_right_permission( + mode: u32, + file_owner: u32, + file_group: u32, +) -> std::io::Result { + let owner_mode = (mode & 0o700) >> 6; + let owner_permissions = get_permissions(owner_mode); + + let group_mode = (mode & 0o070) >> 3; + let group_permissions = get_permissions(group_mode); + + let others_mode = mode & 0o007; + let others_permissions = get_permissions(others_mode); + + let user_id = nix::unistd::getuid().as_raw(); + let groups_ids = getgroups()?; + + if file_owner == user_id { + Ok(owner_permissions) + } else if groups_ids.contains(&file_group) { + Ok(group_permissions) + } else { + Ok(others_permissions) + } +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub fn getgroups() -> std::io::Result> { + use core::ptr; + use libc::{c_int, gid_t}; + use nix::errno::Errno; + + let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; + let mut groups = + Vec::::with_capacity(Errno::result(ret).map_err(std::io::Error::from)? as usize); + let ret = unsafe { libc::getgroups(groups.capacity() as c_int, groups.as_mut_ptr()) }; + + Errno::result(ret).map_err(std::io::Error::from).map(|s| { + unsafe { groups.set_len(s as usize) }; + groups.into_iter().collect() + }) +} + +#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] +pub fn getgroups() -> std::io::Result> { + nix::unistd::getgroups() + .map(|groups| groups.into_iter().map(|gid| gid.as_raw()).collect()) + .map_err(std::io::Error::from) +} + +#[cfg(target_os = "redox")] +pub fn getgroups() -> std::io::Result> { + Err(std::io::Error::from_raw_os_error(libc::EOPNOTSUPP)) +} + +pub fn check_access(path: &Path, mode: u8) -> Result { + use std::os::unix::fs::MetadataExt; + + if mode & !(R_OK | W_OK | X_OK) != 0 { + return Err(AccessError::InvalidMode); + } + + let metadata = match crate::fs::metadata(path) { + Ok(m) => m, + Err(_) => return Ok(false), + }; + + if mode == F_OK { + return Ok(true); + } + + let perm = get_right_permission(metadata.mode(), metadata.uid(), metadata.gid())?; + + let r_ok = (mode & R_OK == 0) || perm.is_readable; + let w_ok = (mode & W_OK == 0) || perm.is_writable; + let x_ok = (mode & X_OK == 0) || perm.is_executable; + + Ok(r_ok && w_ok && x_ok) +} + +pub fn close_fds(above: i32, keep: &[BorrowedFd<'_>]) { + #[cfg(not(target_os = "redox"))] + if close_dir_fds(above, keep).is_ok() { + return; + } + #[cfg(target_os = "redox")] + if close_filetable_fds(above, keep).is_ok() { + return; + } + close_fds_brute_force(above, keep) +} + +#[allow(clippy::too_many_arguments)] +pub fn setup_child_fds( + fds_to_keep: &[BorrowedFd<'_>], + errpipe_write: BorrowedFd<'_>, + p2cread: i32, + p2cwrite: i32, + c2pread: i32, + c2pwrite: i32, + errread: i32, + errwrite: i32, + errpipe_read: i32, +) -> std::io::Result<()> { + for &fd in fds_to_keep { + if fd.as_raw_fd() != errpipe_write.as_raw_fd() { + set_inheritable(fd, true)?; + } + } + + for fd in [p2cwrite, c2pread, errread] { + if fd >= 0 { + nix::unistd::close(fd).map_err(std::io::Error::from)?; + } + } + nix::unistd::close(errpipe_read).map_err(std::io::Error::from)?; + + let c2pwrite = if c2pwrite == 0 { + let fd = unsafe { BorrowedFd::borrow_raw(c2pwrite) }; + let dup = nix::unistd::dup(fd).map_err(std::io::Error::from)?; + set_inheritable(dup.as_fd(), true)?; + dup.into_raw_fd() + } else { + c2pwrite + }; + + let mut errwrite = errwrite; + while errwrite == 0 || errwrite == 1 { + let fd = unsafe { BorrowedFd::borrow_raw(errwrite) }; + let dup = nix::unistd::dup(fd).map_err(std::io::Error::from)?; + set_inheritable(dup.as_fd(), true)?; + errwrite = dup.into_raw_fd(); + } + + dup_into_stdio(p2cread, 0)?; + dup_into_stdio(c2pwrite, 1)?; + dup_into_stdio(errwrite, 2)?; + Ok(()) +} + +fn dup_into_stdio(fd: i32, io_fd: i32) -> std::io::Result<()> { + if fd < 0 { + return Ok(()); + } + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + if fd.as_raw_fd() == io_fd { + set_inheritable(fd, true) + } else { + match io_fd { + 0 => nix::unistd::dup2_stdin(fd).map_err(std::io::Error::from), + 1 => nix::unistd::dup2_stdout(fd).map_err(std::io::Error::from), + 2 => nix::unistd::dup2_stderr(fd).map_err(std::io::Error::from), + _ => unreachable!(), + } + } +} + +pub fn chdir(cwd: &CStr) -> nix::Result<()> { + nix::unistd::chdir(cwd) +} + +pub fn set_umask(child_umask: i32) { + if child_umask >= 0 { + unsafe { libc::umask(child_umask as libc::mode_t) }; + } +} + +pub fn umask(mask: libc::mode_t) -> libc::mode_t { + unsafe { libc::umask(mask) } +} + +#[cfg(not(any(target_os = "redox", target_os = "android")))] +pub fn sync() { + unsafe { libc::sync() }; +} + +pub fn getlogin() -> Option { + let ptr = unsafe { libc::getlogin() }; + if ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ptr) }.to_owned()) + } +} + +pub fn restore_signals() { + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + libc::signal(libc::SIGXFSZ, libc::SIG_DFL); + } +} + +pub fn setsid_if_needed(call_setsid: bool) -> nix::Result<()> { + if call_setsid { + nix::unistd::setsid()?; + } + Ok(()) +} + +pub fn setpgid_if_needed(pgid_to_set: libc::pid_t) -> nix::Result<()> { + if pgid_to_set > -1 { + nix::unistd::setpgid( + nix::unistd::Pid::from_raw(0), + nix::unistd::Pid::from_raw(pgid_to_set), + )?; + } + Ok(()) +} + +pub fn setgroups_if_needed(_groups: Option<&[u32]>) -> nix::Result<()> { + #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] + if let Some(groups) = _groups { + let groups = groups.iter().copied().map(gid_from_raw).collect::>(); + nix::unistd::setgroups(&groups)?; + } + Ok(()) +} + +pub fn setregid_if_needed(gid: Option) -> nix::Result<()> { + if let Some(gid) = gid.filter(|&x| x != u32::MAX) { + let ret = unsafe { libc::setregid(gid as libc::gid_t, gid as libc::gid_t) }; + nix::Error::result(ret)?; + } + Ok(()) +} + +pub fn setreuid_if_needed(uid: Option) -> nix::Result<()> { + if let Some(uid) = uid.filter(|&x| x != u32::MAX) { + let ret = unsafe { libc::setreuid(uid as libc::uid_t, uid as libc::uid_t) }; + nix::Error::result(ret)?; + } + Ok(()) +} + +pub fn getppid() -> libc::pid_t { + nix::unistd::getppid().as_raw() +} + +pub fn getgid() -> u32 { + nix::unistd::getgid().as_raw() +} + +pub fn getegid() -> u32 { + nix::unistd::getegid().as_raw() +} + +pub fn getpgid(pid: u32) -> std::io::Result { + nix::unistd::getpgid(Some(nix::unistd::Pid::from_raw(pid as i32))) + .map(nix::unistd::Pid::as_raw) + .map_err(std::io::Error::from) +} + +pub fn getpgrp() -> libc::pid_t { + nix::unistd::getpgrp().as_raw() +} + +#[cfg(not(target_os = "redox"))] +pub fn getsid(pid: u32) -> std::io::Result { + nix::unistd::getsid(Some(nix::unistd::Pid::from_raw(pid as i32))) + .map(nix::unistd::Pid::as_raw) + .map_err(std::io::Error::from) +} + +pub fn getuid() -> u32 { + nix::unistd::getuid().as_raw() +} + +pub fn geteuid() -> u32 { + nix::unistd::geteuid().as_raw() +} + +#[cfg(not(any(target_os = "wasi", target_os = "android")))] +pub fn setgid(gid: u32) -> std::io::Result<()> { + nix::unistd::setgid(gid_from_raw(gid)).map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] +pub fn setegid(egid: u32) -> std::io::Result<()> { + nix::unistd::setegid(gid_from_raw(egid)).map_err(std::io::Error::from) +} + +pub fn setpgid(pid: u32, pgid: u32) -> std::io::Result<()> { + nix::unistd::setpgid( + nix::unistd::Pid::from_raw(pid as i32), + nix::unistd::Pid::from_raw(pgid as i32), + ) + .map_err(std::io::Error::from) +} + +pub fn setpgrp() -> std::io::Result<()> { + nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0)) + .map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn setsid() -> std::io::Result<()> { + nix::unistd::setsid() + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn tcgetpgrp(fd: BorrowedFd<'_>) -> std::io::Result { + nix::unistd::tcgetpgrp(fd) + .map(nix::unistd::Pid::as_raw) + .map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn tcsetpgrp(fd: BorrowedFd<'_>, pgid: libc::pid_t) -> std::io::Result<()> { + nix::unistd::tcsetpgrp(fd, nix::unistd::Pid::from_raw(pgid)).map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn getpriority(which: PriorityWhichType, who: PriorityWhoType) -> std::io::Result { + crate::os::clear_errno(); + let retval = unsafe { libc::getpriority(which, who) }; + if crate::os::get_errno() != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(retval) + } +} + +#[cfg(not(target_os = "redox"))] +pub fn setpriority( + which: PriorityWhichType, + who: PriorityWhoType, + priority: i32, +) -> std::io::Result<()> { + let retval = unsafe { libc::setpriority(which, who, priority) }; + if retval == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn waitpid(pid: libc::pid_t, status: &mut i32, opt: i32) -> std::io::Result { + let res = unsafe { libc::waitpid(pid, status, opt) }; + if res == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res) + } +} + +pub fn kill(pid: i32, sig: i32) -> std::io::Result<()> { + let ret = unsafe { libc::kill(pid, sig) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(not(any(target_os = "wasi", target_os = "android")))] +pub fn setuid(uid: u32) -> std::io::Result<()> { + nix::unistd::setuid(uid_from_raw(uid)).map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] +pub fn seteuid(euid: u32) -> std::io::Result<()> { + nix::unistd::seteuid(uid_from_raw(euid)).map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] +pub fn setreuid(ruid: u32, euid: u32) -> std::io::Result<()> { + let ret = unsafe { libc::setreuid(ruid as libc::uid_t, euid as libc::uid_t) }; + nix::Error::result(ret) + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +pub fn setresuid(ruid: u32, euid: u32, suid: u32) -> std::io::Result<()> { + let ret = unsafe { + libc::setresuid( + ruid as libc::uid_t, + euid as libc::uid_t, + suid as libc::uid_t, + ) + }; + nix::Error::result(ret) + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(not(target_os = "redox"))] +pub fn openpty() -> std::io::Result<(OwnedFd, OwnedFd)> { + let pty = nix::pty::openpty(None, None).map_err(std::io::Error::from)?; + set_inheritable(pty.master.as_fd(), false)?; + set_inheritable(pty.slave.as_fd(), false)?; + Ok((pty.master, pty.slave)) +} + +pub fn ttyname(fd: BorrowedFd<'_>) -> std::io::Result { + nix::unistd::ttyname(fd) + .map(std::path::PathBuf::into_os_string) + .map_err(std::io::Error::from) +} + +pub fn execv(path: &CStr, argv: &[&CStr]) -> std::io::Result<()> { + match nix::unistd::execv(path, argv) { + Ok(never) => match never {}, + Err(err) => Err(err.into()), + } +} + +pub fn execve(path: &CStr, argv: &[&CStr], env: &[&CStr]) -> std::io::Result<()> { + match nix::unistd::execve(path, argv, env) { + Ok(never) => match never {}, + Err(err) => Err(err.into()), + } +} + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] +pub fn getresuid() -> std::io::Result<(u32, u32, u32)> { + let ret = nix::unistd::getresuid().map_err(std::io::Error::from)?; + Ok(( + ret.real.as_raw(), + ret.effective.as_raw(), + ret.saved.as_raw(), + )) +} + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] +pub fn getresgid() -> std::io::Result<(u32, u32, u32)> { + let ret = nix::unistd::getresgid().map_err(std::io::Error::from)?; + Ok(( + ret.real.as_raw(), + ret.effective.as_raw(), + ret.saved.as_raw(), + )) +} + +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] +pub fn setresgid(rgid: u32, egid: u32, sgid: u32) -> std::io::Result<()> { + let ret = unsafe { + libc::setresgid( + rgid as libc::gid_t, + egid as libc::gid_t, + sgid as libc::gid_t, + ) + }; + nix::Error::result(ret) + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] +pub fn setregid(rgid: u32, egid: u32) -> std::io::Result<()> { + let ret = unsafe { libc::setregid(rgid as libc::gid_t, egid as libc::gid_t) }; + nix::Error::result(ret) + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] +pub fn initgroups(user: &CStr, gid: u32) -> std::io::Result<()> { + nix::unistd::initgroups(user, gid_from_raw(gid)).map_err(std::io::Error::from) +} + +#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] +pub fn setgroups_raw(groups: &[u32]) -> std::io::Result<()> { + let gids = groups.iter().copied().map(gid_from_raw).collect::>(); + nix::unistd::setgroups(&gids).map_err(std::io::Error::from) +} + +pub fn dup_noninheritable(fd: BorrowedFd<'_>) -> std::io::Result { + let fd = nix::unistd::dup(fd).map_err(std::io::Error::from)?; + set_inheritable(fd.as_fd(), false)?; + Ok(fd) +} + +pub fn dup2(fd: BorrowedFd<'_>, fd2: OwnedFd, inheritable: bool) -> std::io::Result { + let mut fd2 = core::mem::ManuallyDrop::new(fd2); + nix::unistd::dup2(fd, &mut fd2).map_err(std::io::Error::from)?; + let fd2 = core::mem::ManuallyDrop::into_inner(fd2); + if !inheritable { + set_inheritable(fd2.as_fd(), false)?; + } + Ok(fd2) +} + +#[cfg(all(unix, not(target_os = "redox")))] +impl FdDirStream { + pub fn from_fd(fd: BorrowedFd<'_>) -> std::io::Result { + let new_fd = dup_fd(fd)?; + let raw_fd = new_fd.into_raw_fd(); + let ptr = unsafe { libc::fdopendir(raw_fd) }; + match NonNull::new(ptr) { + Some(ptr) => Ok(Self(ptr)), + None => { + unsafe { libc::close(raw_fd) }; + Err(std::io::Error::last_os_error()) + } + } + } + + pub fn next_entry(&mut self) -> std::io::Result> { + loop { + crate::os::clear_errno(); + let ptr = unsafe { libc::readdir(self.0.as_ptr()) }; + if ptr.is_null() { + let err = crate::os::get_errno(); + return if err == 0 { + Ok(None) + } else { + Err(std::io::Error::from_raw_os_error(err)) + }; + } + + let entry = unsafe { &*ptr }; + let name = unsafe { CStr::from_ptr(entry.d_name.as_ptr()) }.to_bytes(); + if name == b"." || name == b".." { + continue; + } + #[cfg(target_os = "freebsd")] + let ino = entry.d_fileno as u64; + #[cfg(not(target_os = "freebsd"))] + let ino = entry.d_ino as u64; + + return Ok(Some(RawDirEntry { + name: name.to_vec(), + d_type: (entry.d_type != libc::DT_UNKNOWN).then_some(entry.d_type), + ino, + })); + } + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +impl Drop for FdDirStream { + fn drop(&mut self) { + unsafe { + libc::rewinddir(self.0.as_ptr()); + libc::closedir(self.0.as_ptr()); + } + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +impl core::fmt::Debug for FdDirStream { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("FdDirStream").field(&self.0).finish() + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +unsafe impl Send for FdDirStream {} +#[cfg(all(unix, not(target_os = "redox")))] +unsafe impl Sync for FdDirStream {} + +pub fn get_terminal_size(fd: libc::c_int) -> std::io::Result<(u16, u16)> { + let mut w = libc::winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + let ret = unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, &mut w) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok((w.ws_col, w.ws_row)) + } +} + +#[cfg(target_os = "macos")] +pub fn full_fsync(fd: BorrowedFd<'_>) -> std::io::Result<()> { + let ret = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_FULLFSYNC) }; + if ret < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn madvise(addr: usize, len: usize, advice: i32) -> std::io::Result<()> { + let ret = unsafe { libc::madvise(addr as *mut libc::c_void, len, advice) }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn pathconf(path: &CStr, name: i32) -> std::io::Result> { + crate::os::clear_errno(); + debug_assert_eq!(crate::os::get_errno(), 0); + let raw = unsafe { libc::pathconf(path.as_ptr(), name) }; + if raw == -1 { + if crate::os::get_errno() == 0 { + Ok(None) + } else { + Err(std::io::Error::last_os_error()) + } + } else { + Ok(Some(raw)) + } +} + +pub fn fpathconf(fd: i32, name: i32) -> std::io::Result> { + crate::os::clear_errno(); + debug_assert_eq!(crate::os::get_errno(), 0); + let raw = unsafe { libc::fpathconf(fd, name) }; + if raw == -1 { + if crate::os::get_errno() == 0 { + Ok(None) + } else { + Err(std::io::Error::last_os_error()) + } + } else { + Ok(Some(raw)) + } +} + +pub fn sysconf(name: i32) -> std::io::Result { + crate::os::set_errno(0); + let raw = unsafe { libc::sysconf(name) }; + if raw == -1 && crate::os::get_errno() != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(raw) + } +} + +#[cfg(target_os = "linux")] +/// # Safety +/// +/// `buf` must be valid for writes of `buflen` bytes. +pub unsafe fn getrandom( + buf: *mut libc::c_void, + buflen: usize, + flags: u32, +) -> std::io::Result { + let len = unsafe { libc::syscall(libc::SYS_getrandom, buf, buflen, flags as usize) as isize }; + if len < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(len as usize) + } +} + +pub fn wcoredump(status: i32) -> bool { + libc::WCOREDUMP(status) +} + +pub fn wifcontinued(status: i32) -> bool { + libc::WIFCONTINUED(status) +} + +pub fn wifstopped(status: i32) -> bool { + libc::WIFSTOPPED(status) +} + +pub fn wifsignaled(status: i32) -> bool { + libc::WIFSIGNALED(status) +} + +pub fn wifexited(status: i32) -> bool { + libc::WIFEXITED(status) +} + +pub fn wexitstatus(status: i32) -> i32 { + libc::WEXITSTATUS(status) +} + +pub fn wstopsig(status: i32) -> i32 { + libc::WSTOPSIG(status) +} + +pub fn wtermsig(status: i32) -> i32 { + libc::WTERMSIG(status) +} + +#[cfg(target_os = "linux")] +pub fn pidfd_open(pid: libc::pid_t, flags: u32) -> std::io::Result { + let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) as libc::c_long }; + if fd == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(unsafe { OwnedFd::from_raw_fd(fd as libc::c_int) }) + } +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +pub fn getgrouplist(user: &CStr, gid: u32) -> std::io::Result> { + nix::unistd::getgrouplist(user, gid_from_raw(gid)) + .map(|groups| groups.into_iter().map(|gid| gid.as_raw()).collect()) + .map_err(std::io::Error::from) +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +pub fn validate_posix_spawn_signal(sig: i32) -> bool { + nix::sys::signal::Signal::try_from(sig).is_ok() +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +pub const fn supports_posix_spawn_setsid() -> bool { + cfg!(any( + target_os = "linux", + target_os = "haiku", + target_os = "solaris", + target_os = "illumos", + target_os = "hurd", + )) +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +fn build_posix_spawn_file_actions( + actions: &[PosixSpawnFileAction], +) -> std::io::Result { + let mut file_actions = + nix::spawn::PosixSpawnFileActions::init().map_err(std::io::Error::from)?; + for action in actions { + match action { + PosixSpawnFileAction::Open { + fd, + path, + oflag, + mode, + } => file_actions + .add_open( + *fd, + path.as_c_str(), + nix::fcntl::OFlag::from_bits_retain(*oflag), + nix::sys::stat::Mode::from_bits_retain(*mode as libc::mode_t), + ) + .map_err(std::io::Error::from)?, + PosixSpawnFileAction::Close { fd } => { + file_actions.add_close(*fd).map_err(std::io::Error::from)? + } + PosixSpawnFileAction::Dup2 { fd, newfd } => file_actions + .add_dup2(*fd, *newfd) + .map_err(std::io::Error::from)?, + } + } + Ok(file_actions) +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +fn build_sigset(signals: &[i32]) -> nix::sys::signal::SigSet { + let mut set = nix::sys::signal::SigSet::empty(); + for &sig in signals { + let sig = nix::sys::signal::Signal::try_from(sig).expect("validated signal"); + set.add(sig); + } + set +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +fn build_posix_spawn_attrs( + config: &PosixSpawnConfig<'_>, +) -> std::io::Result { + let mut attrp = nix::spawn::PosixSpawnAttr::init().map_err(std::io::Error::from)?; + let mut flags = nix::spawn::PosixSpawnFlags::empty(); + + if let Some(sigs) = config.setsigdef { + let set = build_sigset(sigs); + attrp.set_sigdefault(&set).map_err(std::io::Error::from)?; + flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGDEF); + } + + if let Some(pgid) = config.setpgroup { + attrp + .set_pgroup(nix::unistd::Pid::from_raw(pgid)) + .map_err(std::io::Error::from)?; + flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETPGROUP); + } + + if config.resetids { + flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_RESETIDS); + } + + if config.setsid { + #[cfg(any( + target_os = "linux", + target_os = "haiku", + target_os = "solaris", + target_os = "illumos", + target_os = "hurd", + ))] + { + flags.insert(nix::spawn::PosixSpawnFlags::from_bits_retain( + libc::POSIX_SPAWN_SETSID, + )); + } + #[cfg(not(any( + target_os = "linux", + target_os = "haiku", + target_os = "solaris", + target_os = "illumos", + target_os = "hurd", + )))] + { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "setsid parameter is not supported on this platform", + )); + } + } + + if let Some(sigs) = config.setsigmask { + let set = build_sigset(sigs); + attrp.set_sigmask(&set).map_err(std::io::Error::from)?; + flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGMASK); + } + + if !flags.is_empty() { + attrp.set_flags(flags).map_err(std::io::Error::from)?; + } + + Ok(attrp) +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +pub fn posix_spawn(config: PosixSpawnConfig<'_>) -> std::io::Result { + let file_actions = build_posix_spawn_file_actions(config.file_actions)?; + let attrp = build_posix_spawn_attrs(&config)?; + let pid = if config.spawnp { + nix::spawn::posix_spawnp(config.path, &file_actions, &attrp, config.args, config.env) + } else { + nix::spawn::posix_spawn(config.path, &file_actions, &attrp, config.args, config.env) + } + .map_err(std::io::Error::from)?; + Ok(pid.into()) +} + +#[cfg(target_os = "linux")] +pub fn sendfile( + out_fd: BorrowedFd<'_>, + in_fd: BorrowedFd<'_>, + offset: &mut crate::crt_fd::Offset, + count: usize, +) -> std::io::Result { + nix::sys::sendfile::sendfile(out_fd, in_fd, Some(offset), count).map_err(std::io::Error::from) +} + +#[cfg(target_os = "macos")] +pub fn sendfile( + in_fd: BorrowedFd<'_>, + out_fd: BorrowedFd<'_>, + offset: crate::crt_fd::Offset, + count: i64, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]>, +) -> (std::io::Result<()>, i64) { + let (res, written) = + nix::sys::sendfile::sendfile(in_fd, out_fd, offset, Some(count), headers, trailers); + (res.map_err(std::io::Error::from), written) +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" +))] +pub fn sched_getscheduler(pid: libc::pid_t) -> std::io::Result { + let policy = unsafe { libc::sched_getscheduler(pid) }; + if policy == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(policy) + } +} + +#[cfg(all( + not(target_env = "musl"), + any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" + ) +))] +pub fn sched_setscheduler( + pid: i32, + policy: i32, + param: &libc::sched_param, +) -> std::io::Result { + let ret = unsafe { libc::sched_setscheduler(pid, policy, param) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" +))] +pub fn sched_getparam(pid: libc::pid_t) -> std::io::Result { + let mut param = core::mem::MaybeUninit::uninit(); + let ret = unsafe { libc::sched_getparam(pid, param.as_mut_ptr()) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(unsafe { param.assume_init() }) + } +} + +#[cfg(all( + not(target_env = "musl"), + any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" + ) +))] +pub fn sched_setparam(pid: i32, param: &libc::sched_param) -> std::io::Result { + let ret = unsafe { libc::sched_setparam(pid, param) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn exec_replace>( + exec_list: &[T], + argv: *const *const libc::c_char, + envp: Option<*const *const libc::c_char>, +) -> nix::errno::Errno { + let mut first_err = None; + for exec in exec_list { + if let Some(envp) = envp { + unsafe { libc::execve(exec.as_ref().as_ptr(), argv, envp) }; + } else { + unsafe { libc::execv(exec.as_ref().as_ptr(), argv) }; + } + let e = nix::errno::Errno::last(); + if e != nix::errno::Errno::ENOENT && e != nix::errno::Errno::ENOTDIR && first_err.is_none() + { + first_err = Some(e); + } + } + first_err.unwrap_or_else(nix::errno::Errno::last) +} + +fn should_keep(above: i32, keep: &[BorrowedFd<'_>], fd: i32) -> bool { + fd > above + && keep + .binary_search_by_key(&fd, BorrowedFd::as_raw_fd) + .is_err() +} + +#[cfg(not(target_os = "redox"))] +fn close_dir_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> { + use nix::{dir::Dir, fcntl::OFlag}; + use std::os::fd::AsRawFd; + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_vendor = "apple", + ))] + let fd_dir_name = c"/dev/fd"; + + #[cfg(any(target_os = "linux", target_os = "android"))] + let fd_dir_name = c"/proc/self/fd"; + + let mut dir = Dir::open( + fd_dir_name, + OFlag::O_RDONLY | OFlag::O_DIRECTORY, + nix::sys::stat::Mode::empty(), + )?; + let dirfd = dir.as_raw_fd(); + 'outer: for e in dir.iter() { + let e = e?; + let mut parser = IntParser::default(); + for &c in e.file_name().to_bytes() { + if parser.feed(c).is_err() { + continue 'outer; + } + } + let fd = parser.num; + if fd != dirfd && should_keep(above, keep, fd) { + let _ = nix::unistd::close(fd); + } + } + Ok(()) +} + +#[cfg(target_os = "redox")] +fn close_filetable_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> { + use nix::fcntl; + use std::os::fd::AsRawFd; + + let filetable = fcntl::open( + c"/scheme/thisproc/current/filetable", + fcntl::OFlag::O_RDONLY, + nix::sys::stat::Mode::empty(), + )?; + let read_one = || -> nix::Result<_> { + let mut byte = 0; + let n = nix::unistd::read(&filetable, std::slice::from_mut(&mut byte))?; + Ok((n > 0).then_some(byte)) + }; + while let Some(c) = read_one()? { + let mut parser = IntParser::default(); + if parser.feed(c).is_err() { + continue; + } + let done = loop { + let Some(c) = read_one()? else { break true }; + if parser.feed(c).is_err() { + break false; + } + }; + + let fd = parser.num; + if fd != filetable.as_raw_fd() && should_keep(above, keep, fd) { + let _ = nix::unistd::close(fd); + } + if done { + break; + } + } + Ok(()) +} + +fn close_fds_brute_force(above: i32, keep: &[BorrowedFd<'_>]) { + debug_assert!( + keep.windows(2) + .all(|fds| fds[0].as_raw_fd() <= fds[1].as_raw_fd()), + "close_fds_brute_force requires `keep` to be sorted ascending" + ); + + let max_fd = nix::unistd::sysconf(nix::unistd::SysconfVar::OPEN_MAX) + .ok() + .flatten() + .unwrap_or(256) as i32; + + let mut prev = above; + for fd in keep + .iter() + .map(BorrowedFd::as_raw_fd) + .chain(core::iter::once(max_fd)) + { + for candidate in prev + 1..fd { + unsafe { libc::close(candidate) }; + } + prev = fd; + } +} + +#[derive(Default)] +struct IntParser { + num: i32, +} + +struct NonDigit; + +impl IntParser { + fn feed(&mut self, c: u8) -> Result<(), NonDigit> { + let digit = (c as char).to_digit(10).ok_or(NonDigit)?; + self.num *= 10; + self.num += digit as i32; + Ok(()) + } +} diff --git a/crates/host_env/src/posix_wasi.rs b/crates/host_env/src/posix_wasi.rs new file mode 100644 index 00000000000..8650eaa763e --- /dev/null +++ b/crates/host_env/src/posix_wasi.rs @@ -0,0 +1,103 @@ +use alloc::ffi::CString; +use core::{ffi::CStr, time::Duration}; +use std::{ffi::OsStr, io}; + +pub fn make_dir(path: &CStr, mode: u32) -> io::Result<()> { + let ret = unsafe { libc::mkdir(path.as_ptr(), mode as _) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn make_dir_at(dir_fd: i32, path: &CStr, mode: u32) -> io::Result<()> { + let ret = unsafe { libc::mkdirat(dir_fd, path.as_ptr(), mode as _) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn remove_dir_at(dir_fd: i32, path: &CStr) -> io::Result<()> { + let ret = unsafe { libc::unlinkat(dir_fd, path.as_ptr(), libc::AT_REMOVEDIR) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn stat_path( + path: &OsStr, + dir_fd: Option, + follow_symlinks: bool, +) -> io::Result> { + use crate::os::ffi::OsStrExt; + + let path = match CString::new(path.as_bytes()) { + Ok(path) => path, + Err(_) => return Err(io::Error::from(io::ErrorKind::InvalidInput)), + }; + + let mut stat = core::mem::MaybeUninit::uninit(); + if let Some(dir_fd) = dir_fd { + let flags = if follow_symlinks { + 0 + } else { + libc::AT_SYMLINK_NOFOLLOW + }; + let ret = unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + return Ok(Some(unsafe { stat.assume_init() })); + } + + let ret = if follow_symlinks { + unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) } + } else { + unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) } + }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(Some(unsafe { stat.assume_init() })) + } +} + +pub fn stat_fd(fd: crate::crt_fd::Borrowed<'_>) -> io::Result { + crate::fileutils::fstat(fd) +} + +pub fn set_file_times_at( + dir_fd: i32, + path: &CStr, + access: Duration, + modified: Duration, + follow_symlinks: bool, +) -> io::Result<()> { + let ts = |d: Duration| libc::timespec { + tv_sec: d.as_secs() as _, + tv_nsec: d.subsec_nanos() as _, + }; + let times = [ts(access), ts(modified)]; + let ret = unsafe { + libc::utimensat( + dir_fd, + path.as_ptr(), + times.as_ptr(), + if follow_symlinks { + 0 + } else { + libc::AT_SYMLINK_NOFOLLOW + }, + ) + }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/pwd.rs b/crates/host_env/src/pwd.rs new file mode 100644 index 00000000000..db04b46b6d3 --- /dev/null +++ b/crates/host_env/src/pwd.rs @@ -0,0 +1,61 @@ +use nix::unistd::{self, User}; +use std::io; + +#[derive(Debug, Clone)] +pub struct Passwd { + pub name: String, + pub passwd: String, + pub uid: u32, + pub gid: u32, + pub gecos: String, + pub dir: String, + pub shell: String, +} + +impl From for Passwd { + fn from(user: User) -> Self { + let cstr_lossy = |s: alloc::ffi::CString| { + s.into_string() + .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) + }; + let pathbuf_lossy = |p: std::path::PathBuf| { + p.into_os_string() + .into_string() + .unwrap_or_else(|s| s.to_string_lossy().into_owned()) + }; + Self { + name: user.name, + passwd: cstr_lossy(user.passwd), + uid: user.uid.as_raw(), + gid: user.gid.as_raw(), + gecos: cstr_lossy(user.gecos), + dir: pathbuf_lossy(user.dir), + shell: pathbuf_lossy(user.shell), + } + } +} + +pub fn getpwnam(name: &str) -> Option { + User::from_name(name).ok().flatten().map(Into::into) +} + +pub fn getpwuid(uid: libc::uid_t) -> io::Result> { + User::from_uid(unistd::Uid::from_raw(uid)) + .map(|user| user.map(Into::into)) + .map_err(io::Error::from) +} + +#[cfg(not(target_os = "android"))] +pub fn getpwall() -> Vec { + static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); + let _guard = GETPWALL.lock(); + let mut list = Vec::new(); + + unsafe { libc::setpwent() }; + while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) { + list.push(User::from(unsafe { ptr.as_ref() }).into()); + } + unsafe { libc::endpwent() }; + + list +} diff --git a/crates/host_env/src/resource.rs b/crates/host_env/src/resource.rs new file mode 100644 index 00000000000..c18e41da27e --- /dev/null +++ b/crates/host_env/src/resource.rs @@ -0,0 +1,87 @@ +use std::io; + +#[derive(Debug, Clone, Copy)] +pub struct RUsage { + pub ru_utime: libc::timeval, + pub ru_stime: libc::timeval, + pub ru_maxrss: libc::c_long, + pub ru_ixrss: libc::c_long, + pub ru_idrss: libc::c_long, + pub ru_isrss: libc::c_long, + pub ru_minflt: libc::c_long, + pub ru_majflt: libc::c_long, + pub ru_nswap: libc::c_long, + pub ru_inblock: libc::c_long, + pub ru_oublock: libc::c_long, + pub ru_msgsnd: libc::c_long, + pub ru_msgrcv: libc::c_long, + pub ru_nsignals: libc::c_long, + pub ru_nvcsw: libc::c_long, + pub ru_nivcsw: libc::c_long, +} + +impl From for RUsage { + fn from(rusage: libc::rusage) -> Self { + Self { + ru_utime: rusage.ru_utime, + ru_stime: rusage.ru_stime, + ru_maxrss: rusage.ru_maxrss, + ru_ixrss: rusage.ru_ixrss, + ru_idrss: rusage.ru_idrss, + ru_isrss: rusage.ru_isrss, + ru_minflt: rusage.ru_minflt, + ru_majflt: rusage.ru_majflt, + ru_nswap: rusage.ru_nswap, + ru_inblock: rusage.ru_inblock, + ru_oublock: rusage.ru_oublock, + ru_msgsnd: rusage.ru_msgsnd, + ru_msgrcv: rusage.ru_msgrcv, + ru_nsignals: rusage.ru_nsignals, + ru_nvcsw: rusage.ru_nvcsw, + ru_nivcsw: rusage.ru_nivcsw, + } + } +} + +pub fn getrusage(who: i32) -> io::Result { + unsafe { + let mut rusage = core::mem::MaybeUninit::::uninit(); + if libc::getrusage(who, rusage.as_mut_ptr()) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(rusage.assume_init().into()) + } + } +} + +pub fn getrlimit(resource: libc::rlim_t) -> io::Result { + unsafe { + let mut rlimit = core::mem::MaybeUninit::::uninit(); + if libc::getrlimit(resource as _, rlimit.as_mut_ptr()) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(rlimit.assume_init()) + } + } +} + +pub fn setrlimit(resource: libc::rlim_t, limits: libc::rlimit) -> io::Result<()> { + unsafe { + if libc::setrlimit(resource as _, &limits) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +#[cfg(not(any(target_os = "redox", target_os = "wasi")))] +pub fn disable_core_dumps() { + let rl = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + unsafe { + let _ = libc::setrlimit(libc::RLIMIT_CORE, &rl); + } +} diff --git a/crates/host_env/src/select.rs b/crates/host_env/src/select.rs index 922bafe0697..385a6a110b2 100644 --- a/crates/host_env/src/select.rs +++ b/crates/host_env/src/select.rs @@ -3,24 +3,31 @@ use std::io; #[cfg(unix)] pub mod platform { + pub use libc::pollfd; pub use libc::{FD_ISSET, FD_SET, FD_SETSIZE, FD_ZERO, fd_set, select, timeval}; + use std::io; pub use std::os::unix::io::RawFd; #[must_use] pub const fn check_err(x: i32) -> bool { x < 0 } + + pub fn last_select_error() -> io::Error { + io::Error::last_os_error() + } } #[allow(non_snake_case)] #[cfg(windows)] pub mod platform { pub use WinSock::{FD_SET as fd_set, FD_SETSIZE, SOCKET as RawFd, TIMEVAL as timeval, select}; + use std::io; use windows_sys::Win32::Networking::WinSock; /// # Safety /// - /// Requirements forwarded from the caller. + /// `set` must be a valid mutable pointer to an initialized WinSock fd_set. pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { let mut slot = unsafe { (&raw mut (*set).fd_array).cast::() }; let fd_count = unsafe { (*set).fd_count }; @@ -40,14 +47,14 @@ pub mod platform { /// # Safety /// - /// Requirements forwarded from the caller. + /// `set` must be a valid mutable pointer to a WinSock fd_set. pub unsafe fn FD_ZERO(set: *mut fd_set) { unsafe { (*set).fd_count = 0 }; } /// # Safety /// - /// Requirements forwarded from the caller. + /// `set` must be a valid mutable pointer to an initialized WinSock fd_set. pub unsafe fn FD_ISSET(fd: RawFd, set: *mut fd_set) -> bool { use WinSock::__WSAFDIsSet; unsafe { __WSAFDIsSet(fd as _, set) != 0 } @@ -57,11 +64,16 @@ pub mod platform { pub fn check_err(x: i32) -> bool { x == WinSock::SOCKET_ERROR } + + pub fn last_select_error() -> io::Error { + io::Error::from_raw_os_error(unsafe { WinSock::WSAGetLastError() }) + } } #[cfg(target_os = "wasi")] pub mod platform { pub use libc::{FD_SETSIZE, timeval}; + use std::io; pub use std::os::fd::RawFd; pub const fn check_err(x: i32) -> bool { @@ -75,6 +87,9 @@ pub mod platform { } #[allow(non_snake_case)] + /// # Safety + /// + /// `set` must be a valid pointer to an initialized fd_set. pub unsafe fn FD_ISSET(fd: RawFd, set: *const fd_set) -> bool { let set = unsafe { &*set }; for p in &set.__fds[..set.__nfds] { @@ -86,6 +101,9 @@ pub mod platform { } #[allow(non_snake_case)] + /// # Safety + /// + /// `set` must be a valid mutable pointer to an initialized fd_set. pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { let set = unsafe { &mut *set }; for p in &set.__fds[..set.__nfds] { @@ -94,12 +112,16 @@ pub mod platform { } } let n = set.__nfds; - assert!(n < set.__fds.len(), "fd_set full"); - set.__fds[n] = fd; - set.__nfds = n + 1; + if n < FD_SETSIZE { + set.__fds[n] = fd; + set.__nfds = n + 1; + } } #[allow(non_snake_case)] + /// # Safety + /// + /// `set` must be a valid mutable pointer to an fd_set. pub unsafe fn FD_ZERO(set: *mut fd_set) { unsafe { (*set).__nfds = 0 }; } @@ -113,15 +135,21 @@ pub mod platform { timeout: *const timeval, ) -> libc::c_int; } + + pub fn last_select_error() -> io::Error { + io::Error::last_os_error() + } } pub use platform::{RawFd, timeval}; +#[cfg(unix)] +pub type PollFd = platform::pollfd; + #[repr(transparent)] pub struct FdSet(MaybeUninit); impl FdSet { - #[must_use] pub fn new() -> Self { let mut fdset = MaybeUninit::zeroed(); unsafe { platform::FD_ZERO(fdset.as_mut_ptr()) }; @@ -174,16 +202,118 @@ pub fn select( ) }; if platform::check_err(ret) { - Err(io::Error::last_os_error()) + Err(platform::last_select_error()) } else { Ok(ret) } } -#[must_use] pub fn sec_to_timeval(sec: f64) -> timeval { timeval { tv_sec: sec.trunc() as _, tv_usec: (sec.fract() * 1e6) as _, } } + +#[cfg(unix)] +#[inline] +pub fn search_poll_fd(fds: &[PollFd], fd: i32) -> Result { + fds.binary_search_by_key(&fd, |pfd| pfd.fd) +} + +#[cfg(unix)] +pub fn insert_poll_fd(fds: &mut Vec, fd: i32, events: i16) { + match search_poll_fd(fds, fd) { + Ok(i) => fds[i].events = events, + Err(i) => fds.insert( + i, + PollFd { + fd, + events, + revents: 0, + }, + ), + } +} + +#[cfg(unix)] +pub fn get_poll_fd_mut(fds: &mut [PollFd], fd: i32) -> Option<&mut PollFd> { + search_poll_fd(fds, fd).ok().map(move |i| &mut fds[i]) +} + +#[cfg(unix)] +pub fn remove_poll_fd(fds: &mut Vec, fd: i32) -> Option { + search_poll_fd(fds, fd).ok().map(|i| fds.remove(i)) +} + +#[cfg(unix)] +pub fn poll_fds(fds: &mut [PollFd], timeout: i32) -> std::io::Result { + let res = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as _, timeout) }; + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res) + } +} + +#[cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))] +pub mod epoll { + use std::os::fd::{AsFd, IntoRawFd, OwnedFd}; + + pub use rustix::event::Timespec; + pub use rustix::event::epoll::{Event, EventData, EventFlags}; + + #[derive(Debug)] + pub enum WaitError { + Interrupted, + Io(std::io::Error), + } + + pub fn create() -> std::io::Result { + rustix::event::epoll::create(rustix::event::epoll::CreateFlags::CLOEXEC).map_err(Into::into) + } + + pub fn close(fd: OwnedFd) -> nix::Result<()> { + nix::unistd::close(fd.into_raw_fd()) + } + + pub fn add(epoll: &OwnedFd, fd: F, data: u64, events: u32) -> std::io::Result<()> { + rustix::event::epoll::add( + epoll, + fd, + EventData::new_u64(data), + EventFlags::from_bits_retain(events), + ) + .map_err(Into::into) + } + + pub fn modify(epoll: &OwnedFd, fd: F, data: u64, events: u32) -> std::io::Result<()> { + rustix::event::epoll::modify( + epoll, + fd, + EventData::new_u64(data), + EventFlags::from_bits_retain(events), + ) + .map_err(Into::into) + } + + pub fn delete(epoll: &OwnedFd, fd: F) -> std::io::Result<()> { + rustix::event::epoll::delete(epoll, fd).map_err(Into::into) + } + + pub fn wait( + epoll: &OwnedFd, + events: &mut Vec, + timeout: Option<&Timespec>, + ) -> Result { + events.clear(); + match rustix::event::epoll::wait(epoll, rustix::buffer::spare_capacity(events), timeout) { + Ok(n) => { + unsafe { events.set_len(n) }; + Ok(n) + } + Err(rustix::io::Errno::INTR) => Err(WaitError::Interrupted), + Err(err) => Err(WaitError::Io(err.into())), + } + } +} diff --git a/crates/host_env/src/signal.rs b/crates/host_env/src/signal.rs index 21794cc1827..c246ecc122f 100644 --- a/crates/host_env/src/signal.rs +++ b/crates/host_env/src/signal.rs @@ -1,8 +1,17 @@ +use std::io; +#[cfg(windows)] +use std::sync::Once; + +#[cfg(any(unix, windows))] +pub use libc::sighandler_t; + +#[cfg(unix)] #[must_use] pub fn timeval_to_double(tv: &libc::timeval) -> f64 { tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) } +#[cfg(unix)] #[must_use] pub fn double_to_timeval(val: f64) -> libc::timeval { libc::timeval { @@ -11,6 +20,7 @@ pub fn double_to_timeval(val: f64) -> libc::timeval { } } +#[cfg(unix)] #[must_use] pub fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { ( @@ -18,3 +28,345 @@ pub fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { timeval_to_double(&it.it_interval), ) } + +#[cfg(all(unix, not(target_os = "redox")))] +unsafe extern "C" { + #[link_name = "siginterrupt"] + fn c_siginterrupt(sig: i32, flag: i32) -> i32; +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod ffi { + unsafe extern "C" { + pub(super) fn getitimer( + which: libc::c_int, + curr_value: *mut libc::itimerval, + ) -> libc::c_int; + pub(super) fn setitimer( + which: libc::c_int, + new_value: *const libc::itimerval, + old_value: *mut libc::itimerval, + ) -> libc::c_int; + } +} + +#[cfg(any(unix, windows))] +/// # Safety +/// +/// The caller must ensure `signalnum` is a valid platform signal number. +pub unsafe fn probe_handler(signalnum: i32) -> Option { + let handler = unsafe { libc::signal(signalnum, libc::SIG_IGN) }; + if handler == libc::SIG_ERR as sighandler_t { + None + } else { + unsafe { libc::signal(signalnum, handler) }; + Some(handler) + } +} + +#[cfg(any(unix, windows))] +/// # Safety +/// +/// The caller must ensure `signalnum` is a valid platform signal number and +/// `handler` is accepted by the platform signal ABI. +pub unsafe fn install_handler(signalnum: i32, handler: sighandler_t) -> io::Result { + let old = unsafe { libc::signal(signalnum, handler) }; + if old == libc::SIG_ERR as sighandler_t { + return Err(io::Error::last_os_error()); + } + #[cfg(all(unix, not(target_os = "redox")))] + let _ = siginterrupt(signalnum, 1); + Ok(old) +} + +#[cfg(any(unix, windows))] +pub fn raise_signal(signalnum: i32) -> io::Result<()> { + let res = unsafe { libc::raise(signalnum) }; + if res != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn alarm(seconds: u32) -> u32 { + unsafe { libc::alarm(seconds) } +} + +#[cfg(unix)] +pub fn pause() { + unsafe { libc::pause() }; +} + +#[cfg(unix)] +pub fn set_sigint_default_onstack() -> io::Result<()> { + let mut action: libc::sigaction = unsafe { core::mem::zeroed() }; + action.sa_sigaction = libc::SIG_DFL; + action.sa_flags = libc::SA_ONSTACK; + if unsafe { libc::sigemptyset(&mut action.sa_mask) } != 0 { + return Err(io::Error::last_os_error()); + } + if unsafe { libc::sigaction(libc::SIGINT, &action, core::ptr::null_mut()) } != 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +#[cfg(unix)] +pub fn send_sigint_to_self() -> io::Result<()> { + if unsafe { libc::kill(libc::getpid(), libc::SIGINT) } != 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +#[cfg(unix)] +pub fn setitimer(which: i32, new: &libc::itimerval) -> io::Result { + let mut old = core::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::setitimer(which, new, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::setitimer(which, new, old.as_mut_ptr()) }; + if ret != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { old.assume_init() }) + } +} + +#[cfg(unix)] +pub fn getitimer(which: i32) -> io::Result { + let mut old = core::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; + if ret != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { old.assume_init() }) + } +} + +#[cfg(unix)] +pub fn sigemptyset() -> io::Result { + let mut set: libc::sigset_t = unsafe { core::mem::zeroed() }; + if unsafe { libc::sigemptyset(&mut set) } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(set) + } +} + +#[cfg(unix)] +pub fn sigaddset(set: &mut libc::sigset_t, signum: i32) -> io::Result<()> { + if unsafe { libc::sigaddset(set, signum) } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn pthread_sigmask(how: i32, set: &libc::sigset_t) -> io::Result { + let mut old_mask: libc::sigset_t = unsafe { core::mem::zeroed() }; + let err = unsafe { libc::pthread_sigmask(how, set, &mut old_mask) }; + if err != 0 { + Err(io::Error::from_raw_os_error(err)) + } else { + Ok(old_mask) + } +} + +#[cfg(target_os = "linux")] +pub fn pidfd_send_signal(pidfd: i32, sig: i32, flags: u32) -> io::Result<()> { + let ret = unsafe { + libc::syscall( + libc::SYS_pidfd_send_signal, + pidfd, + sig, + core::ptr::null::(), + flags, + ) as libc::c_long + }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn siginterrupt(signalnum: i32, flag: i32) -> io::Result<()> { + let res = unsafe { c_siginterrupt(signalnum, flag) }; + if res < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub const VALID_SIGNALS: &[i32] = &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + 21, // SIGBREAK / _SIGBREAK + libc::SIGABRT, +]; + +#[cfg(windows)] +pub const SIGBREAK: i32 = 21; +#[cfg(windows)] +pub const CTRL_C_EVENT: u32 = 0; +#[cfg(windows)] +pub const CTRL_BREAK_EVENT: u32 = 1; +#[cfg(windows)] +pub const INVALID_SOCKET: libc::SOCKET = windows_sys::Win32::Networking::WinSock::INVALID_SOCKET; + +#[cfg(windows)] +pub fn is_valid_signal(signalnum: i32) -> bool { + VALID_SIGNALS.contains(&signalnum) +} + +#[cfg(windows)] +fn init_winsock() { + static WSA_INIT: Once = Once::new(); + WSA_INIT.call_once(|| unsafe { + let mut wsa_data = core::mem::MaybeUninit::uninit(); + let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr()); + }); +} + +#[cfg(windows)] +pub fn wakeup_fd_is_socket(fd: libc::SOCKET) -> io::Result { + use windows_sys::Win32::Networking::WinSock; + + init_winsock(); + let mut res = 0i32; + let mut res_size = core::mem::size_of::() as i32; + let getsockopt_res = unsafe { + WinSock::getsockopt( + fd, + WinSock::SOL_SOCKET, + WinSock::SO_ERROR, + &mut res as *mut i32 as *mut _, + &mut res_size, + ) + }; + if getsockopt_res == 0 { + return Ok(true); + } + + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { + return Err(err); + } + + let fd_i32 = + i32::try_from(fd).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid fd"))?; + let borrowed = unsafe { crate::crt_fd::Borrowed::try_borrow_raw(fd_i32) }?; + crate::fileutils::fstat(borrowed)?; + Ok(false) +} + +#[cfg(windows)] +pub fn notify_signal( + signum: i32, + wakeup_fd: libc::SOCKET, + wakeup_is_socket: bool, + sigint_event: Option, +) { + if signum == libc::SIGINT + && let Some(handle) = sigint_event + { + unsafe { + windows_sys::Win32::System::Threading::SetEvent(handle as _); + } + } + + if wakeup_fd == INVALID_SOCKET { + return; + } + + let sigbyte = signum as u8; + if wakeup_is_socket { + unsafe { + let _ = windows_sys::Win32::Networking::WinSock::send( + wakeup_fd, + &sigbyte as *const u8 as *const _, + 1, + 0, + ); + } + } else { + unsafe { + let _ = libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1); + } + } +} + +#[cfg(unix)] +pub fn notify_signal(signum: i32, wakeup_fd: i32) { + if wakeup_fd == -1 { + return; + } + let sigbyte = signum as u8; + unsafe { + let _ = libc::write(wakeup_fd, &sigbyte as *const u8 as *const _, 1); + } +} + +#[cfg(unix)] +pub fn strsignal(signalnum: i32) -> Option { + let s = unsafe { libc::strsignal(signalnum) }; + if s.is_null() { + None + } else { + let cstr = unsafe { core::ffi::CStr::from_ptr(s) }; + Some(cstr.to_string_lossy().into_owned()) + } +} + +#[cfg(windows)] +pub fn strsignal(signalnum: i32) -> Option { + let name = match signalnum { + libc::SIGINT => "Interrupt", + libc::SIGILL => "Illegal instruction", + libc::SIGFPE => "Floating-point exception", + libc::SIGSEGV => "Segmentation fault", + libc::SIGTERM => "Terminated", + 21 => "Break", + libc::SIGABRT => "Aborted", + _ => return None, + }; + Some(name.to_owned()) +} + +#[cfg(unix)] +pub fn valid_signals(max_signum: usize) -> io::Result> { + let mut mask: libc::sigset_t = unsafe { core::mem::zeroed() }; + if unsafe { libc::sigfillset(&mut mask) } != 0 { + return Err(io::Error::last_os_error()); + } + let mut signals = Vec::new(); + for signum in 1..max_signum { + if unsafe { libc::sigismember(&mask, signum as i32) } == 1 { + signals.push(signum as i32); + } + } + Ok(signals) +} + +#[cfg(unix)] +pub fn sigset_contains(mask: &libc::sigset_t, signum: i32) -> bool { + unsafe { libc::sigismember(mask, signum) == 1 } +} + +#[cfg(windows)] +pub fn valid_signals(_max_signum: usize) -> io::Result> { + Ok(VALID_SIGNALS.to_vec()) +} diff --git a/crates/host_env/src/socket.rs b/crates/host_env/src/socket.rs new file mode 100644 index 00000000000..ee8edab2d47 --- /dev/null +++ b/crates/host_env/src/socket.rs @@ -0,0 +1,870 @@ +#[cfg(unix)] +use core::ffi::CStr; +#[cfg(unix)] +use core::time::Duration; +#[cfg(unix)] +use std::os::fd::AsRawFd; +#[cfg(unix)] +use std::{io, os::fd::BorrowedFd}; + +#[cfg(unix)] +#[derive(Copy, Clone)] +pub enum PollKind { + Read, + Write, + Connect, +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn sethostname(hostname: &str) -> io::Result<()> { + nix::unistd::sethostname(hostname).map_err(io::Error::from) +} + +#[cfg(unix)] +pub fn close_socket_ignore_connreset(socket: libc::c_int) -> io::Result<()> { + let ret = unsafe { libc::close(socket) }; + if ret < 0 { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(libc::ECONNRESET) { + return Err(err); + } + } + Ok(()) +} + +#[cfg(unix)] +pub fn getsockopt_int(fd: libc::c_int, level: i32, name: i32) -> io::Result { + let mut flag: libc::c_int = 0; + let mut flagsize = core::mem::size_of::() as libc::socklen_t; + let ret = unsafe { + libc::getsockopt( + fd, + level, + name, + &mut flag as *mut libc::c_int as *mut _, + &mut flagsize, + ) + }; + if ret < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(flag) + } +} + +#[cfg(unix)] +pub fn getsockopt_bytes( + fd: libc::c_int, + level: i32, + name: i32, + buflen: usize, +) -> io::Result> { + let mut buf = vec![0u8; buflen]; + let mut optlen = buflen as libc::socklen_t; + let ret = unsafe { libc::getsockopt(fd, level, name, buf.as_mut_ptr() as *mut _, &mut optlen) }; + if ret < 0 { + Err(crate::os::errno_io_error()) + } else { + buf.truncate(optlen as usize); + Ok(buf) + } +} + +#[cfg(unix)] +pub fn setsockopt_bytes(fd: libc::c_int, level: i32, name: i32, value: &[u8]) -> io::Result<()> { + let ret = unsafe { + libc::setsockopt( + fd, + level, + name, + value.as_ptr() as *const _, + value.len() as libc::socklen_t, + ) + }; + if ret < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn setsockopt_int(fd: libc::c_int, level: i32, name: i32, value: i32) -> io::Result<()> { + let ret = unsafe { + libc::setsockopt( + fd, + level, + name, + &value as *const i32 as *const _, + core::mem::size_of::() as libc::socklen_t, + ) + }; + if ret < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn setsockopt_none(fd: libc::c_int, level: i32, name: i32, optlen: u32) -> io::Result<()> { + let ret = unsafe { + libc::setsockopt( + fd, + level, + name, + core::ptr::null(), + optlen as libc::socklen_t, + ) + }; + if ret < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn poll_socket( + fd: BorrowedFd<'_>, + kind: PollKind, + interval: Option, +) -> io::Result { + use nix::poll::{PollFd, PollFlags, PollTimeout, poll}; + + let events = match kind { + PollKind::Read => PollFlags::POLLIN, + PollKind::Write => PollFlags::POLLOUT, + PollKind::Connect => PollFlags::POLLOUT | PollFlags::POLLERR, + }; + let mut pollfd = [PollFd::new(fd, events)]; + let timeout = match interval { + Some(d) => d.try_into().unwrap_or(PollTimeout::MAX), + None => PollTimeout::NONE, + }; + poll(&mut pollfd, timeout) + .map(|ret| ret == 0) + .map_err(io::Error::from) +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +pub fn if_nameindex() -> io::Result> { + let list = nix::net::if_::if_nameindex().map_err(io::Error::from)?; + Ok(list + .to_slice() + .iter() + .map(|iface| (iface.index(), iface.name().to_string_lossy().into_owned())) + .collect()) +} + +#[cfg(unix)] +pub fn if_nametoindex_checked(name: &CStr) -> io::Result { + let ret = unsafe { libc::if_nametoindex(name.as_ptr()) }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[cfg(unix)] +pub fn if_indextoname_checked(index: u32) -> io::Result { + let mut buf = [0u8; libc::IF_NAMESIZE]; + let ret = unsafe { + libc::if_indextoname(index as libc::c_uint, buf.as_mut_ptr() as *mut libc::c_char) + }; + if ret.is_null() { + Err(io::Error::last_os_error()) + } else { + let buf = unsafe { CStr::from_ptr(buf.as_ptr() as *const libc::c_char) }; + Ok(buf.to_string_lossy().into_owned()) + } +} + +#[cfg(unix)] +pub fn gai_error_string(err: i32) -> String { + unsafe { CStr::from_ptr(libc::gai_strerror(err)) } + .to_string_lossy() + .into_owned() +} + +#[cfg(unix)] +pub fn h_error_string(err: i32) -> String { + unsafe { CStr::from_ptr(libc::hstrerror(err)) } + .to_string_lossy() + .into_owned() +} + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Debug, Clone)] +pub struct AncillaryMessage { + pub level: i32, + pub kind: i32, + pub data: Vec, +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub type SocketAddressBytes = [u8; core::mem::size_of::()]; + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Debug, Clone)] +pub struct RawSocketAddress { + pub storage: SocketAddressBytes, + pub len: usize, +} + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Debug, Clone)] +pub struct RecvMsgResult { + pub data: Vec, + pub ancdata: Vec, + pub msg_flags: i32, + pub address: Option, +} + +#[cfg(all(unix, not(target_os = "redox")))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AncillaryPackError { + ItemTooLarge, + TooMuchData, + UnexpectedNullHeader, +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn checked_cmsg_len(len: usize) -> Option { + let cmsg_len = |length| unsafe { libc::CMSG_LEN(length) }; + if len as u64 > (i32::MAX as u64 - cmsg_len(0) as u64) { + return None; + } + let res = cmsg_len(len as _) as usize; + if res > i32::MAX as usize || res < len { + return None; + } + Some(res) +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn checked_cmsg_space(len: usize) -> Option { + let cmsg_space = |length| unsafe { libc::CMSG_SPACE(length) }; + if len as u64 > (i32::MAX as u64 - cmsg_space(1) as u64) { + return None; + } + let res = cmsg_space(len as _) as usize; + if res > i32::MAX as usize || res < len { + return None; + } + Some(res) +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn pack_ancillary_messages(cmsgs: &[(i32, i32, &[u8])]) -> Result, AncillaryPackError> { + use core::{mem, ptr}; + + if cmsgs.is_empty() { + return Ok(vec![]); + } + + let capacity = cmsgs + .iter() + .map(|(_, _, buf)| buf.len()) + .try_fold(0usize, |sum, len| { + let space = checked_cmsg_space(len).ok_or(AncillaryPackError::ItemTooLarge)?; + usize::checked_add(sum, space).ok_or(AncillaryPackError::TooMuchData) + })?; + + let mut cmsg_buffer = vec![0u8; capacity]; + let mut mhdr = unsafe { mem::zeroed::() }; + mhdr.msg_control = cmsg_buffer.as_mut_ptr().cast(); + mhdr.msg_controllen = capacity as _; + + let mut pmhdr: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(&mhdr) }; + for (lvl, typ, data) in cmsgs { + if pmhdr.is_null() { + return Err(AncillaryPackError::UnexpectedNullHeader); + } + let cmsg_len = checked_cmsg_len(data.len()).ok_or(AncillaryPackError::ItemTooLarge)?; + unsafe { + (*pmhdr).cmsg_level = *lvl; + (*pmhdr).cmsg_type = *typ; + (*pmhdr).cmsg_len = cmsg_len as _; + ptr::copy_nonoverlapping(data.as_ptr(), libc::CMSG_DATA(pmhdr), data.len()); + pmhdr = libc::CMSG_NXTHDR(&mhdr, pmhdr); + } + } + + Ok(cmsg_buffer) +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn parse_ancillary_messages(control: &[u8]) -> Vec { + use core::mem; + + if control.is_empty() { + return Vec::new(); + } + + let mut msg = unsafe { mem::zeroed::() }; + msg.msg_control = control.as_ptr() as *mut _; + msg.msg_controllen = control.len() as _; + + let ctrl_buf = msg.msg_control as *const u8; + let ctrl_end = unsafe { ctrl_buf.add(msg.msg_controllen as _) }; + + let mut result = Vec::new(); + let mut cmsg: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(&msg) }; + while !cmsg.is_null() { + let cmsg_ref = unsafe { &*cmsg }; + let data_ptr = unsafe { libc::CMSG_DATA(cmsg) }; + let data_len_from_cmsg = cmsg_ref.cmsg_len as usize - (data_ptr as usize - cmsg as usize); + let available = ctrl_end as usize - data_ptr as usize; + let data_len = data_len_from_cmsg.min(available); + let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) }; + result.push(AncillaryMessage { + level: cmsg_ref.cmsg_level, + kind: cmsg_ref.cmsg_type, + data: data.to_vec(), + }); + cmsg = unsafe { libc::CMSG_NXTHDR(&msg, cmsg) }; + } + + result +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn recvmsg( + fd: BorrowedFd<'_>, + bufsize: usize, + ancbufsize: usize, + flags: i32, +) -> io::Result { + use core::mem::MaybeUninit; + + let mut data_buf: Vec> = vec![MaybeUninit::uninit(); bufsize]; + let mut anc_buf: Vec> = vec![MaybeUninit::uninit(); ancbufsize]; + let mut addr_storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; + + let mut iov = [libc::iovec { + iov_base: data_buf.as_mut_ptr().cast(), + iov_len: bufsize, + }]; + + let mut msg: libc::msghdr = unsafe { core::mem::zeroed() }; + msg.msg_name = (&mut addr_storage as *mut libc::sockaddr_storage).cast(); + msg.msg_namelen = core::mem::size_of::() as libc::socklen_t; + msg.msg_iov = iov.as_mut_ptr(); + msg.msg_iovlen = 1; + if ancbufsize > 0 { + msg.msg_control = anc_buf.as_mut_ptr().cast(); + msg.msg_controllen = ancbufsize as _; + } + + let ret = unsafe { libc::recvmsg(fd.as_raw_fd(), &mut msg, flags) }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + + let data = unsafe { + data_buf.set_len(ret as usize); + core::mem::transmute::>, Vec>(data_buf) + }; + let control = unsafe { + core::slice::from_raw_parts(anc_buf.as_ptr().cast::(), msg.msg_controllen as usize) + }; + let ancdata = parse_ancillary_messages(control); + let address = if msg.msg_namelen > 0 { + let storage = unsafe { + core::mem::transmute::(addr_storage) + }; + Some(RawSocketAddress { + storage, + len: msg.msg_namelen as usize, + }) + } else { + None + }; + + Ok(RecvMsgResult { + data, + ancdata, + msg_flags: msg.msg_flags, + address, + }) +} + +#[cfg(target_os = "linux")] +pub fn sendmsg_afalg( + fd: BorrowedFd<'_>, + buffers: &[io::IoSlice<'_>], + op: u32, + iv: Option<&[u8]>, + assoclen: Option, + flags: i32, +) -> io::Result { + let mut control_buf = Vec::new(); + + { + let op_bytes = op.to_ne_bytes(); + let space = unsafe { libc::CMSG_SPACE(core::mem::size_of::() as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(core::mem::size_of::() as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_OP; + let data = libc::CMSG_DATA(cmsg); + core::ptr::copy_nonoverlapping(op_bytes.as_ptr(), data, op_bytes.len()); + } + } + + if let Some(iv_bytes) = iv { + let iv_struct_size = 4 + iv_bytes.len(); + let space = unsafe { libc::CMSG_SPACE(iv_struct_size as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(iv_struct_size as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_IV; + let data = libc::CMSG_DATA(cmsg); + let ivlen = (iv_bytes.len() as u32).to_ne_bytes(); + core::ptr::copy_nonoverlapping(ivlen.as_ptr(), data, 4); + core::ptr::copy_nonoverlapping(iv_bytes.as_ptr(), data.add(4), iv_bytes.len()); + } + } + + if let Some(assoclen_val) = assoclen { + let assoclen_bytes = assoclen_val.to_ne_bytes(); + let space = unsafe { libc::CMSG_SPACE(core::mem::size_of::() as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(core::mem::size_of::() as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_AEAD_ASSOCLEN; + let data = libc::CMSG_DATA(cmsg); + core::ptr::copy_nonoverlapping(assoclen_bytes.as_ptr(), data, assoclen_bytes.len()); + } + } + + let iovecs: Vec = buffers + .iter() + .map(|buf| libc::iovec { + iov_base: buf.as_ptr() as *mut _, + iov_len: buf.len(), + }) + .collect(); + + let mut msghdr: libc::msghdr = unsafe { core::mem::zeroed() }; + msghdr.msg_iov = iovecs.as_ptr() as *mut _; + msghdr.msg_iovlen = iovecs.len() as _; + if !control_buf.is_empty() { + msghdr.msg_control = control_buf.as_mut_ptr() as *mut _; + msghdr.msg_controllen = control_buf.len() as _; + } + + let ret = unsafe { libc::sendmsg(fd.as_raw_fd(), &msghdr, flags) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret as usize) + } +} + +#[cfg(windows)] +use core::{ffi::CStr, ptr::NonNull}; +#[cfg(windows)] +use std::io; +#[cfg(windows)] +use windows_sys::Win32::{ + NetworkManagement::{ + IpHelper::{ + ConvertInterfaceLuidToNameW, FreeMibTable, GetIfTable2Ex, MIB_IF_ROW2, MIB_IF_TABLE2, + MibIfTableRaw, if_indextoname, if_nametoindex, + }, + Ndis::{IF_MAX_STRING_SIZE, NET_LUID_LH}, + }, + Networking::WinSock::{ + FROM_PROTOCOL_INFO, INVALID_SOCKET, SOCKET, SOCKET_ERROR, WSA_FLAG_OVERLAPPED, + WSADuplicateSocketW, WSAGetLastError, WSAIoctl, WSAPROTOCOL_INFOW, WSASocketW, + }, +}; + +#[cfg(windows)] +pub use windows_sys::Win32::Networking::WinSock::{ + AF_APPLETALK, AF_DECnet, AF_IPX, AF_LINK, AI_ADDRCONFIG, AI_ALL, AI_CANONNAME, AI_NUMERICSERV, + AI_V4MAPPED, INADDR_ANY, INADDR_BROADCAST, INADDR_LOOPBACK, INADDR_NONE, IP_ADD_MEMBERSHIP, + IP_DROP_MEMBERSHIP, IP_HDRINCL, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, + IP_OPTIONS, IP_RECVDSTADDR, IP_TOS, IP_TTL, IPPORT_RESERVED, IPPROTO_AH, IPPROTO_CBT, + IPPROTO_DSTOPTS, IPPROTO_EGP, IPPROTO_ESP, IPPROTO_FRAGMENT, IPPROTO_GGP, IPPROTO_HOPOPTS, + IPPROTO_ICLFXBM, IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IGP, + IPPROTO_IP, IPPROTO_IP as IPPROTO_IPIP, IPPROTO_IPV4, IPPROTO_IPV6, IPPROTO_L2TP, IPPROTO_ND, + IPPROTO_NONE, IPPROTO_PGM, IPPROTO_PIM, IPPROTO_PUP, IPPROTO_RAW, IPPROTO_RDP, IPPROTO_ROUTING, + IPPROTO_SCTP, IPPROTO_ST, IPPROTO_TCP, IPPROTO_UDP, IPV6_CHECKSUM, IPV6_DONTFRAG, + IPV6_HOPLIMIT, IPV6_HOPOPTS, IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS, + IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_PKTINFO, IPV6_RECVRTHDR, IPV6_RECVTCLASS, + IPV6_RTHDR, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, MSG_BCAST, MSG_CTRUNC, MSG_DONTROUTE, + MSG_MCAST, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, NI_DGRAM, NI_MAXHOST, NI_MAXSERV, + NI_NAMEREQD, NI_NOFQDN, NI_NUMERICHOST, NI_NUMERICSERV, RCVALL_IPLEVEL, RCVALL_OFF, RCVALL_ON, + RCVALL_SOCKETLEVELONLY, SD_BOTH, SD_RECEIVE, SD_SEND, SIO_KEEPALIVE_VALS, + SIO_LOOPBACK_FAST_PATH, SIO_RCVALL, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, + SO_OOBINLINE, SO_RCVBUF, SO_REUSEADDR, SO_SNDBUF, SO_TYPE, SO_USELOOPBACK, SOCK_DGRAM, + SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_STREAM, SOCKET_ERROR as SOCKET_ERROR_CODE, SOL_SOCKET, + SOMAXCONN, TCP_NODELAY, WSAEBADF, WSAECONNRESET, WSAENOTSOCK, WSAEWOULDBLOCK, getprotobyname, + getservbyname, getservbyport, getsockopt, setsockopt, +}; + +#[cfg(windows)] +pub const SO_EXCLUSIVEADDRUSE: i32 = -5; +#[cfg(windows)] +pub const EAI_MEMORY: i32 = windows_sys::Win32::Networking::WinSock::WSA_NOT_ENOUGH_MEMORY; +#[cfg(windows)] +pub const EAI_FAMILY: i32 = windows_sys::Win32::Networking::WinSock::WSAEAFNOSUPPORT; +#[cfg(windows)] +pub const EAI_BADFLAGS: i32 = windows_sys::Win32::Networking::WinSock::WSAEINVAL; +#[cfg(windows)] +pub const EAI_SOCKTYPE: i32 = windows_sys::Win32::Networking::WinSock::WSAESOCKTNOSUPPORT; +#[cfg(windows)] +pub const EAI_NODATA: i32 = windows_sys::Win32::Networking::WinSock::WSAHOST_NOT_FOUND; +#[cfg(windows)] +pub const EAI_NONAME: i32 = windows_sys::Win32::Networking::WinSock::WSAHOST_NOT_FOUND; +#[cfg(windows)] +pub const EAI_FAIL: i32 = windows_sys::Win32::Networking::WinSock::WSANO_RECOVERY; +#[cfg(windows)] +pub const EAI_AGAIN: i32 = windows_sys::Win32::Networking::WinSock::WSATRY_AGAIN; +#[cfg(windows)] +pub const EAI_SERVICE: i32 = windows_sys::Win32::Networking::WinSock::WSATYPE_NOT_FOUND; +#[cfg(windows)] +pub const IF_NAMESIZE: usize = IF_MAX_STRING_SIZE as usize; +#[cfg(windows)] +pub const AF_UNSPEC: i32 = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as i32; +#[cfg(windows)] +pub const AF_INET: i32 = windows_sys::Win32::Networking::WinSock::AF_INET as i32; +#[cfg(windows)] +pub const AF_INET6: i32 = windows_sys::Win32::Networking::WinSock::AF_INET6 as i32; +#[cfg(windows)] +pub const AI_PASSIVE: i32 = windows_sys::Win32::Networking::WinSock::AI_PASSIVE as i32; +#[cfg(windows)] +pub const AI_NUMERICHOST: i32 = windows_sys::Win32::Networking::WinSock::AI_NUMERICHOST as i32; +#[cfg(windows)] +pub const FROM_PROTOCOL_INFO_VALUE: i32 = FROM_PROTOCOL_INFO; + +#[cfg(windows)] +pub type RawSocket = SOCKET; + +#[cfg(windows)] +pub const INVALID_RAW_SOCKET: RawSocket = INVALID_SOCKET as RawSocket; + +#[cfg(windows)] +#[repr(C)] +pub struct TcpKeepalive { + pub onoff: u32, + pub keepalivetime: u32, + pub keepaliveinterval: u32, +} + +#[cfg(windows)] +pub struct SharedSocket { + pub raw: RawSocket, + pub family: i32, + pub socket_type: i32, + pub protocol: i32, +} + +#[cfg(windows)] +pub fn last_socket_error() -> io::Error { + io::Error::from_raw_os_error(unsafe { WSAGetLastError() }) +} + +#[cfg(windows)] +pub fn set_socket_inheritable(socket: RawSocket, inheritable: bool) -> io::Result<()> { + crate::nt::set_handle_inheritable(socket as _, inheritable) +} + +#[cfg(windows)] +pub fn close_socket_ignore_connreset(socket: RawSocket) -> io::Result<()> { + let ret = unsafe { windows_sys::Win32::Networking::WinSock::closesocket(socket) }; + if ret != 0 { + let err = last_socket_error(); + if err.raw_os_error() != Some(WSAECONNRESET) { + return Err(err); + } + } + Ok(()) +} + +#[cfg(windows)] +pub fn getsockopt_int(socket: RawSocket, level: i32, name: i32) -> io::Result { + let mut flag = 0i32; + let mut optlen = core::mem::size_of::() as i32; + let ret = unsafe { + getsockopt( + socket, + level, + name, + &mut flag as *mut i32 as *mut _, + &mut optlen, + ) + }; + if ret == SOCKET_ERROR { + Err(crate::os::errno_io_error()) + } else { + Ok(flag) + } +} + +#[cfg(windows)] +pub fn getsockopt_bytes( + socket: RawSocket, + level: i32, + name: i32, + buflen: usize, +) -> io::Result> { + let mut buf = vec![0u8; buflen]; + let mut optlen = buflen as i32; + let ret = unsafe { getsockopt(socket, level, name, buf.as_mut_ptr() as *mut _, &mut optlen) }; + if ret == SOCKET_ERROR { + Err(crate::os::errno_io_error()) + } else { + buf.truncate(optlen as usize); + Ok(buf) + } +} + +#[cfg(windows)] +pub fn setsockopt_bytes(socket: RawSocket, level: i32, name: i32, value: &[u8]) -> io::Result<()> { + let ret = unsafe { + setsockopt( + socket, + level, + name, + value.as_ptr() as *const _, + value.len() as i32, + ) + }; + if ret == SOCKET_ERROR { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn setsockopt_int(socket: RawSocket, level: i32, name: i32, value: i32) -> io::Result<()> { + let ret = unsafe { + setsockopt( + socket, + level, + name, + &value as *const i32 as *const _, + core::mem::size_of::() as i32, + ) + }; + if ret == SOCKET_ERROR { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn setsockopt_none(socket: RawSocket, level: i32, name: i32, optlen: u32) -> io::Result<()> { + let ret = unsafe { setsockopt(socket, level, name, core::ptr::null(), optlen as i32) }; + if ret == SOCKET_ERROR { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn protocol_info_size() -> usize { + core::mem::size_of::() +} + +#[cfg(windows)] +pub fn socket_from_share_data(bytes: &[u8]) -> io::Result { + let mut info: WSAPROTOCOL_INFOW = unsafe { core::mem::zeroed() }; + unsafe { + core::ptr::copy_nonoverlapping( + bytes.as_ptr(), + &mut info as *mut WSAPROTOCOL_INFOW as *mut u8, + protocol_info_size(), + ); + } + + let raw = unsafe { + WSASocketW( + FROM_PROTOCOL_INFO, + FROM_PROTOCOL_INFO, + FROM_PROTOCOL_INFO, + &info, + 0, + WSA_FLAG_OVERLAPPED, + ) + }; + if raw == INVALID_SOCKET { + return Err(last_socket_error()); + } + + crate::nt::set_handle_inheritable(raw as _, false)?; + + Ok(SharedSocket { + raw, + family: info.iAddressFamily, + socket_type: info.iSocketType, + protocol: info.iProtocol, + }) +} + +#[cfg(windows)] +pub fn share_socket(socket: RawSocket, process_id: u32) -> io::Result> { + let mut info = core::mem::MaybeUninit::::uninit(); + let ret = unsafe { WSADuplicateSocketW(socket, process_id, info.as_mut_ptr()) }; + if ret == SOCKET_ERROR { + return Err(last_socket_error()); + } + let info = unsafe { info.assume_init() }; + let bytes = unsafe { + core::slice::from_raw_parts( + &info as *const WSAPROTOCOL_INFOW as *const u8, + core::mem::size_of::(), + ) + }; + Ok(bytes.to_vec()) +} + +#[cfg(windows)] +pub fn ioctl_u32(socket: RawSocket, cmd: u32, option: u32) -> io::Result { + let mut recv = 0u32; + let ret = unsafe { + WSAIoctl( + socket, + cmd, + &option as *const u32 as *const _, + core::mem::size_of::() as u32, + core::ptr::null_mut(), + 0, + &mut recv, + core::ptr::null_mut(), + None, + ) + }; + if ret == SOCKET_ERROR { + Err(last_socket_error()) + } else { + Ok(recv) + } +} + +#[cfg(windows)] +pub fn ioctl_keepalive(socket: RawSocket, keepalive: TcpKeepalive) -> io::Result { + let mut recv = 0u32; + let ret = unsafe { + WSAIoctl( + socket, + windows_sys::Win32::Networking::WinSock::SIO_KEEPALIVE_VALS, + &keepalive as *const TcpKeepalive as *const _, + core::mem::size_of::() as u32, + core::ptr::null_mut(), + 0, + &mut recv, + core::ptr::null_mut(), + None, + ) + }; + if ret == SOCKET_ERROR { + Err(last_socket_error()) + } else { + Ok(recv) + } +} + +#[cfg(windows)] +pub fn if_nametoindex_checked(name: &CStr) -> io::Result { + crate::os::set_errno(libc::ENODEV); + let ret = unsafe { if_nametoindex(name.as_ptr() as _) }; + if ret == 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(ret) + } +} + +#[cfg(windows)] +pub fn if_indextoname_checked(index: u32) -> io::Result { + let mut buf = [0; IF_MAX_STRING_SIZE as usize + 1]; + crate::os::set_errno(libc::ENXIO); + let ret = unsafe { if_indextoname(index, buf.as_mut_ptr()) }; + if ret.is_null() { + Err(crate::os::errno_io_error()) + } else { + let buf = unsafe { CStr::from_ptr(buf.as_ptr() as _) }; + Ok(buf.to_string_lossy().into_owned()) + } +} + +#[cfg(windows)] +pub fn if_nameindex() -> io::Result> { + fn get_name(luid: &NET_LUID_LH) -> io::Result { + let mut buf = [0u16; IF_MAX_STRING_SIZE as usize + 1]; + let ret = unsafe { ConvertInterfaceLuidToNameW(luid, buf.as_mut_ptr(), buf.len()) }; + if ret != 0 { + return Err(io::Error::from_raw_os_error(ret as i32)); + } + let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len()); + Ok(String::from_utf16_lossy(&buf[..len])) + } + + struct MibTable { + ptr: NonNull, + } + + impl MibTable { + fn get_raw() -> io::Result { + let mut ptr = core::ptr::null_mut(); + let ret = unsafe { GetIfTable2Ex(MibIfTableRaw, &mut ptr) }; + if ret == 0 { + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + Ok(Self { ptr }) + } else { + Err(io::Error::from_raw_os_error(ret as i32)) + } + } + + fn as_slice(&self) -> &[MIB_IF_ROW2] { + unsafe { + let p = self.ptr.as_ptr(); + let ptr = &raw const (*p).Table as *const MIB_IF_ROW2; + core::slice::from_raw_parts(ptr, (*p).NumEntries as usize) + } + } + } + + impl Drop for MibTable { + fn drop(&mut self) { + unsafe { FreeMibTable(self.ptr.as_ptr() as *mut _) }; + } + } + + let table = MibTable::get_raw()?; + table + .as_slice() + .iter() + .map(|entry| Ok((entry.InterfaceIndex, get_name(&entry.InterfaceLuid)?))) + .collect() +} diff --git a/crates/host_env/src/syslog.rs b/crates/host_env/src/syslog.rs index a4100ac2a21..8820b8f1c5d 100644 --- a/crates/host_env/src/syslog.rs +++ b/crates/host_env/src/syslog.rs @@ -1,9 +1,7 @@ use alloc::boxed::Box; use core::ffi::CStr; -use std::{ - os::raw::c_char, - sync::{OnceLock, RwLock}, -}; +use parking_lot::RwLock; +use std::{os::raw::c_char, sync::OnceLock}; #[derive(Debug)] enum GlobalIdent { @@ -27,10 +25,7 @@ fn global_ident() -> &'static RwLock> { #[must_use] pub fn is_open() -> bool { - global_ident() - .read() - .expect("syslog lock poisoned") - .is_some() + global_ident().read().is_some() } pub fn openlog(ident: Option>, logoption: i32, facility: i32) { @@ -38,19 +33,20 @@ pub fn openlog(ident: Option>, logoption: i32, facility: i32) { Some(ident) => GlobalIdent::Explicit(ident), None => GlobalIdent::Implicit, }; - let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + let mut locked_ident = global_ident().write(); unsafe { libc::openlog(ident.as_ptr(), logoption, facility) }; *locked_ident = Some(ident); } pub fn syslog(priority: i32, msg: &CStr) { + let _locked_ident = global_ident().read(); let cformat = c"%s"; unsafe { libc::syslog(priority, cformat.as_ptr(), msg.as_ptr()) }; } pub fn closelog() { - if is_open() { - let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + let mut locked_ident = global_ident().write(); + if locked_ident.is_some() { unsafe { libc::closelog() }; *locked_ident = None; } @@ -63,7 +59,7 @@ pub fn setlogmask(maskpri: i32) -> i32 { #[must_use] pub const fn log_mask(pri: i32) -> i32 { - pri << 1 + 1 << pri } #[must_use] diff --git a/crates/host_env/src/termios.rs b/crates/host_env/src/termios.rs index 76bcd0c9f01..074d03a455b 100644 --- a/crates/host_env/src/termios.rs +++ b/crates/host_env/src/termios.rs @@ -1,11 +1,105 @@ -pub fn tcgetattr(fd: i32) -> std::io::Result<::termios::Termios> { - ::termios::Termios::from_fd(fd) +pub type Termios = ::termios::Termios; + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "macos", + target_os = "openbsd", + target_os = "solaris" +))] +pub use ::termios::os::target::TAB3; +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use ::termios::os::target::TCSASOFT; +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "solaris" +))] +pub use ::termios::os::target::{B460800, B921600}; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use ::termios::os::target::{ + B500000, B576000, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, + B4000000, CBAUDEX, +}; +#[cfg(any( + target_os = "android", + target_os = "illumos", + target_os = "linux", + target_os = "macos", + target_os = "solaris" +))] +pub use ::termios::os::target::{ + BS0, BS1, BSDLY, CR0, CR1, CR2, CR3, CRDLY, FF0, FF1, FFDLY, NL0, NL1, NLDLY, OFDEL, OFILL, + TAB1, TAB2, VT0, VT1, VTDLY, +}; +#[cfg(any( + target_os = "android", + target_os = "illumos", + target_os = "linux", + target_os = "solaris" +))] +pub use ::termios::os::target::{CBAUD, CIBAUD, IUCLC, OLCUC, XCASE}; +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "macos", + target_os = "solaris" +))] +pub use ::termios::os::target::{TAB0, TABDLY}; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use ::termios::os::target::{VSWTC, VSWTC as VSWTCH}; +#[cfg(any(target_os = "illumos", target_os = "solaris"))] +pub use ::termios::os::target::{VSWTCH, VSWTCH as VSWTC}; +pub use ::termios::{ + B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, + B38400, BRKINT, CLOCAL, CREAD, CS5, CS6, CS7, CS8, CSIZE, CSTOPB, ECHO, ECHOE, ECHOK, ECHONL, + HUPCL, ICANON, ICRNL, IEXTEN, IGNBRK, IGNCR, IGNPAR, INLCR, INPCK, ISIG, ISTRIP, IXANY, IXOFF, + IXON, NOFLSH, OCRNL, ONLCR, ONLRET, ONOCR, OPOST, PARENB, PARMRK, PARODD, TCIFLUSH, TCIOFF, + TCIOFLUSH, TCION, TCOFLUSH, TCOOFF, TCOON, TCSADRAIN, TCSAFLUSH, TCSANOW, TOSTOP, VEOF, VEOL, + VERASE, VINTR, VKILL, VMIN, VQUIT, VSTART, VSTOP, VSUSP, VTIME, + os::target::{ + B57600, B115200, B230400, CRTSCTS, ECHOCTL, ECHOKE, ECHOPRT, EXTA, EXTB, FLUSHO, IMAXBEL, + NCCS, PENDIN, VDISCARD, VEOL2, VLNEXT, VREPRINT, VWERASE, + }, +}; + +pub fn tcgetattr(fd: i32) -> std::io::Result { + Termios::from_fd(fd) } -pub fn tcsetattr(fd: i32, when: i32, termios: &::termios::Termios) -> std::io::Result<()> { +pub fn tcsetattr(fd: i32, when: i32, termios: &Termios) -> std::io::Result<()> { ::termios::tcsetattr(fd, when, termios) } +pub fn cfgetispeed(termios: &Termios) -> libc::speed_t { + ::termios::cfgetispeed(termios) +} + +pub fn cfgetospeed(termios: &Termios) -> libc::speed_t { + ::termios::cfgetospeed(termios) +} + +pub fn cfsetispeed(termios: &mut Termios, speed: libc::speed_t) -> std::io::Result<()> { + ::termios::cfsetispeed(termios, speed) +} + +pub fn cfsetospeed(termios: &mut Termios, speed: libc::speed_t) -> std::io::Result<()> { + ::termios::cfsetospeed(termios, speed) +} + pub fn tcsendbreak(fd: i32, duration: i32) -> std::io::Result<()> { ::termios::tcsendbreak(fd, duration) } diff --git a/crates/host_env/src/testconsole.rs b/crates/host_env/src/testconsole.rs new file mode 100644 index 00000000000..001e72e6ed5 --- /dev/null +++ b/crates/host_env/src/testconsole.rs @@ -0,0 +1,48 @@ +use std::io; +use windows_sys::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + System::Console::{INPUT_RECORD, KEY_EVENT, WriteConsoleInputW}, +}; + +pub fn write_console_input(fd: i32, data: &[u16]) -> io::Result<()> { + let handle = unsafe { libc::get_osfhandle(fd) } as HANDLE; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let size = data.len() as u32; + let mut records: Vec = Vec::with_capacity(data.len()); + for &wc in data { + let mut rec: INPUT_RECORD = unsafe { core::mem::zeroed() }; + rec.EventType = KEY_EVENT as u16; + rec.Event.KeyEvent.bKeyDown = 1; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.uChar.UnicodeChar = wc; + records.push(rec); + } + + let mut total: u32 = 0; + while total < size { + let mut wrote: u32 = 0; + let res = unsafe { + WriteConsoleInputW( + handle, + records[total as usize..].as_ptr(), + size - total, + &mut wrote, + ) + }; + if res == 0 { + return Err(io::Error::last_os_error()); + } + if wrote == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "WriteConsoleInputW made no progress", + )); + } + total += wrote; + } + + Ok(()) +} diff --git a/crates/host_env/src/thread.rs b/crates/host_env/src/thread.rs new file mode 100644 index 00000000000..5b0d42477b6 --- /dev/null +++ b/crates/host_env/src/thread.rs @@ -0,0 +1,42 @@ +#[cfg(any(target_os = "linux", target_os = "macos"))] +use alloc::ffi::CString; + +#[cfg(unix)] +pub fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } +} + +#[cfg(windows)] +pub fn current_thread_id() -> u64 { + unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() as u64 } +} + +#[cfg(target_os = "linux")] +pub fn set_current_thread_name(name: &str) { + if CString::new(name).is_ok() { + let truncated = if name.len() > 15 { + let mut end = 15; + while !name.is_char_boundary(end) { + end -= 1; + } + CString::new(&name[..end]).expect("slice of null-free string is null-free") + } else { + CString::new(name).expect("name was already checked for nul bytes") + }; + unsafe { + libc::pthread_setname_np(libc::pthread_self(), truncated.as_ptr()); + } + } +} + +#[cfg(target_os = "macos")] +pub fn set_current_thread_name(name: &str) { + if let Ok(c_name) = CString::new(name) { + unsafe { + libc::pthread_setname_np(c_name.as_ptr()); + } + } +} + +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +pub fn set_current_thread_name(_name: &str) {} diff --git a/crates/host_env/src/time.rs b/crates/host_env/src/time.rs index 0bcabe9957b..cac3c795b2b 100644 --- a/crates/host_env/src/time.rs +++ b/crates/host_env/src/time.rs @@ -1,6 +1,11 @@ +#[cfg(unix)] +use alloc::ffi::CString; use core::time::Duration; use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; +#[cfg(target_env = "msvc")] +use alloc::string::String; + pub const SEC_TO_MS: i64 = 1000; pub const MS_TO_US: i64 = 1000; pub const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US; @@ -14,17 +19,370 @@ pub fn duration_since_system_now() -> Result { SystemTime::now().duration_since(UNIX_EPOCH) } +#[cfg(unix)] +pub type TimeT = libc::time_t; + +#[cfg(windows)] +pub type TimeT = libc::time_t; + +#[cfg(unix)] +#[derive(Clone, Copy, Debug)] +pub struct ProcessTimes { + pub user: f64, + pub system: f64, + pub children_user: f64, + pub children_system: f64, + pub elapsed: f64, +} + +#[cfg(unix)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn current_time_t() -> TimeT { + unsafe { libc::time(core::ptr::null_mut()) } +} + +#[cfg(unix)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn gmtime_from_timestamp(when: TimeT) -> Option { + let mut out = core::mem::MaybeUninit::::uninit(); + let ret = unsafe { libc::gmtime_r(&when, out.as_mut_ptr()) }; + (!ret.is_null()).then(|| unsafe { out.assume_init() }) +} + +#[cfg(unix)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn localtime_from_timestamp(when: TimeT) -> Option { + let mut out = core::mem::MaybeUninit::::uninit(); + let ret = unsafe { libc::localtime_r(&when, out.as_mut_ptr()) }; + (!ret.is_null()).then(|| unsafe { out.assume_init() }) +} + +#[cfg(unix)] +pub fn mktime(tm: &mut libc::tm) -> TimeT { + unsafe { libc::mktime(tm) } +} + +#[cfg(windows)] +unsafe extern "C" { + fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int; + fn _localtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int; + #[link_name = "_mktime64"] + fn c_mktime(tm: *mut libc::tm) -> libc::time_t; +} + +#[cfg(windows)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn current_time_t() -> TimeT { + unsafe { libc::time(core::ptr::null_mut()) } +} + +#[cfg(windows)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn gmtime_from_timestamp(when: TimeT) -> Option { + let mut out = core::mem::MaybeUninit::::uninit(); + let err = unsafe { _gmtime64_s(out.as_mut_ptr(), &when) }; + (err == 0).then(|| unsafe { out.assume_init() }) +} + +#[cfg(windows)] +#[cfg_attr(target_env = "musl", allow(deprecated))] +pub fn localtime_from_timestamp(when: TimeT) -> Option { + let mut out = core::mem::MaybeUninit::::uninit(); + let err = unsafe { _localtime64_s(out.as_mut_ptr(), &when) }; + (err == 0).then(|| unsafe { out.assume_init() }) +} + +#[cfg(windows)] +pub fn mktime(tm: &mut libc::tm) -> TimeT { + unsafe { crate::suppress_iph!(c_mktime(tm)) } +} + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub fn strerror(errno: i32) -> String { + unsafe { core::ffi::CStr::from_ptr(libc::strerror(errno)) } + .to_string_lossy() + .into_owned() +} + +#[cfg(unix)] +pub fn nix_errno_display(errno: i32) -> String { + nix::errno::Errno::from_raw(errno).to_string() +} + +#[cfg(all(unix, not(any(target_os = "redox", target_os = "android"))))] +pub fn getloadavg() -> std::io::Result<[f64; 3]> { + let mut loadavg = [0f64; 3]; + let ok = unsafe { libc::getloadavg(loadavg.as_mut_ptr(), 3) }; + if ok != 3 { + Err(std::io::Error::last_os_error()) + } else { + Ok(loadavg) + } +} + +#[cfg(unix)] +pub fn waitstatus_to_exitcode(status: libc::c_int) -> Option { + if libc::WIFEXITED(status) { + return Some(libc::WEXITSTATUS(status)); + } + if libc::WIFSIGNALED(status) { + return Some(-libc::WTERMSIG(status)); + } + None +} + +#[cfg(any(unix, all(target_arch = "wasm32", target_os = "emscripten")))] +pub fn process_times() -> std::io::Result { + let mut t = libc::tms { + tms_utime: 0, + tms_stime: 0, + tms_cutime: 0, + tms_cstime: 0, + }; + + let tick_for_second = unsafe { libc::sysconf(libc::_SC_CLK_TCK) }; + if tick_for_second <= 0 { + return Err(std::io::Error::last_os_error()); + } + let tick_for_second = tick_for_second as f64; + let c = unsafe { libc::times(&mut t as *mut _) }; + if c == (-1i8) as libc::clock_t { + return Err(std::io::Error::last_os_error()); + } + + Ok(ProcessTimes { + user: t.tms_utime as f64 / tick_for_second, + system: t.tms_stime as f64 / tick_for_second, + children_user: t.tms_cutime as f64 / tick_for_second, + children_system: t.tms_cstime as f64 / tick_for_second, + elapsed: c as f64 / tick_for_second, + }) +} + +#[cfg(unix)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ClockId(libc::clockid_t); + +#[cfg(unix)] +impl ClockId { + pub const fn from_raw(raw: libc::clockid_t) -> Self { + Self(raw) + } + + pub const fn as_raw(self) -> libc::clockid_t { + self.0 + } + + pub const CLOCK_MONOTONIC: Self = Self(libc::CLOCK_MONOTONIC); + pub const CLOCK_REALTIME: Self = Self(libc::CLOCK_REALTIME); + + #[cfg(not(any( + target_os = "illumos", + target_os = "netbsd", + target_os = "solaris", + target_os = "openbsd", + target_os = "wasi", + )))] + pub const CLOCK_PROCESS_CPUTIME_ID: Self = Self(libc::CLOCK_PROCESS_CPUTIME_ID); + + #[cfg(not(any( + target_os = "illumos", + target_os = "netbsd", + target_os = "solaris", + target_os = "openbsd", + target_os = "redox", + )))] + pub const CLOCK_THREAD_CPUTIME_ID: Self = Self(libc::CLOCK_THREAD_CPUTIME_ID); +} + +#[cfg(unix)] +fn nix_clock_id(id: ClockId) -> nix::time::ClockId { + nix::time::ClockId::from_raw(id.as_raw()) +} + +#[cfg(unix)] +pub fn clock_gettime(id: ClockId) -> std::io::Result { + nix::time::clock_gettime(nix_clock_id(id)) + .map(Duration::from) + .map_err(std::io::Error::from) +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn clock_getres(id: ClockId) -> std::io::Result { + nix::time::clock_getres(nix_clock_id(id)) + .map(Duration::from) + .map_err(std::io::Error::from) +} + +#[cfg(all(unix, not(target_os = "redox"), not(target_vendor = "apple")))] +pub fn clock_settime(id: ClockId, time: Duration) -> std::io::Result<()> { + let ts = nix::sys::time::TimeSpec::from(time); + nix::time::clock_settime(nix_clock_id(id), ts) + .map(drop) + .map_err(std::io::Error::from) +} + +#[cfg(all(unix, not(target_os = "redox"), target_os = "macos"))] +pub fn clock_settime(id: ClockId, time: Duration) -> std::io::Result<()> { + let ts = nix::sys::time::TimeSpec::from(time); + let ret = unsafe { libc::clock_settime(id.as_raw(), ts.as_ref()) }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn nanosleep(duration: Duration) -> std::io::Result<()> { + let ts = nix::sys::time::TimeSpec::from(duration); + let ret = unsafe { libc::nanosleep(ts.as_ref(), core::ptr::null_mut()) }; + if ret != 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(target_os = "solaris")] +pub fn gethrvtime_duration() -> Duration { + Duration::from_nanos(unsafe { libc::gethrvtime() }) +} + +#[cfg(target_env = "msvc")] +#[cfg(not(target_arch = "wasm32"))] +#[derive(Clone, Debug)] +pub struct WindowsTimeZoneInfo { + pub bias: i32, + pub standard_bias: i32, + pub daylight_bias: i32, + pub standard_name: String, + pub daylight_name: String, +} + +#[cfg(target_env = "msvc")] +#[cfg(not(target_arch = "wasm32"))] +fn decode_tz_name(name: &[u16]) -> String { + widestring::decode_utf16_lossy(name.iter().copied()) + .take_while(|&c| c != '\0') + .collect() +} + #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] #[must_use] -pub fn get_tz_info() -> windows_sys::Win32::System::Time::TIME_ZONE_INFORMATION { +pub fn get_tz_info() -> WindowsTimeZoneInfo { let mut info = unsafe { core::mem::zeroed() }; unsafe { windows_sys::Win32::System::Time::GetTimeZoneInformation(&mut info) }; - info + WindowsTimeZoneInfo { + bias: info.Bias as i32, + standard_bias: info.StandardBias as i32, + daylight_bias: info.DaylightBias as i32, + standard_name: decode_tz_name(&info.StandardName), + daylight_name: decode_tz_name(&info.DaylightName), + } +} + +#[cfg(windows)] +fn u64_from_filetime(time: windows_sys::Win32::Foundation::FILETIME) -> u64 { + u64::from(time.dwLowDateTime) | (u64::from(time.dwHighDateTime) << 32) +} + +#[cfg(windows)] +#[derive(Clone, Copy, Debug)] +pub struct ProcessTimes100ns { + pub user: u64, + pub system: u64, +} + +#[cfg(windows)] +pub fn query_performance_frequency() -> Option { + let mut freq = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Performance::QueryPerformanceFrequency(freq.as_mut_ptr()) + } != 0) + .then(|| unsafe { freq.assume_init() }) +} + +#[cfg(windows)] +pub fn query_performance_counter() -> i64 { + let mut counter = core::mem::MaybeUninit::uninit(); + unsafe { + windows_sys::Win32::System::Performance::QueryPerformanceCounter(counter.as_mut_ptr()); + counter.assume_init() + } +} + +#[cfg(windows)] +pub fn get_system_time_adjustment() -> Option { + let mut time_adjustment = core::mem::MaybeUninit::uninit(); + let mut time_increment = core::mem::MaybeUninit::uninit(); + let mut is_time_adjustment_disabled = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::SystemInformation::GetSystemTimeAdjustment( + time_adjustment.as_mut_ptr(), + time_increment.as_mut_ptr(), + is_time_adjustment_disabled.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { time_increment.assume_init() }) +} + +#[cfg(windows)] +pub fn tick_count64() -> u64 { + unsafe { windows_sys::Win32::System::SystemInformation::GetTickCount64() } +} + +#[cfg(windows)] +pub fn get_thread_time_100ns() -> Option { + let mut creation_time = core::mem::MaybeUninit::uninit(); + let mut exit_time = core::mem::MaybeUninit::uninit(); + let mut kernel_time = core::mem::MaybeUninit::uninit(); + let mut user_time = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Threading::GetThreadTimes( + windows_sys::Win32::System::Threading::GetCurrentThread(), + creation_time.as_mut_ptr(), + exit_time.as_mut_ptr(), + kernel_time.as_mut_ptr(), + user_time.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { + u64_from_filetime(kernel_time.assume_init()) + + u64_from_filetime(user_time.assume_init()) + }) +} + +#[cfg(windows)] +pub fn get_process_time_100ns() -> Option { + get_process_times_100ns().map(|times| times.user + times.system) +} + +#[cfg(windows)] +pub fn get_process_times_100ns() -> Option { + let mut creation_time = core::mem::MaybeUninit::uninit(); + let mut exit_time = core::mem::MaybeUninit::uninit(); + let mut kernel_time = core::mem::MaybeUninit::uninit(); + let mut user_time = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Threading::GetProcessTimes( + windows_sys::Win32::System::Threading::GetCurrentProcess(), + creation_time.as_mut_ptr(), + exit_time.as_mut_ptr(), + kernel_time.as_mut_ptr(), + user_time.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { + ProcessTimes100ns { + user: u64_from_filetime(user_time.assume_init()), + system: u64_from_filetime(kernel_time.assume_init()), + } + }) } #[cfg(any(unix, windows))] -#[must_use] pub fn asctime_from_tm(tm: &libc::tm) -> String { const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const MON_NAME: [&str; 12] = [ @@ -41,3 +399,196 @@ pub fn asctime_from_tm(tm: &libc::tm) -> String { tm.tm_year + 1900 ) } + +#[cfg(any(unix, windows))] +#[derive(Clone, Debug)] +pub struct CheckedTm { + pub tm: libc::tm, + #[cfg(unix)] + pub zone: Option, +} + +#[cfg(any(unix, windows))] +#[derive(Clone, Debug)] +pub struct CheckedTmParts { + pub year: i64, + pub tm_mon: i32, + pub tm_mday: i32, + pub tm_hour: i32, + pub tm_min: i32, + pub tm_sec: i32, + pub tm_wday: i32, + pub tm_yday: i32, + pub tm_isdst: i32, + #[cfg(unix)] + pub zone: Option, + #[cfg(unix)] + pub gmtoff: Option, +} + +#[cfg(any(unix, windows))] +#[derive(Clone, Copy, Debug)] +pub struct MktimeTmParts { + pub year: i32, + pub tm_sec: i32, + pub tm_min: i32, + pub tm_hour: i32, + pub tm_mday: i32, + pub tm_mon: i32, + pub tm_yday: i32, + pub tm_isdst: i32, +} + +#[cfg(any(unix, windows))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CheckedTmError { + YearOutOfRange, + MonthOutOfRange, + DayOfMonthOutOfRange, + HourOutOfRange, + MinuteOutOfRange, + SecondsOutOfRange, + DayOfWeekOutOfRange, + DayOfYearOutOfRange, + EmbeddedNul, +} + +#[cfg(any(unix, windows))] +pub fn checked_tm_from_parts(parts: CheckedTmParts) -> Result { + if parts.year < i64::from(i32::MIN) + 1900 || parts.year > i64::from(i32::MAX) { + return Err(CheckedTmError::YearOutOfRange); + } + + let mut tm: libc::tm = unsafe { core::mem::zeroed() }; + tm.tm_year = parts.year as i32 - 1900; + tm.tm_mon = parts.tm_mon; + tm.tm_mday = parts.tm_mday; + tm.tm_hour = parts.tm_hour; + tm.tm_min = parts.tm_min; + tm.tm_sec = parts.tm_sec; + tm.tm_wday = parts.tm_wday; + tm.tm_yday = parts.tm_yday; + tm.tm_isdst = parts.tm_isdst; + + if tm.tm_mon == -1 { + tm.tm_mon = 0; + } else if !(0..=11).contains(&tm.tm_mon) { + return Err(CheckedTmError::MonthOutOfRange); + } + if tm.tm_mday == 0 { + tm.tm_mday = 1; + } else if !(0..=31).contains(&tm.tm_mday) { + return Err(CheckedTmError::DayOfMonthOutOfRange); + } + if !(0..=23).contains(&tm.tm_hour) { + return Err(CheckedTmError::HourOutOfRange); + } + if !(0..=59).contains(&tm.tm_min) { + return Err(CheckedTmError::MinuteOutOfRange); + } + if !(0..=61).contains(&tm.tm_sec) { + return Err(CheckedTmError::SecondsOutOfRange); + } + if tm.tm_wday < 0 { + return Err(CheckedTmError::DayOfWeekOutOfRange); + } + if tm.tm_yday == -1 { + tm.tm_yday = 0; + } else if !(0..=365).contains(&tm.tm_yday) { + return Err(CheckedTmError::DayOfYearOutOfRange); + } + + #[cfg(unix)] + { + let zone = match parts.zone { + Some(zone) => Some(CString::new(zone).map_err(|_| CheckedTmError::EmbeddedNul)?), + None => None, + }; + if let Some(zone) = &zone { + tm.tm_zone = zone.as_ptr().cast_mut(); + } + if let Some(gmtoff) = parts.gmtoff { + tm.tm_gmtoff = gmtoff as _; + } + Ok(CheckedTm { tm, zone }) + } + #[cfg(windows)] + { + Ok(CheckedTm { tm }) + } +} + +#[cfg(any(unix, windows))] +pub fn mktime_tm_from_parts(parts: MktimeTmParts) -> Result { + if parts.year < i32::MIN + 1900 { + return Err(CheckedTmError::YearOutOfRange); + } + let mut tm: libc::tm = unsafe { core::mem::zeroed() }; + tm.tm_sec = parts.tm_sec; + tm.tm_min = parts.tm_min; + tm.tm_hour = parts.tm_hour; + tm.tm_mday = parts.tm_mday; + tm.tm_mon = parts.tm_mon - 1; + tm.tm_year = parts.year - 1900; + tm.tm_wday = -1; + tm.tm_yday = parts.tm_yday - 1; + tm.tm_isdst = parts.tm_isdst; + Ok(tm) +} + +#[cfg(unix)] +pub fn strftime_ascii(fmt: &str, tm: &libc::tm) -> Result { + let fmt_c = CString::new(fmt).map_err(|_| CheckedTmError::EmbeddedNul)?; + let mut size = 1024usize; + let max_scale = 256usize.saturating_mul(fmt.len().max(1)); + loop { + let mut out = vec![0u8; size]; + let written = unsafe { + libc::strftime( + out.as_mut_ptr().cast(), + out.len(), + fmt_c.as_ptr(), + tm as *const libc::tm, + ) + }; + if written > 0 || size >= max_scale { + return Ok(String::from_utf8_lossy(&out[..written]).into_owned()); + } + size = size.saturating_mul(2); + } +} + +#[cfg(windows)] +unsafe extern "C" { + fn wcsftime( + s: *mut libc::wchar_t, + max: libc::size_t, + format: *const libc::wchar_t, + tm: *const libc::tm, + ) -> libc::size_t; +} + +#[cfg(windows)] +pub fn strftime_ascii(fmt: &str, tm: &libc::tm) -> Result { + if fmt.contains('\0') { + return Err(CheckedTmError::EmbeddedNul); + } + let fmt_wide: Vec = fmt.encode_utf16().chain(core::iter::once(0)).collect(); + let mut size = 1024usize; + let max_scale = 256usize.saturating_mul(fmt.len().max(1)); + loop { + let mut out = vec![0u16; size]; + let written = unsafe { + crate::suppress_iph!(wcsftime( + out.as_mut_ptr(), + out.len(), + fmt_wide.as_ptr(), + tm as *const libc::tm, + )) + }; + if written > 0 || size >= max_scale { + return Ok(String::from_utf16_lossy(&out[..written])); + } + size = size.saturating_mul(2); + } +} diff --git a/crates/host_env/src/winapi.rs b/crates/host_env/src/winapi.rs index 70d7feea01d..b2a15bca7d4 100644 --- a/crates/host_env/src/winapi.rs +++ b/crates/host_env/src/winapi.rs @@ -1,15 +1,793 @@ -use windows_sys::Win32::Foundation::HANDLE; +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "This module mirrors Win32 APIs with raw handle and pointer parameters." +)] + +use std::{io, path::Path}; +use windows_sys::Win32::{ + Foundation::{HANDLE, HMODULE, WAIT_FAILED}, + System::Threading::PROCESS_INFORMATION, +}; + +pub use windows_sys::Win32::{ + Foundation::{ + DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, + ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_NETNAME_DELETED, ERROR_NO_DATA, + ERROR_NO_SYSTEM_RESOURCES, ERROR_NOT_FOUND, ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY, + ERROR_PIPE_CONNECTED, ERROR_PORT_UNREACHABLE, ERROR_PRIVILEGE_NOT_HELD, ERROR_SEM_TIMEOUT, + ERROR_SUCCESS, GENERIC_READ, GENERIC_WRITE, STILL_ACTIVE, WAIT_ABANDONED_0, WAIT_OBJECT_0, + WAIT_TIMEOUT, + }, + Globalization::{ + LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, LCMAP_KATAKANA, LCMAP_LINGUISTIC_CASING, + LCMAP_LOWERCASE, LCMAP_SIMPLIFIED_CHINESE, LCMAP_TITLECASE, LCMAP_TRADITIONAL_CHINESE, + LCMAP_UPPERCASE, + }, + Storage::FileSystem::{ + COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK, COPY_FILE_FAIL_IF_EXISTS, + COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD, COPY_FILE_OPEN_SOURCE_FOR_WRITE, + COPY_FILE_REQUEST_COMPRESSED_TRAFFIC, COPY_FILE_REQUEST_SECURITY_PRIVILEGES, + COPY_FILE_RESTARTABLE, COPY_FILE_RESUME_FROM_PAUSE, COPYFILE2_CALLBACK_CHUNK_FINISHED, + COPYFILE2_CALLBACK_CHUNK_STARTED, COPYFILE2_CALLBACK_ERROR, + COPYFILE2_CALLBACK_POLL_CONTINUE, COPYFILE2_CALLBACK_STREAM_FINISHED, + COPYFILE2_CALLBACK_STREAM_STARTED, COPYFILE2_PROGRESS_CANCEL, COPYFILE2_PROGRESS_CONTINUE, + COPYFILE2_PROGRESS_PAUSE, COPYFILE2_PROGRESS_QUIET, COPYFILE2_PROGRESS_STOP, + FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, + FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, OPEN_EXISTING, + PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE, + }, + System::{ + Console::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}, + Memory::{ + FILE_MAP_ALL_ACCESS, FILE_MAP_COPY, FILE_MAP_EXECUTE, FILE_MAP_READ, FILE_MAP_WRITE, + MEM_COMMIT, MEM_FREE, MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE, MEM_RESERVE, PAGE_EXECUTE, + PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, PAGE_GUARD, + PAGE_NOACCESS, PAGE_NOCACHE, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOMBINE, + PAGE_WRITECOPY, SEC_COMMIT, SEC_IMAGE, SEC_LARGE_PAGES, SEC_NOCACHE, SEC_RESERVE, + SEC_WRITECOMBINE, + }, + Pipes::{ + NMPWAIT_WAIT_FOREVER, PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, + }, + SystemServices::LOCALE_NAME_MAX_LENGTH, + Threading::{ + ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, CREATE_BREAKAWAY_FROM_JOB, + CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, + CREATE_NO_WINDOW, DETACHED_PROCESS, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_DUP_HANDLE, REALTIME_PRIORITY_CLASS, + STARTF_FORCEOFFFEEDBACK, STARTF_FORCEONFEEDBACK, STARTF_PREVENTPINNING, + STARTF_RUNFULLSCREEN, STARTF_TITLEISAPPID, STARTF_TITLEISLINKNAME, + STARTF_UNTRUSTEDSOURCE, STARTF_USECOUNTCHARS, STARTF_USEFILLATTRIBUTE, + STARTF_USEHOTKEY, STARTF_USEPOSITION, STARTF_USESHOWWINDOW, STARTF_USESIZE, + STARTF_USESTDHANDLES, + }, + }, + UI::WindowsAndMessaging::SW_HIDE, +}; + +pub type Handle = HANDLE; +pub type StdHandle = windows_sys::Win32::System::Console::STD_HANDLE; +pub type FileType = windows_sys::Win32::Storage::FileSystem::FILE_TYPE; +pub const MAX_PATH_USIZE: usize = windows_sys::Win32::Foundation::MAX_PATH as usize; +pub const INFINITE_TIMEOUT: u32 = windows_sys::Win32::System::Threading::INFINITE; +pub const CREATE_UNICODE_ENVIRONMENT_FLAG: u32 = + windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT; +pub const EXTENDED_STARTUPINFO_PRESENT_FLAG: u32 = + windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT; +pub const LCMAP_BYTEREV_FLAG: u32 = windows_sys::Win32::Globalization::LCMAP_BYTEREV; +pub const LCMAP_HASH_FLAG: u32 = windows_sys::Win32::Globalization::LCMAP_HASH; +pub const LCMAP_SORTHANDLE_FLAG: u32 = windows_sys::Win32::Globalization::LCMAP_SORTHANDLE; +pub const LCMAP_SORTKEY_FLAG: u32 = windows_sys::Win32::Globalization::LCMAP_SORTKEY; + +pub struct PeekNamedPipeResult { + pub data: Option>, + pub available: u32, + pub left_this_message: u32, +} + +pub struct ReadFileResult { + pub data: Vec, + pub error: u32, +} + +pub struct WriteFileResult { + pub written: u32, + pub error: u32, +} + +pub enum BatchedWaitResult { + All, + Indices(Vec), +} + +pub enum BatchedWaitError { + Timeout, + Interrupted, + Os(u32), +} + +pub enum BuildEnvironmentBlockError { + ContainsNul, + IllegalName, +} + +pub enum MimeRegistryReadError { + Os(u32), + Callback(E), +} + +pub struct AttrList { + handlelist: Vec, + attrlist: Vec, +} + +pub struct StartupInfoData { + pub flags: u32, + pub show_window: u16, + pub std_input: HANDLE, + pub std_output: HANDLE, + pub std_error: HANDLE, +} + +pub struct ProcessInfo { + pub process: HANDLE, + pub thread: HANDLE, + pub process_id: u32, + pub thread_id: u32, +} #[must_use] pub fn get_acp() -> u32 { unsafe { windows_sys::Win32::Globalization::GetACP() } } +pub fn close_handle(handle: HANDLE) -> i32 { + unsafe { windows_sys::Win32::Foundation::CloseHandle(handle) } +} + +impl AttrList { + pub fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void { + self.attrlist.as_mut_ptr().cast() + } +} + +impl Drop for AttrList { + fn drop(&mut self) { + unsafe { + windows_sys::Win32::System::Threading::DeleteProcThreadAttributeList( + self.attrlist.as_mut_ptr().cast(), + ) + }; + } +} + +pub fn create_file_w( + file_name: *const u16, + desired_access: u32, + share_mode: u32, + creation_disposition: u32, + flags_and_attributes: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::Storage::FileSystem::CreateFileW( + file_name, + desired_access, + share_mode, + core::ptr::null(), + creation_disposition, + flags_and_attributes, + core::ptr::null_mut(), + ) + }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +/// # Safety +/// The pointer arguments must follow the Win32 `CreateProcessW` contract. +pub unsafe fn create_process_w( + app_name: *const u16, + command_line: *mut u16, + inherit_handles: i32, + creation_flags: u32, + env: *mut u16, + current_dir: *mut u16, + startup_info: *mut windows_sys::Win32::System::Threading::STARTUPINFOW, +) -> io::Result { + let mut procinfo = core::mem::MaybeUninit::::uninit(); + let ok = unsafe { + windows_sys::Win32::System::Threading::CreateProcessW( + app_name, + command_line, + core::ptr::null(), + core::ptr::null(), + inherit_handles, + creation_flags, + env.cast(), + current_dir, + startup_info, + procinfo.as_mut_ptr(), + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { procinfo.assume_init() }) + } +} + +#[allow( + clippy::too_many_arguments, + reason = "This is the semantic host wrapper for Win32 CreateProcess parameters." +)] +pub fn create_process( + app_name: *const u16, + command_line: *mut u16, + inherit_handles: i32, + creation_flags: u32, + env: *mut u16, + current_dir: *mut u16, + startup_info: StartupInfoData, + handle_list: Option>, +) -> io::Result { + let mut si: windows_sys::Win32::System::Threading::STARTUPINFOEXW = + unsafe { core::mem::zeroed() }; + si.StartupInfo.cb = core::mem::size_of_val(&si) as _; + si.StartupInfo.dwFlags = startup_info.flags; + si.StartupInfo.wShowWindow = startup_info.show_window; + si.StartupInfo.hStdInput = startup_info.std_input; + si.StartupInfo.hStdOutput = startup_info.std_output; + si.StartupInfo.hStdError = startup_info.std_error; + + let mut attrlist = create_handle_list_attribute_list(handle_list)?; + si.lpAttributeList = attrlist + .as_mut() + .map_or_else(core::ptr::null_mut, |l| l.as_mut_ptr() as _); + + let procinfo = unsafe { + create_process_w( + app_name, + command_line, + inherit_handles, + creation_flags | EXTENDED_STARTUPINFO_PRESENT_FLAG | CREATE_UNICODE_ENVIRONMENT_FLAG, + env, + current_dir, + &mut si as *mut _ as *mut _, + )? + }; + + Ok(ProcessInfo { + process: procinfo.hProcess, + thread: procinfo.hThread, + process_id: procinfo.dwProcessId, + thread_id: procinfo.dwThreadId, + }) +} + +pub fn create_junction(src: &Path, dst: &Path) -> io::Result<()> { + junction::create(src, dst) +} + +pub fn build_environment_block( + entries: Vec<(String, String)>, +) -> Result, BuildEnvironmentBlockError> { + use std::collections::HashMap; + + let mut last_entry: HashMap> = HashMap::new(); + for (key, value) in entries { + if key.contains('\0') || value.contains('\0') { + return Err(BuildEnvironmentBlockError::ContainsNul); + } + if key.is_empty() || key[1..].contains('=') { + return Err(BuildEnvironmentBlockError::IllegalName); + } + + let key_upper = key.to_uppercase(); + let mut entry: Vec = key.encode_utf16().collect(); + entry.push(b'=' as u16); + entry.extend(value.encode_utf16()); + entry.push(0); + last_entry.insert(key_upper, entry); + } + + let mut entries: Vec<(String, Vec)> = last_entry.into_iter().collect(); + entries.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut out = Vec::new(); + for (_, entry) in entries { + out.extend(entry); + } + if out.is_empty() { + out.push(0); + } + out.push(0); + Ok(out) +} + +pub fn create_handle_list_attribute_list( + handlelist: Option>, +) -> io::Result> { + let Some(handlelist) = handlelist else { + return Ok(None); + }; + + let mut size = 0; + let first = unsafe { + windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( + core::ptr::null_mut(), + 1, + 0, + &mut size, + ) + }; + if first != 0 + || unsafe { windows_sys::Win32::Foundation::GetLastError() } + != windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER + { + return Err(io::Error::last_os_error()); + } + + let mut attrs = AttrList { + handlelist, + attrlist: vec![0u8; size], + }; + let ok = unsafe { + windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( + attrs.attrlist.as_mut_ptr().cast(), + 1, + 0, + &mut size, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + + let ok = unsafe { + windows_sys::Win32::System::Threading::UpdateProcThreadAttribute( + attrs.attrlist.as_mut_ptr().cast(), + 0, + (2 & 0xffff) | 0x20000, + attrs.handlelist.as_mut_ptr().cast(), + (attrs.handlelist.len() * core::mem::size_of::()) as _, + core::ptr::null_mut(), + core::ptr::null(), + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(Some(attrs)) +} + +pub fn get_std_handle(std_handle: StdHandle) -> io::Result> { + let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else if handle.is_null() { + Ok(None) + } else { + Ok(Some(handle)) + } +} + +pub fn open_process( + desired_access: u32, + inherit_handle: bool, + process_id: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenProcess( + desired_access, + i32::from(inherit_handle), + process_id, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn create_pipe(size: u32) -> io::Result<(HANDLE, HANDLE)> { + let (read, write) = unsafe { + let mut read = core::mem::MaybeUninit::::uninit(); + let mut write = core::mem::MaybeUninit::::uninit(); + let ok = windows_sys::Win32::System::Pipes::CreatePipe( + read.as_mut_ptr(), + write.as_mut_ptr(), + core::ptr::null(), + size, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + (read.assume_init(), write.assume_init()) + }; + Ok((read, write)) +} + +pub fn create_event_w( + manual_reset: bool, + initial_state: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::CreateEventW( + core::ptr::null(), + i32::from(manual_reset), + i32::from(initial_state), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn set_event(handle: HANDLE) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Threading::SetEvent(handle) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn reset_event(handle: HANDLE) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Threading::ResetEvent(handle) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn wait_for_single_object(handle: HANDLE, milliseconds: u32) -> io::Result { + let ret = + unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(handle, milliseconds) }; + if ret == WAIT_FAILED { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn wait_for_multiple_objects( + handles: &[HANDLE], + wait_all: bool, + milliseconds: u32, +) -> io::Result { + let ret = unsafe { + windows_sys::Win32::System::Threading::WaitForMultipleObjects( + handles.len() as u32, + handles.as_ptr(), + i32::from(wait_all), + milliseconds, + ) + }; + if ret == WAIT_FAILED { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn batched_wait_for_multiple_objects( + handles: &[HANDLE], + wait_all: bool, + milliseconds: u32, + sigint_event: Option, +) -> Result { + use alloc::sync::Arc; + use core::sync::atomic::{AtomicU32, Ordering}; + use windows_sys::Win32::{ + Foundation::{CloseHandle, WAIT_ABANDONED_0}, + System::{ + SystemInformation::GetTickCount64, + Threading::{ + CreateThread, GetExitCodeThread, INFINITE, ResumeThread, TerminateThread, + WaitForMultipleObjects, + }, + }, + }; + + const MAXIMUM_WAIT_OBJECTS: usize = 64; + let batch_size = MAXIMUM_WAIT_OBJECTS - 1; + let mut batches: Vec<&[HANDLE]> = Vec::new(); + let mut i = 0; + while i < handles.len() { + let end = core::cmp::min(i + batch_size, handles.len()); + batches.push(&handles[i..end]); + i = end; + } + + if wait_all { + let mut err = None; + let deadline = if milliseconds != INFINITE { + Some(unsafe { GetTickCount64() } + milliseconds as u64) + } else { + None + }; + + for batch in &batches { + let timeout = if let Some(deadline) = deadline { + let now = unsafe { GetTickCount64() }; + if now >= deadline { + err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); + break; + } + (deadline - now) as u32 + } else { + INFINITE + }; + + let result = + unsafe { WaitForMultipleObjects(batch.len() as u32, batch.as_ptr(), 1, timeout) }; + if result == WAIT_FAILED { + err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + break; + } + if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); + break; + } + + if let Some(sigint_event) = sigint_event { + let sig_result = unsafe { + windows_sys::Win32::System::Threading::WaitForSingleObject(sigint_event, 0) + }; + if sig_result == WAIT_OBJECT_0 { + err = Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT); + break; + } + if sig_result == WAIT_FAILED { + err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + break; + } + } + } + + return match err { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) => Err(BatchedWaitError::Timeout), + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) => { + Err(BatchedWaitError::Interrupted) + } + Some(err) => Err(BatchedWaitError::Os(err)), + None => Ok(BatchedWaitResult::All), + }; + } + + let cancel_event = create_event_w(true, false, core::ptr::null()) + .map_err(|err| BatchedWaitError::Os(err.raw_os_error().unwrap_or_default() as u32))?; + + struct BatchData { + handles: Vec, + cancel_event: HANDLE, + handle_base: usize, + result: AtomicU32, + thread: core::cell::UnsafeCell, + } + + unsafe impl Send for BatchData {} + unsafe impl Sync for BatchData {} + + extern "system" fn batch_wait_thread(param: *mut core::ffi::c_void) -> u32 { + let data = unsafe { &*(param as *const BatchData) }; + let result = unsafe { + windows_sys::Win32::System::Threading::WaitForMultipleObjects( + data.handles.len() as u32, + data.handles.as_ptr(), + 0, + windows_sys::Win32::System::Threading::INFINITE, + ) + }; + data.result.store(result, Ordering::SeqCst); + + if result == WAIT_FAILED { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + let _ = set_event(data.cancel_event); + err + } else if (WAIT_ABANDONED_0..WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS as u32) + .contains(&result) + { + data.result.store(WAIT_FAILED, Ordering::SeqCst); + let _ = set_event(data.cancel_event); + windows_sys::Win32::Foundation::ERROR_ABANDONED_WAIT_0 + } else { + 0 + } + } + + let batch_data: Vec> = batches + .iter() + .enumerate() + .map(|(idx, batch)| { + let base = idx * batch_size; + let mut handles_with_cancel = batch.to_vec(); + handles_with_cancel.push(cancel_event); + Arc::new(BatchData { + handles: handles_with_cancel, + cancel_event, + handle_base: base, + result: AtomicU32::new(WAIT_FAILED), + thread: core::cell::UnsafeCell::new(core::ptr::null_mut()), + }) + }) + .collect(); + + let mut thread_handles: Vec = Vec::new(); + for data in &batch_data { + let thread = unsafe { + CreateThread( + core::ptr::null(), + 1, + Some(batch_wait_thread), + Arc::as_ptr(data) as *const _ as *mut _, + 4, + core::ptr::null_mut(), + ) + }; + if thread.is_null() { + for &handle in &thread_handles { + unsafe { TerminateThread(handle, 0) }; + unsafe { CloseHandle(handle) }; + } + unsafe { CloseHandle(cancel_event) }; + return Err(BatchedWaitError::Os( + io::Error::last_os_error() + .raw_os_error() + .unwrap_or_default() as u32, + )); + } + unsafe { *data.thread.get() = thread }; + thread_handles.push(thread); + } + + for &thread in &thread_handles { + unsafe { ResumeThread(thread) }; + } + + let mut thread_handles_raw = thread_handles.clone(); + if let Some(sigint_event) = sigint_event { + thread_handles_raw.push(sigint_event); + } + let result = unsafe { + WaitForMultipleObjects( + thread_handles_raw.len() as u32, + thread_handles_raw.as_ptr(), + 0, + milliseconds, + ) + }; + + let err = if result == WAIT_FAILED { + Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + } else if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) + } else if sigint_event.is_some() + && result == WAIT_OBJECT_0 + (thread_handles_raw.len() - 1) as u32 + { + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) + } else { + None + }; + + let _ = set_event(cancel_event); + unsafe { + WaitForMultipleObjects( + thread_handles.len() as u32, + thread_handles.as_ptr(), + 1, + INFINITE, + ) + }; + + let mut thread_err = err; + for data in &batch_data { + if thread_err.is_none() && data.result.load(Ordering::SeqCst) == WAIT_FAILED { + let mut exit_code = 0; + let thread = unsafe { *data.thread.get() }; + if unsafe { GetExitCodeThread(thread, &mut exit_code) } == 0 { + thread_err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + } else if exit_code != 0 { + thread_err = Some(exit_code); + } + } + let thread = unsafe { *data.thread.get() }; + unsafe { CloseHandle(thread) }; + } + unsafe { CloseHandle(cancel_event) }; + + match thread_err { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) => Err(BatchedWaitError::Timeout), + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) => { + Err(BatchedWaitError::Interrupted) + } + Some(err) => Err(BatchedWaitError::Os(err)), + None => { + let mut triggered_indices = Vec::new(); + for data in &batch_data { + let result = data.result.load(Ordering::SeqCst); + let triggered = result as i32 - WAIT_OBJECT_0 as i32; + if triggered >= 0 && (triggered as usize) < data.handles.len() - 1 { + triggered_indices.push(data.handle_base + triggered as usize); + } + } + Ok(BatchedWaitResult::Indices(triggered_indices)) + } + } +} + +pub fn duplicate_handle( + src_process: HANDLE, + src: HANDLE, + target_process: HANDLE, + access: u32, + inherit: i32, + options: u32, +) -> io::Result { + let target = unsafe { + let mut target = core::mem::MaybeUninit::::uninit(); + let ok = windows_sys::Win32::Foundation::DuplicateHandle( + src_process, + src, + target_process, + target.as_mut_ptr(), + access, + inherit, + options, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + target.assume_init() + }; + Ok(target) +} + #[must_use] pub fn get_current_process() -> HANDLE { unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() } } +pub fn get_exit_code_process(handle: HANDLE) -> io::Result { + let mut exit_code = core::mem::MaybeUninit::::uninit(); + let ok = unsafe { + windows_sys::Win32::System::Threading::GetExitCodeProcess(handle, exit_code.as_mut_ptr()) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { exit_code.assume_init() }) + } +} + +pub fn get_file_type(handle: HANDLE) -> io::Result { + let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(handle) }; + if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(file_type) + } +} + +pub fn terminate_process(handle: HANDLE, exit_code: u32) -> i32 { + unsafe { windows_sys::Win32::System::Threading::TerminateProcess(handle, exit_code) } +} + +pub fn exit_process(exit_code: u32) -> ! { + unsafe { windows_sys::Win32::System::Threading::ExitProcess(exit_code) } +} + #[must_use] pub fn get_last_error() -> u32 { unsafe { windows_sys::Win32::Foundation::GetLastError() } @@ -19,3 +797,604 @@ pub fn get_last_error() -> u32 { pub fn get_version() -> u32 { unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() } } + +pub fn create_job_object_w(name: *const u16) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::JobObjects::CreateJobObjectW(core::ptr::null(), name) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn assign_process_to_job_object(job: HANDLE, process: HANDLE) -> io::Result<()> { + let ok = + unsafe { windows_sys::Win32::System::JobObjects::AssignProcessToJobObject(job, process) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn terminate_job_object(job: HANDLE, exit_code: u32) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::JobObjects::TerminateJobObject(job, exit_code) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn set_job_object_kill_on_close(job: HANDLE) -> io::Result<()> { + use windows_sys::Win32::System::JobObjects::{ + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, + JobObjectExtendedLimitInformation, SetInformationJobObject, + }; + + let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { core::mem::zeroed() }; + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + let ok = unsafe { + SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + (&info as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(), + core::mem::size_of::() as u32, + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn get_module_file_name(module: HMODULE, buffer: &mut [u16]) -> u32 { + unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW( + module, + buffer.as_mut_ptr(), + buffer.len() as u32, + ) + } +} + +pub fn get_short_path_name_w(path: *const u16) -> io::Result> { + get_path_name_impl( + path, + windows_sys::Win32::Storage::FileSystem::GetShortPathNameW, + ) +} + +pub fn get_long_path_name_w(path: *const u16) -> io::Result> { + get_path_name_impl( + path, + windows_sys::Win32::Storage::FileSystem::GetLongPathNameW, + ) +} + +fn get_path_name_impl( + path: *const u16, + api_fn: unsafe extern "system" fn(*const u16, *mut u16, u32) -> u32, +) -> io::Result> { + let size = unsafe { api_fn(path, core::ptr::null_mut(), 0) }; + if size == 0 { + return Err(io::Error::last_os_error()); + } + + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { api_fn(path, buffer.as_mut_ptr(), buffer.len() as u32) }; + if result == 0 { + return Err(io::Error::last_os_error()); + } + buffer.truncate(result as usize); + Ok(buffer) +} + +pub fn open_mutex_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenMutexW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn release_mutex(handle: HANDLE) -> i32 { + unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle) } +} + +pub fn create_named_pipe_w( + name: *const u16, + open_mode: u32, + pipe_mode: u32, + max_instances: u32, + out_buffer_size: u32, + in_buffer_size: u32, + default_timeout: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Pipes::CreateNamedPipeW( + name, + open_mode, + pipe_mode, + max_instances, + out_buffer_size, + in_buffer_size, + default_timeout, + core::ptr::null(), + ) + }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn create_file_mapping_w( + file_handle: HANDLE, + protect: u32, + max_size_high: u32, + max_size_low: u32, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Memory::CreateFileMappingW( + file_handle, + core::ptr::null(), + protect, + max_size_high, + max_size_low, + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn open_file_mapping_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Memory::OpenFileMappingW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn map_view_of_file( + file_map: HANDLE, + desired_access: u32, + file_offset_high: u32, + file_offset_low: u32, + number_bytes: usize, +) -> io::Result { + let address = unsafe { + windows_sys::Win32::System::Memory::MapViewOfFile( + file_map, + desired_access, + file_offset_high, + file_offset_low, + number_bytes, + ) + }; + let ptr = address.Value; + if ptr.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(ptr as isize) + } +} + +pub fn unmap_view_of_file(address: isize) -> io::Result<()> { + let view = windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS { + Value: address as *mut core::ffi::c_void, + }; + let ok = unsafe { windows_sys::Win32::System::Memory::UnmapViewOfFile(view) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn virtual_query_size(address: isize) -> io::Result { + let mut mbi: windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION = + unsafe { core::mem::zeroed() }; + let ret = unsafe { + windows_sys::Win32::System::Memory::VirtualQuery( + address as *const core::ffi::c_void, + &mut mbi, + core::mem::size_of::(), + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(mbi.RegionSize) + } +} + +pub fn copy_file2(src: *const u16, dst: *const u16, flags: u32) -> io::Result<()> { + let mut params: windows_sys::Win32::Storage::FileSystem::COPYFILE2_EXTENDED_PARAMETERS = + unsafe { core::mem::zeroed() }; + params.dwSize = core::mem::size_of_val(¶ms) as u32; + params.dwCopyFlags = flags; + + let hr = unsafe { windows_sys::Win32::Storage::FileSystem::CopyFile2(src, dst, ¶ms) }; + if hr < 0 { + let err = if (hr as u32 >> 16) == 0x8007 { + (hr as u32) & 0xFFFF + } else { + hr as u32 + }; + Err(io::Error::from_raw_os_error(err as i32)) + } else { + Ok(()) + } +} + +pub fn read_windows_mimetype_registry_in_batches( + mut on_entries: F, +) -> Result<(), MimeRegistryReadError> +where + F: FnMut(&mut Vec<(String, String)>) -> Result<(), E>, +{ + use windows_sys::Win32::System::Registry::{ + HKEY, HKEY_CLASSES_ROOT, KEY_READ, REG_SZ, RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, + RegQueryValueExW, + }; + + let mut hkcr: HKEY = core::ptr::null_mut(); + let err = + unsafe { RegOpenKeyExW(HKEY_CLASSES_ROOT, core::ptr::null(), 0, KEY_READ, &mut hkcr) }; + if err != 0 { + return Err(MimeRegistryReadError::Os(err)); + } + + let mut index = 0; + let mut entries = Vec::new(); + loop { + let mut ext_buf = [0u16; 128]; + let mut cch_ext = ext_buf.len() as u32; + let err = unsafe { + RegEnumKeyExW( + hkcr, + index, + ext_buf.as_mut_ptr(), + &mut cch_ext, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + }; + index += 1; + + if err == windows_sys::Win32::Foundation::ERROR_NO_MORE_ITEMS { + break; + } + if err != 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { + unsafe { RegCloseKey(hkcr) }; + return Err(MimeRegistryReadError::Os(err)); + } + if cch_ext == 0 || ext_buf[0] != b'.' as u16 { + continue; + } + + let ext_wide = &ext_buf[..cch_ext as usize]; + let mut subkey: HKEY = core::ptr::null_mut(); + let err = unsafe { RegOpenKeyExW(hkcr, ext_buf.as_ptr(), 0, KEY_READ, &mut subkey) }; + if err == windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND + || err == windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED + { + continue; + } + if err != 0 { + unsafe { RegCloseKey(hkcr) }; + return Err(MimeRegistryReadError::Os(err)); + } + + let content_type_key: Vec = "Content Type\0".encode_utf16().collect(); + let mut type_buf = [0u16; 256]; + let mut cb_type = (type_buf.len() * 2) as u32; + let mut reg_type = 0; + let err = unsafe { + RegQueryValueExW( + subkey, + content_type_key.as_ptr(), + core::ptr::null_mut(), + &mut reg_type, + type_buf.as_mut_ptr().cast(), + &mut cb_type, + ) + }; + unsafe { RegCloseKey(subkey) }; + + if err != 0 || reg_type != REG_SZ || cb_type == 0 { + continue; + } + + let type_len = (cb_type as usize / 2).saturating_sub(1); + let type_str = String::from_utf16_lossy(&type_buf[..type_len]); + let ext_str = String::from_utf16_lossy(ext_wide); + if type_str.is_empty() { + continue; + } + + entries.push((type_str, ext_str)); + if entries.len() >= 64 { + on_entries(&mut entries).map_err(MimeRegistryReadError::Callback)?; + } + } + + unsafe { RegCloseKey(hkcr) }; + if !entries.is_empty() { + on_entries(&mut entries).map_err(MimeRegistryReadError::Callback)?; + } + Ok(()) +} + +pub fn lc_map_string_ex( + locale: *const u16, + flags: u32, + src: *const u16, + src_len: i32, +) -> io::Result> { + let dest_size = unsafe { + windows_sys::Win32::Globalization::LCMapStringEx( + locale, + flags, + src, + src_len, + core::ptr::null_mut(), + 0, + core::ptr::null(), + core::ptr::null(), + 0, + ) + }; + if dest_size <= 0 { + return Err(io::Error::last_os_error()); + } + + let mut dest = vec![0u16; dest_size as usize]; + let nmapped = unsafe { + windows_sys::Win32::Globalization::LCMapStringEx( + locale, + flags, + src, + src_len, + dest.as_mut_ptr(), + dest_size, + core::ptr::null(), + core::ptr::null(), + 0, + ) + }; + if nmapped <= 0 { + return Err(io::Error::last_os_error()); + } + dest.truncate(nmapped as usize); + Ok(dest) +} + +pub fn connect_named_pipe(handle: HANDLE) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::Pipes::ConnectNamedPipe(handle, core::ptr::null_mut()) + }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_PIPE_CONNECTED { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + Ok(()) +} + +pub fn wait_named_pipe_w(name: *const u16, timeout: u32) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Pipes::WaitNamedPipeW(name, timeout) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn peek_named_pipe(handle: HANDLE, size: Option) -> io::Result { + let mut available = 0; + let mut left_this_message = 0; + match size { + Some(size) => { + let mut data = vec![0u8; size as usize]; + let mut read = 0; + let ok = unsafe { + windows_sys::Win32::System::Pipes::PeekNamedPipe( + handle, + data.as_mut_ptr().cast(), + size, + &mut read, + &mut available, + &mut left_this_message, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + data.truncate(read as usize); + Ok(PeekNamedPipeResult { + data: Some(data), + available, + left_this_message, + }) + } + None => { + let ok = unsafe { + windows_sys::Win32::System::Pipes::PeekNamedPipe( + handle, + core::ptr::null_mut(), + 0, + core::ptr::null_mut(), + &mut available, + &mut left_this_message, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + Ok(PeekNamedPipeResult { + data: None, + available, + left_this_message, + }) + } + } +} + +pub fn write_file(handle: HANDLE, buffer: &[u8]) -> io::Result { + let len = core::cmp::min(buffer.len(), u32::MAX as usize) as u32; + let mut written = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::WriteFile( + handle, + buffer.as_ptr().cast(), + len, + &mut written, + core::ptr::null_mut(), + ) + }; + let err = if ret == 0 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } else { + 0 + }; + if ret == 0 { + Err(io::Error::from_raw_os_error(err as i32)) + } else { + Ok(WriteFileResult { + written, + error: err, + }) + } +} + +pub fn read_file(handle: HANDLE, size: u32) -> io::Result { + let mut data = vec![0u8; size as usize]; + let mut read = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::ReadFile( + handle, + data.as_mut_ptr().cast(), + size, + &mut read, + core::ptr::null_mut(), + ) + }; + let err = if ret == 0 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } else { + 0 + }; + if ret == 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { + return Err(io::Error::from_raw_os_error(err as i32)); + } + data.truncate(read as usize); + Ok(ReadFileResult { data, error: err }) +} + +pub fn set_named_pipe_handle_state( + handle: HANDLE, + mode: Option, + max_collection_count: Option, + collect_data_timeout: Option, +) -> io::Result<()> { + let mut dw_args = [ + mode.unwrap_or_default(), + max_collection_count.unwrap_or_default(), + collect_data_timeout.unwrap_or_default(), + ]; + let mut p_args = [core::ptr::null_mut(); 3]; + for (index, arg) in [mode, max_collection_count, collect_data_timeout] + .into_iter() + .enumerate() + { + if arg.is_some() { + p_args[index] = &mut dw_args[index]; + } + } + let ok = unsafe { + windows_sys::Win32::System::Pipes::SetNamedPipeHandleState( + handle, p_args[0], p_args[1], p_args[2], + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn create_mutex_w(initial_owner: bool, name: *const u16) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::CreateMutexW( + core::ptr::null(), + i32::from(initial_owner), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn open_event_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenEventW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn need_current_directory_for_exe_path_w(exe_name: *const u16) -> bool { + unsafe { + windows_sys::Win32::System::Environment::NeedCurrentDirectoryForExePathW(exe_name) != 0 + } +} diff --git a/crates/host_env/src/windows.rs b/crates/host_env/src/windows.rs index 9667edf9149..e43cd1aa9f6 100644 --- a/crates/host_env/src/windows.rs +++ b/crates/host_env/src/windows.rs @@ -1,11 +1,314 @@ use rustpython_wtf8::Wtf8; use std::{ ffi::{OsStr, OsString}, + io, os::windows::ffi::{OsStrExt, OsStringExt}, }; +use windows_sys::Win32::{ + Foundation::{ + E_POINTER, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FLAGS, ERROR_NO_UNICODE_TRANSLATION, + MAX_PATH, S_OK, + }, + Networking::WinSock::WSAStartup, + Storage::FileSystem::{ + GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FIXEDFILEINFO, VerQueryValueW, + }, + System::{ + Diagnostics::Debug::{ + FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, + }, + LibraryLoader::{GetModuleFileNameW, GetModuleHandleW}, + SystemInformation::{GetVersionExW, OSVERSIONINFOEXW, OSVERSIONINFOW}, + Threading::{GetCurrentThreadStackLimits, SetThreadStackGuarantee}, + }, +}; /// _MAX_ENV from Windows CRT stdlib.h - maximum environment variable size pub const _MAX_ENV: usize = 32767; +pub const HRESULT_E_POINTER: i32 = E_POINTER; +pub const HRESULT_S_OK: i32 = S_OK; +pub const CP_ACP: u32 = windows_sys::Win32::Globalization::CP_ACP; +pub const CP_OEMCP: u32 = windows_sys::Win32::Globalization::CP_OEMCP; +pub const CP_UTF7: u32 = windows_sys::Win32::Globalization::CP_UTF7; +pub const CP_UTF8: u32 = windows_sys::Win32::Globalization::CP_UTF8; +pub const MB_ERR_INVALID_CHARS: u32 = windows_sys::Win32::Globalization::MB_ERR_INVALID_CHARS; +pub const WC_ERR_INVALID_CHARS: u32 = windows_sys::Win32::Globalization::WC_ERR_INVALID_CHARS; +pub const WC_NO_BEST_FIT_CHARS: u32 = windows_sys::Win32::Globalization::WC_NO_BEST_FIT_CHARS; +pub const ERROR_INVALID_FLAGS_I32: i32 = ERROR_INVALID_FLAGS as i32; +pub const ERROR_NO_UNICODE_TRANSLATION_I32: i32 = ERROR_NO_UNICODE_TRANSLATION as i32; +pub const ERROR_INSUFFICIENT_BUFFER_I32: i32 = ERROR_INSUFFICIENT_BUFFER as i32; + +pub fn init_winsock() { + static WSA_INIT: parking_lot::Once = parking_lot::Once::new(); + WSA_INIT.call_once(|| unsafe { + let mut wsa_data = core::mem::MaybeUninit::uninit(); + let _ = WSAStartup(0x0101, wsa_data.as_mut_ptr()); + }) +} + +#[derive(Clone, Debug)] +pub struct WindowsVersionInfo { + pub major: u32, + pub minor: u32, + pub build: u32, + pub platform: u32, + pub service_pack: String, + pub service_pack_major: u16, + pub service_pack_minor: u16, + pub suite_mask: u16, + pub product_type: u8, +} + +fn get_kernel32_version() -> io::Result<(u32, u32, u32)> { + unsafe { + let module_name: Vec = OsStr::new("kernel32.dll").to_wide_with_nul(); + let h_kernel32 = GetModuleHandleW(module_name.as_ptr()); + if h_kernel32.is_null() { + return Err(io::Error::last_os_error()); + } + + let mut kernel32_path = [0u16; MAX_PATH as usize]; + let len = GetModuleFileNameW( + h_kernel32, + kernel32_path.as_mut_ptr(), + kernel32_path.len() as u32, + ); + if len == 0 { + return Err(io::Error::last_os_error()); + } + + let ver_block_size = GetFileVersionInfoSizeW(kernel32_path.as_ptr(), core::ptr::null_mut()); + if ver_block_size == 0 { + return Err(io::Error::last_os_error()); + } + + let mut ver_block = vec![0u8; ver_block_size as usize]; + if GetFileVersionInfoW( + kernel32_path.as_ptr(), + 0, + ver_block_size, + ver_block.as_mut_ptr() as *mut _, + ) == 0 + { + return Err(io::Error::last_os_error()); + } + + let sub_block: Vec = OsStr::new("").to_wide_with_nul(); + + let mut ffi_ptr: *mut VS_FIXEDFILEINFO = core::ptr::null_mut(); + let mut ffi_len: u32 = 0; + if VerQueryValueW( + ver_block.as_ptr() as *const _, + sub_block.as_ptr(), + &mut ffi_ptr as *mut *mut VS_FIXEDFILEINFO as *mut *mut _, + &mut ffi_len as *mut u32, + ) == 0 + || ffi_ptr.is_null() + { + return Err(io::Error::last_os_error()); + } + + let ffi = *ffi_ptr; + let real_major = (ffi.dwProductVersionMS >> 16) & 0xFFFF; + let real_minor = ffi.dwProductVersionMS & 0xFFFF; + let real_build = (ffi.dwProductVersionLS >> 16) & 0xFFFF; + + Ok((real_major, real_minor, real_build)) + } +} + +pub fn get_windows_version() -> io::Result { + let mut version: OSVERSIONINFOEXW = unsafe { core::mem::zeroed() }; + version.dwOSVersionInfoSize = core::mem::size_of::() as u32; + let result = unsafe { + let os_vi = &mut version as *mut OSVERSIONINFOEXW as *mut OSVERSIONINFOW; + GetVersionExW(os_vi) + }; + + if result == 0 { + return Err(io::Error::last_os_error()); + } + + let service_pack = { + let (last, _) = version + .szCSDVersion + .iter() + .take_while(|&x| x != &0) + .enumerate() + .last() + .unwrap_or((0, &0)); + let sp = OsString::from_wide(&version.szCSDVersion[..last]); + sp.into_string() + .map_err(|_| io::Error::other("service pack is not ASCII"))? + }; + let (major, minor, build) = get_kernel32_version()?; + Ok(WindowsVersionInfo { + major, + minor, + build, + platform: version.dwPlatformId, + service_pack, + service_pack_major: version.wServicePackMajor, + service_pack_minor: version.wServicePackMinor, + suite_mask: version.wSuiteMask, + product_type: version.wProductType, + }) +} + +pub fn current_thread_stack_bounds() -> (usize, usize) { + let mut low: usize = 0; + let mut high: usize = 0; + unsafe { + GetCurrentThreadStackLimits(&mut low as *mut usize, &mut high as *mut usize); + let mut guarantee: u32 = 0; + SetThreadStackGuarantee(&mut guarantee); + low += guarantee as usize; + } + (low, high) +} + +pub fn set_last_error(error: u32) { + unsafe { windows_sys::Win32::Foundation::SetLastError(error) } +} + +pub fn get_last_error() -> u32 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } +} + +pub fn format_error_message(code: Option) -> Option { + let error_code = code.unwrap_or_else(get_last_error); + let mut buffer: *mut u16 = core::ptr::null_mut(); + let len = unsafe { + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + core::ptr::null(), + error_code, + 0, + &mut buffer as *mut *mut u16 as *mut u16, + 0, + core::ptr::null(), + ) + }; + + if len == 0 || buffer.is_null() { + return None; + } + + let message = unsafe { + let slice = core::slice::from_raw_parts(buffer, len as usize); + let msg = String::from_utf16_lossy(slice).trim_end().to_string(); + windows_sys::Win32::Foundation::LocalFree(buffer as *mut _); + msg + }; + Some(message) +} + +pub fn wide_char_to_multi_byte_len( + code_page: u32, + flags: u32, + wide: &[u16], + track_default_char: bool, +) -> io::Result<(usize, bool)> { + let mut used_default_char = 0i32; + let pused = if track_default_char { + &mut used_default_char as *mut i32 + } else { + core::ptr::null_mut() + }; + let size = unsafe { + windows_sys::Win32::Globalization::WideCharToMultiByte( + code_page, + flags, + wide.as_ptr(), + wide.len() as i32, + core::ptr::null_mut(), + 0, + core::ptr::null(), + pused, + ) + }; + if size <= 0 { + Err(io::Error::last_os_error()) + } else { + Ok((size as usize, used_default_char != 0)) + } +} + +pub fn wide_char_to_multi_byte( + code_page: u32, + flags: u32, + wide: &[u16], + out: &mut [u8], + track_default_char: bool, +) -> io::Result<(usize, bool)> { + let mut used_default_char = 0i32; + let pused = if track_default_char { + &mut used_default_char as *mut i32 + } else { + core::ptr::null_mut() + }; + let size = unsafe { + windows_sys::Win32::Globalization::WideCharToMultiByte( + code_page, + flags, + wide.as_ptr(), + wide.len() as i32, + out.as_mut_ptr().cast(), + out.len() as i32, + core::ptr::null(), + pused, + ) + }; + if size <= 0 { + Err(io::Error::last_os_error()) + } else { + Ok((size as usize, used_default_char != 0)) + } +} + +pub fn multi_byte_to_wide_len(code_page: u32, flags: u32, bytes: &[u8]) -> io::Result { + let size = unsafe { + windows_sys::Win32::Globalization::MultiByteToWideChar( + code_page, + flags, + bytes.as_ptr().cast(), + bytes.len() as i32, + core::ptr::null_mut(), + 0, + ) + }; + if size <= 0 { + Err(io::Error::last_os_error()) + } else { + Ok(size as usize) + } +} + +pub fn multi_byte_to_wide( + code_page: u32, + flags: u32, + bytes: &[u8], + out: &mut [u16], +) -> io::Result { + let size = unsafe { + windows_sys::Win32::Globalization::MultiByteToWideChar( + code_page, + flags, + bytes.as_ptr().cast(), + bytes.len() as i32, + out.as_mut_ptr(), + out.len() as i32, + ) + }; + if size <= 0 { + Err(io::Error::last_os_error()) + } else { + Ok(size as usize) + } +} pub trait ToWideString { fn to_wide(&self) -> Vec; diff --git a/crates/host_env/src/winreg.rs b/crates/host_env/src/winreg.rs new file mode 100644 index 00000000000..fa95b066b1d --- /dev/null +++ b/crates/host_env/src/winreg.rs @@ -0,0 +1,516 @@ +#![allow( + clippy::missing_safety_doc, + reason = "This module intentionally exposes raw Win32 registry wrappers." +)] +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "These wrappers mirror Win32 APIs that operate on caller-provided pointers." +)] +#![allow( + clippy::too_many_arguments, + reason = "These helpers preserve the underlying Win32 registry call shapes." +)] + +extern crate alloc; + +use alloc::string::FromUtf16Error; +use std::ffi::OsStr; + +use crate::windows::ToWideString; +use windows_sys::Win32::{ + Foundation, + Security::SECURITY_ATTRIBUTES, + System::{Environment, Registry}, +}; + +pub type HKEY = Registry::HKEY; +pub const ERROR_MORE_DATA: u32 = Foundation::ERROR_MORE_DATA; +pub const ERROR_INVALID_HANDLE: u32 = Foundation::ERROR_INVALID_HANDLE; +pub const HKEY_CLASSES_ROOT: HKEY = Registry::HKEY_CLASSES_ROOT; +pub const HKEY_CURRENT_USER: HKEY = Registry::HKEY_CURRENT_USER; +pub const HKEY_LOCAL_MACHINE: HKEY = Registry::HKEY_LOCAL_MACHINE; +pub const HKEY_USERS: HKEY = Registry::HKEY_USERS; +pub const HKEY_PERFORMANCE_DATA: HKEY = Registry::HKEY_PERFORMANCE_DATA; +pub const HKEY_CURRENT_CONFIG: HKEY = Registry::HKEY_CURRENT_CONFIG; +pub const HKEY_DYN_DATA: HKEY = Registry::HKEY_DYN_DATA; + +pub const KEY_ALL_ACCESS: u32 = Registry::KEY_ALL_ACCESS; +pub const KEY_CREATE_LINK: u32 = Registry::KEY_CREATE_LINK; +pub const KEY_CREATE_SUB_KEY: u32 = Registry::KEY_CREATE_SUB_KEY; +pub const KEY_ENUMERATE_SUB_KEYS: u32 = Registry::KEY_ENUMERATE_SUB_KEYS; +pub const KEY_EXECUTE: u32 = Registry::KEY_EXECUTE; +pub const KEY_NOTIFY: u32 = Registry::KEY_NOTIFY; +pub const KEY_QUERY_VALUE: u32 = Registry::KEY_QUERY_VALUE; +pub const KEY_READ: u32 = Registry::KEY_READ; +pub const KEY_SET_VALUE: u32 = Registry::KEY_SET_VALUE; +pub const KEY_WOW64_32KEY: u32 = Registry::KEY_WOW64_32KEY; +pub const KEY_WOW64_64KEY: u32 = Registry::KEY_WOW64_64KEY; +pub const KEY_WRITE: u32 = Registry::KEY_WRITE; + +pub const REG_BINARY: u32 = Registry::REG_BINARY; +pub const REG_CREATED_NEW_KEY: u32 = Registry::REG_CREATED_NEW_KEY; +pub const REG_DWORD: u32 = Registry::REG_DWORD; +pub const REG_DWORD_BIG_ENDIAN: u32 = Registry::REG_DWORD_BIG_ENDIAN; +pub const REG_DWORD_LITTLE_ENDIAN: u32 = Registry::REG_DWORD_LITTLE_ENDIAN; +pub const REG_EXPAND_SZ: u32 = Registry::REG_EXPAND_SZ; +pub const REG_FULL_RESOURCE_DESCRIPTOR: u32 = Registry::REG_FULL_RESOURCE_DESCRIPTOR; +pub const REG_LINK: u32 = Registry::REG_LINK; +pub const REG_MULTI_SZ: u32 = Registry::REG_MULTI_SZ; +pub const REG_NONE: u32 = Registry::REG_NONE; +pub const REG_NOTIFY_CHANGE_ATTRIBUTES: u32 = Registry::REG_NOTIFY_CHANGE_ATTRIBUTES; +pub const REG_NOTIFY_CHANGE_LAST_SET: u32 = Registry::REG_NOTIFY_CHANGE_LAST_SET; +pub const REG_NOTIFY_CHANGE_NAME: u32 = Registry::REG_NOTIFY_CHANGE_NAME; +pub const REG_NOTIFY_CHANGE_SECURITY: u32 = Registry::REG_NOTIFY_CHANGE_SECURITY; +pub const REG_OPENED_EXISTING_KEY: u32 = Registry::REG_OPENED_EXISTING_KEY; +pub const REG_OPTION_BACKUP_RESTORE: u32 = Registry::REG_OPTION_BACKUP_RESTORE; +pub const REG_OPTION_CREATE_LINK: u32 = Registry::REG_OPTION_CREATE_LINK; +pub const REG_OPTION_NON_VOLATILE: u32 = Registry::REG_OPTION_NON_VOLATILE; +pub const REG_OPTION_OPEN_LINK: u32 = Registry::REG_OPTION_OPEN_LINK; +pub const REG_OPTION_RESERVED: u32 = Registry::REG_OPTION_RESERVED; +pub const REG_OPTION_VOLATILE: u32 = Registry::REG_OPTION_VOLATILE; +pub const REG_QWORD: u32 = Registry::REG_QWORD; +pub const REG_QWORD_LITTLE_ENDIAN: u32 = Registry::REG_QWORD_LITTLE_ENDIAN; +pub const REG_RESOURCE_LIST: u32 = Registry::REG_RESOURCE_LIST; +pub const REG_RESOURCE_REQUIREMENTS_LIST: u32 = Registry::REG_RESOURCE_REQUIREMENTS_LIST; +pub const REG_SZ: u32 = Registry::REG_SZ; +pub const REG_WHOLE_HIVE_VOLATILE: u32 = Registry::REG_WHOLE_HIVE_VOLATILE as u32; +pub const REG_REFRESH_HIVE: u32 = 0x00000002; +pub const REG_NO_LAZY_FLUSH: u32 = 0x00000004; +pub const REG_LEGAL_OPTION: u32 = Registry::REG_OPTION_RESERVED + | Registry::REG_OPTION_NON_VOLATILE + | Registry::REG_OPTION_VOLATILE + | Registry::REG_OPTION_CREATE_LINK + | Registry::REG_OPTION_BACKUP_RESTORE + | Registry::REG_OPTION_OPEN_LINK; +pub const REG_LEGAL_CHANGE_FILTER: u32 = Registry::REG_NOTIFY_CHANGE_NAME + | Registry::REG_NOTIFY_CHANGE_ATTRIBUTES + | Registry::REG_NOTIFY_CHANGE_LAST_SET + | Registry::REG_NOTIFY_CHANGE_SECURITY; + +pub fn bytes_as_wide_slice(bytes: &[u8]) -> &[u16] { + let (prefix, u16_slice, suffix) = unsafe { bytes.align_to::() }; + debug_assert!( + prefix.is_empty() && suffix.is_empty(), + "Registry data should be u16-aligned" + ); + u16_slice +} + +pub fn close_key(hkey: Registry::HKEY) -> u32 { + unsafe { Registry::RegCloseKey(hkey) } +} + +pub unsafe fn connect_registry( + computer_name: *const u16, + key: Registry::HKEY, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegConnectRegistryW(computer_name, key, out_key) } +} + +pub unsafe fn create_key( + key: Registry::HKEY, + sub_key: *const u16, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegCreateKeyW(key, sub_key, out_key) } +} + +pub unsafe fn create_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + reserved: u32, + class: *mut u16, + options: u32, + sam: u32, + security: *const SECURITY_ATTRIBUTES, + result: *mut Registry::HKEY, + disposition: *mut u32, +) -> u32 { + unsafe { + Registry::RegCreateKeyExW( + key, + sub_key, + reserved, + class, + options, + sam, + security, + result, + disposition, + ) + } +} + +pub unsafe fn delete_key(key: Registry::HKEY, sub_key: *const u16) -> u32 { + unsafe { Registry::RegDeleteKeyW(key, sub_key) } +} + +pub unsafe fn delete_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + sam: u32, + reserved: u32, +) -> u32 { + unsafe { Registry::RegDeleteKeyExW(key, sub_key, sam, reserved) } +} + +pub unsafe fn delete_value(key: Registry::HKEY, value_name: *const u16) -> u32 { + unsafe { Registry::RegDeleteValueW(key, value_name) } +} + +pub unsafe fn enum_key_ex( + key: Registry::HKEY, + index: u32, + name: *mut u16, + name_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegEnumKeyExW( + key, + index, + name, + name_len, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + } +} + +pub unsafe fn query_info_key( + key: Registry::HKEY, + sub_keys: *mut u32, + values: *mut u32, + max_value_name_len: *mut u32, + max_value_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegQueryInfoKeyW( + key, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + sub_keys, + core::ptr::null_mut(), + core::ptr::null_mut(), + values, + max_value_name_len, + max_value_len, + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + } +} + +pub struct QueryInfo { + pub sub_keys: u32, + pub values: u32, + pub last_write_time: u64, +} + +pub fn query_info_key_full(key: Registry::HKEY) -> Result { + let mut sub_keys = 0; + let mut values = 0; + let mut last_write_time: Foundation::FILETIME = unsafe { core::mem::zeroed() }; + let err = unsafe { + Registry::RegQueryInfoKeyW( + key, + core::ptr::null_mut(), + core::ptr::null_mut(), + 0 as _, + &mut sub_keys, + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut values, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut last_write_time, + ) + }; + if err != 0 { + return Err(err); + } + Ok(QueryInfo { + sub_keys, + values, + last_write_time: ((last_write_time.dwHighDateTime as u64) << 32) + | last_write_time.dwLowDateTime as u64, + }) +} + +pub unsafe fn enum_value( + key: Registry::HKEY, + index: u32, + value_name: *mut u16, + value_name_len: *mut u32, + value_type: *mut u32, + data: *mut u8, + data_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegEnumValueW( + key, + index, + value_name, + value_name_len, + core::ptr::null_mut(), + value_type, + data, + data_len, + ) + } +} + +pub fn flush_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegFlushKey(key) } +} + +pub unsafe fn load_key(key: Registry::HKEY, sub_key: *const u16, file_name: *const u16) -> u32 { + unsafe { Registry::RegLoadKeyW(key, sub_key, file_name) } +} + +pub unsafe fn open_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + options: u32, + sam: u32, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegOpenKeyExW(key, sub_key, options, sam, out_key) } +} + +pub unsafe fn query_value_ex( + key: Registry::HKEY, + value_name: *const u16, + value_type: *mut u32, + data: *mut u8, + data_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegQueryValueExW( + key, + value_name, + core::ptr::null_mut(), + value_type, + data, + data_len, + ) + } +} + +pub unsafe fn save_key(key: Registry::HKEY, file_name: *const u16) -> u32 { + unsafe { Registry::RegSaveKeyW(key, file_name, core::ptr::null_mut()) } +} + +pub unsafe fn set_value_ex( + key: Registry::HKEY, + value_name: *const u16, + typ: u32, + ptr: *const u8, + len: u32, +) -> u32 { + unsafe { Registry::RegSetValueExW(key, value_name, 0, typ, ptr, len) } +} + +pub fn disable_reflection_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegDisableReflectionKey(key) } +} + +pub fn enable_reflection_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegEnableReflectionKey(key) } +} + +pub unsafe fn query_reflection_key(key: Registry::HKEY, result: *mut i32) -> u32 { + unsafe { Registry::RegQueryReflectionKey(key, result) } +} + +pub enum ExpandEnvironmentStringsError { + Os, + Utf16(FromUtf16Error), +} + +pub enum QueryStringError { + Code(u32), + Utf16(FromUtf16Error), +} + +pub fn query_default_value( + hkey: Registry::HKEY, + sub_key: Option<&OsStr>, +) -> Result { + let child_key = if let Some(sub_key) = sub_key.filter(|s| !s.is_empty()) { + let wide_sub_key = sub_key.to_wide_with_nul(); + let mut out_key = core::ptr::null_mut(); + let res = unsafe { + open_key_ex( + hkey, + wide_sub_key.as_ptr(), + 0, + Registry::KEY_QUERY_VALUE, + &mut out_key, + ) + }; + if res != 0 { + return Err(QueryStringError::Code(res)); + } + Some(out_key) + } else { + None + }; + + let target_key = child_key.unwrap_or(hkey); + let mut buf_size: u32 = 256; + let mut buffer: Vec = vec![0; buf_size as usize]; + let mut reg_type: u32 = 0; + + let result = loop { + let mut size = buf_size; + let res = unsafe { + query_value_ex( + target_key, + core::ptr::null(), + &mut reg_type, + buffer.as_mut_ptr(), + &mut size, + ) + }; + if res == Foundation::ERROR_MORE_DATA { + buf_size *= 2; + buffer.resize(buf_size as usize, 0); + continue; + } + if res == Foundation::ERROR_FILE_NOT_FOUND { + break Ok(String::new()); + } + if res != 0 { + break Err(QueryStringError::Code(res)); + } + if reg_type != Registry::REG_SZ { + break Err(QueryStringError::Code(Foundation::ERROR_INVALID_DATA)); + } + + let u16_slice = bytes_as_wide_slice(&buffer[..size as usize]); + let len = u16_slice + .iter() + .position(|&c| c == 0) + .unwrap_or(u16_slice.len()); + break String::from_utf16(&u16_slice[..len]).map_err(QueryStringError::Utf16); + }; + + if let Some(ck) = child_key { + close_key(ck); + } + + result +} + +pub fn query_value_bytes(hkey: Registry::HKEY, value_name: &OsStr) -> Result<(Vec, u32), u32> { + let wide_name = value_name.to_wide_with_nul(); + let mut buf_size: u32 = 0; + let res = unsafe { + query_value_ex( + hkey, + wide_name.as_ptr(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut buf_size, + ) + }; + if res == Foundation::ERROR_MORE_DATA || buf_size == 0 { + buf_size = 256; + } else if res != 0 { + return Err(res); + } + + let mut ret_buf = vec![0u8; buf_size as usize]; + let mut typ = 0; + + loop { + let mut ret_size = buf_size; + let res = unsafe { + query_value_ex( + hkey, + wide_name.as_ptr(), + &mut typ, + ret_buf.as_mut_ptr(), + &mut ret_size, + ) + }; + if res != Foundation::ERROR_MORE_DATA { + if res != 0 { + return Err(res); + } + ret_buf.truncate(ret_size as usize); + return Ok((ret_buf, typ)); + } + buf_size *= 2; + ret_buf.resize(buf_size as usize, 0); + } +} + +pub fn set_default_value(hkey: Registry::HKEY, sub_key: &OsStr, typ: u32, value: &OsStr) -> u32 { + let child_key = if !sub_key.is_empty() { + let wide_sub_key = sub_key.to_wide_with_nul(); + let mut out_key = core::ptr::null_mut(); + let res = unsafe { + create_key_ex( + hkey, + wide_sub_key.as_ptr(), + 0, + core::ptr::null_mut(), + 0, + Registry::KEY_SET_VALUE, + core::ptr::null(), + &mut out_key, + core::ptr::null_mut(), + ) + }; + if res != 0 { + return res; + } + Some(out_key) + } else { + None + }; + + let target_key = child_key.unwrap_or(hkey); + let wide_value = value.to_wide_with_nul(); + let res = unsafe { + set_value_ex( + target_key, + core::ptr::null(), + typ, + wide_value.as_ptr() as *const u8, + (wide_value.len() * 2) as u32, + ) + }; + + if let Some(ck) = child_key { + close_key(ck); + } + res +} + +pub fn expand_environment_strings(input: &OsStr) -> Result { + let wide_input = input.to_wide_with_nul(); + let required_size = unsafe { + Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0) + }; + if required_size == 0 { + return Err(ExpandEnvironmentStringsError::Os); + } + + let mut out = vec![0u16; required_size as usize]; + let written = unsafe { + Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size) + }; + if written == 0 { + return Err(ExpandEnvironmentStringsError::Os); + } + + let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); + String::from_utf16(&out[..len]).map_err(ExpandEnvironmentStringsError::Utf16) +} diff --git a/crates/host_env/src/wmi.rs b/crates/host_env/src/wmi.rs new file mode 100644 index 00000000000..1165a756306 --- /dev/null +++ b/crates/host_env/src/wmi.rs @@ -0,0 +1,676 @@ +#![allow( + clippy::upper_case_acronyms, + reason = "These names mirror the Windows COM and ABI types they wrap." +)] +#![allow(non_snake_case)] +#![allow(unsafe_op_in_unsafe_fn)] + +use core::ffi::c_void; +use core::ptr::{null, null_mut}; +use windows_sys::Win32::Foundation::{ + CloseHandle, ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_NOT_ENOUGH_MEMORY, GetLastError, HANDLE, + WAIT_OBJECT_0, WAIT_TIMEOUT, +}; +use windows_sys::Win32::Storage::FileSystem::{ReadFile, WriteFile}; +use windows_sys::Win32::System::Pipes::CreatePipe; +use windows_sys::Win32::System::Threading::{ + CreateEventW, CreateThread, GetExitCodeThread, SetEvent, WaitForSingleObject, +}; + +const BUFFER_SIZE: usize = 8192; + +pub enum ExecQueryError { + MoreData, + Code(u32), +} + +type HRESULT = i32; + +#[repr(C)] +struct GUID { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} + +#[repr(C, align(8))] +struct VARIANT([u64; 3]); + +impl VARIANT { + fn zeroed() -> Self { + Self([0u64; 3]) + } +} + +const CLSID_WBEM_LOCATOR: GUID = GUID { + data1: 0x4590F811, + data2: 0x1D3A, + data3: 0x11D0, + data4: [0x89, 0x1F, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], +}; + +const IID_IWBEM_LOCATOR: GUID = GUID { + data1: 0xDC12A687, + data2: 0x737F, + data3: 0x11CF, + data4: [0x88, 0x4D, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], +}; + +const COINIT_APARTMENTTHREADED: u32 = 0x2; +const CLSCTX_INPROC_SERVER: u32 = 0x1; +const RPC_C_AUTHN_LEVEL_DEFAULT: u32 = 0; +const RPC_C_IMP_LEVEL_IMPERSONATE: u32 = 3; +const RPC_C_AUTHN_LEVEL_CALL: u32 = 3; +const RPC_C_AUTHN_WINNT: u32 = 10; +const RPC_C_AUTHZ_NONE: u32 = 0; +const EOAC_NONE: u32 = 0; +const RPC_E_TOO_LATE: HRESULT = 0x80010119_u32 as i32; +const WBEM_FLAG_FORWARD_ONLY: i32 = 0x20; +const WBEM_FLAG_RETURN_IMMEDIATELY: i32 = 0x10; +const WBEM_S_FALSE: HRESULT = 1; +const WBEM_S_NO_MORE_DATA: HRESULT = 0x40005; +const WBEM_INFINITE: i32 = -1; +const WBEM_FLAVOR_MASK_ORIGIN: i32 = 0x60; +const WBEM_FLAVOR_ORIGIN_SYSTEM: i32 = 0x40; + +#[link(name = "ole32")] +unsafe extern "system" { + fn CoInitializeEx(pvReserved: *mut c_void, dwCoInit: u32) -> HRESULT; + fn CoUninitialize(); + fn CoInitializeSecurity( + pSecDesc: *const c_void, + cAuthSvc: i32, + asAuthSvc: *const c_void, + pReserved1: *const c_void, + dwAuthnLevel: u32, + dwImpLevel: u32, + pAuthList: *const c_void, + dwCapabilities: u32, + pReserved3: *const c_void, + ) -> HRESULT; + fn CoCreateInstance( + rclsid: *const GUID, + pUnkOuter: *mut c_void, + dwClsContext: u32, + riid: *const GUID, + ppv: *mut *mut c_void, + ) -> HRESULT; + fn CoSetProxyBlanket( + pProxy: *mut c_void, + dwAuthnSvc: u32, + dwAuthzSvc: u32, + pServerPrincName: *const u16, + dwAuthnLevel: u32, + dwImpLevel: u32, + pAuthInfo: *const c_void, + dwCapabilities: u32, + ) -> HRESULT; +} + +#[link(name = "oleaut32")] +unsafe extern "system" { + fn SysAllocString(psz: *const u16) -> *mut u16; + fn SysFreeString(bstrString: *mut u16); + fn VariantClear(pvarg: *mut VARIANT) -> HRESULT; +} + +#[link(name = "propsys")] +unsafe extern "system" { + fn VariantToString(varIn: *const VARIANT, pszBuf: *mut u16, cchBuf: u32) -> HRESULT; +} + +unsafe fn com_release(this: *mut c_void) { + if !this.is_null() { + let vtable = *(this as *const *const usize); + let release: unsafe extern "system" fn(*mut c_void) -> u32 = + core::mem::transmute(*vtable.add(2)); + release(this); + } +} + +#[allow(clippy::too_many_arguments)] +unsafe fn locator_connect_server( + this: *mut c_void, + network_resource: *const u16, + user: *const u16, + password: *const u16, + locale: *const u16, + security_flags: i32, + authority: *const u16, + ctx: *mut c_void, + services: *mut *mut c_void, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + *const u16, + *const u16, + *const u16, + *const u16, + i32, + *const u16, + *mut c_void, + *mut *mut c_void, + ) -> HRESULT = core::mem::transmute(*vtable.add(3)); + method( + this, + network_resource, + user, + password, + locale, + security_flags, + authority, + ctx, + services, + ) +} + +unsafe fn services_exec_query( + this: *mut c_void, + query_language: *const u16, + query: *const u16, + flags: i32, + ctx: *mut c_void, + enumerator: *mut *mut c_void, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + *const u16, + *const u16, + i32, + *mut c_void, + *mut *mut c_void, + ) -> HRESULT = core::mem::transmute(*vtable.add(20)); + method(this, query_language, query, flags, ctx, enumerator) +} + +unsafe fn enum_next( + this: *mut c_void, + timeout: i32, + count: u32, + objects: *mut *mut c_void, + returned: *mut u32, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + i32, + u32, + *mut *mut c_void, + *mut u32, + ) -> HRESULT = core::mem::transmute(*vtable.add(4)); + method(this, timeout, count, objects, returned) +} + +unsafe fn object_begin_enumeration(this: *mut c_void, enum_flags: i32) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn(*mut c_void, i32) -> HRESULT = + core::mem::transmute(*vtable.add(8)); + method(this, enum_flags) +} + +unsafe fn object_next( + this: *mut c_void, + flags: i32, + name: *mut *mut u16, + val: *mut VARIANT, + cim_type: *mut i32, + flavor: *mut i32, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + i32, + *mut *mut u16, + *mut VARIANT, + *mut i32, + *mut i32, + ) -> HRESULT = core::mem::transmute(*vtable.add(9)); + method(this, flags, name, val, cim_type, flavor) +} + +unsafe fn object_end_enumeration(this: *mut c_void) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn(*mut c_void) -> HRESULT = + core::mem::transmute(*vtable.add(10)); + method(this) +} + +fn hresult_from_win32(err: u32) -> HRESULT { + if err == 0 { + 0 + } else { + ((err & 0xFFFF) | 0x80070000) as HRESULT + } +} + +fn succeeded(hr: HRESULT) -> bool { + hr >= 0 +} + +fn failed(hr: HRESULT) -> bool { + hr < 0 +} + +fn wide_str(s: &str) -> Vec { + s.encode_utf16().chain(core::iter::once(0)).collect() +} + +unsafe fn wcslen(s: *const u16) -> usize { + let mut len = 0; + while unsafe { *s.add(len) } != 0 { + len += 1; + } + len +} + +unsafe fn wait_event(event: HANDLE, timeout: u32) -> u32 { + match unsafe { WaitForSingleObject(event, timeout) } { + WAIT_OBJECT_0 => 0, + WAIT_TIMEOUT => WAIT_TIMEOUT, + _ => unsafe { GetLastError() }, + } +} + +struct QueryThreadData { + query: Vec, + write_pipe: HANDLE, + init_event: HANDLE, + connect_event: HANDLE, +} + +unsafe impl Send for QueryThreadData {} + +unsafe extern "system" fn query_thread(param: *mut c_void) -> u32 { + unsafe { query_thread_impl(param) } +} + +unsafe fn query_thread_impl(param: *mut c_void) -> u32 { + let data = unsafe { Box::from_raw(param as *mut QueryThreadData) }; + let write_pipe = data.write_pipe; + let init_event = data.init_event; + let connect_event = data.connect_event; + + let mut locator: *mut c_void = null_mut(); + let mut services: *mut c_void = null_mut(); + let mut enumerator: *mut c_void = null_mut(); + let mut hr: HRESULT = 0; + + let bstr_query = unsafe { SysAllocString(data.query.as_ptr()) }; + if bstr_query.is_null() { + hr = hresult_from_win32(ERROR_NOT_ENOUGH_MEMORY); + } + + drop(data); + + if succeeded(hr) { + hr = unsafe { CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED) }; + } + + if failed(hr) { + unsafe { + CloseHandle(write_pipe); + if !bstr_query.is_null() { + SysFreeString(bstr_query); + } + } + return hr as u32; + } + + hr = unsafe { + CoInitializeSecurity( + null(), + -1, + null(), + null(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null(), + EOAC_NONE, + null(), + ) + }; + if hr == RPC_E_TOO_LATE { + hr = 0; + } + + if succeeded(hr) { + hr = unsafe { + CoCreateInstance( + &CLSID_WBEM_LOCATOR, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWBEM_LOCATOR, + &mut locator, + ) + }; + } + if succeeded(hr) && unsafe { SetEvent(init_event) } == 0 { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + + if succeeded(hr) { + let root_cimv2 = wide_str("ROOT\\CIMV2"); + let bstr_root = unsafe { SysAllocString(root_cimv2.as_ptr()) }; + hr = unsafe { + locator_connect_server( + locator, + bstr_root, + null(), + null(), + null(), + 0, + null(), + null_mut(), + &mut services, + ) + }; + if !bstr_root.is_null() { + unsafe { SysFreeString(bstr_root) }; + } + } + if succeeded(hr) && unsafe { SetEvent(connect_event) } == 0 { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + + if succeeded(hr) { + hr = unsafe { + CoSetProxyBlanket( + services, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null(), + EOAC_NONE, + ) + }; + } + if succeeded(hr) { + let wql = wide_str("WQL"); + let bstr_wql = unsafe { SysAllocString(wql.as_ptr()) }; + hr = unsafe { + services_exec_query( + services, + bstr_wql, + bstr_query, + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + null_mut(), + &mut enumerator, + ) + }; + if !bstr_wql.is_null() { + unsafe { SysFreeString(bstr_wql) }; + } + } + + let mut value: *mut c_void; + let mut start_of_enum = true; + let null_sep: u16 = 0; + let eq_sign: u16 = b'=' as u16; + + while succeeded(hr) { + let mut got: u32 = 0; + let mut written: u32 = 0; + value = null_mut(); + hr = unsafe { enum_next(enumerator, WBEM_INFINITE, 1, &mut value, &mut got) }; + + if hr == WBEM_S_FALSE { + hr = 0; + break; + } + if failed(hr) || got != 1 || value.is_null() { + continue; + } + + if !start_of_enum + && unsafe { + WriteFile( + write_pipe, + &null_sep as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + { + hr = hresult_from_win32(unsafe { GetLastError() }); + unsafe { com_release(value) }; + break; + } + start_of_enum = false; + + hr = unsafe { object_begin_enumeration(value, 0) }; + if failed(hr) { + unsafe { com_release(value) }; + break; + } + + while succeeded(hr) { + let mut prop_name: *mut u16 = null_mut(); + let mut prop_value = VARIANT::zeroed(); + let mut flavor: i32 = 0; + + hr = unsafe { + object_next( + value, + 0, + &mut prop_name, + &mut prop_value, + null_mut(), + &mut flavor, + ) + }; + + if hr == WBEM_S_NO_MORE_DATA { + hr = 0; + break; + } + + if succeeded(hr) && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM { + let mut prop_str = [0u16; BUFFER_SIZE]; + hr = unsafe { + VariantToString(&prop_value, prop_str.as_mut_ptr(), BUFFER_SIZE as u32) + }; + + if succeeded(hr) { + let cb_str1 = (unsafe { wcslen(prop_name) } * 2) as u32; + let cb_str2 = (unsafe { wcslen(prop_str.as_ptr()) } * 2) as u32; + + if unsafe { + WriteFile( + write_pipe, + prop_name as *const _, + cb_str1, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + &eq_sign as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + prop_str.as_ptr() as *const _, + cb_str2, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + &null_sep as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + } + + unsafe { + VariantClear(&mut prop_value); + SysFreeString(prop_name); + } + } + } + + unsafe { + object_end_enumeration(value); + com_release(value); + } + } + + unsafe { + if !bstr_query.is_null() { + SysFreeString(bstr_query); + } + if !enumerator.is_null() { + com_release(enumerator); + } + if !services.is_null() { + com_release(services); + } + if !locator.is_null() { + com_release(locator); + } + CoUninitialize(); + CloseHandle(write_pipe); + } + + hr as u32 +} + +pub fn exec_query(query_str: &str) -> Result { + let query_wide = wide_str(query_str); + + let mut h_thread: HANDLE = null_mut(); + let mut err: u32 = 0; + let mut buffer = [0u16; BUFFER_SIZE]; + let mut offset: u32 = 0; + let mut bytes_read: u32 = 0; + + let mut read_pipe: HANDLE = null_mut(); + let mut write_pipe: HANDLE = null_mut(); + + unsafe { + let init_event = CreateEventW(null(), 1, 0, null()); + let connect_event = CreateEventW(null(), 1, 0, null()); + + if init_event.is_null() + || connect_event.is_null() + || CreatePipe(&mut read_pipe, &mut write_pipe, null(), 0) == 0 + { + err = GetLastError(); + } else { + let thread_data = Box::new(QueryThreadData { + query: query_wide, + write_pipe, + init_event, + connect_event, + }); + let thread_data_ptr = Box::into_raw(thread_data); + + h_thread = CreateThread( + null(), + 0, + Some(query_thread), + thread_data_ptr as *const _ as *mut _, + 0, + null_mut(), + ); + + if h_thread.is_null() { + err = GetLastError(); + let data = Box::from_raw(thread_data_ptr); + CloseHandle(data.write_pipe); + } + } + + if err == 0 { + err = wait_event(init_event, 1000); + if err == 0 { + err = wait_event(connect_event, 100); + } + } + + while err == 0 { + let buf_ptr = (buffer.as_mut_ptr() as *mut u8).add(offset as usize); + let buf_remaining = (BUFFER_SIZE * 2) as u32 - offset; + + if ReadFile( + read_pipe, + buf_ptr as *mut _, + buf_remaining, + &mut bytes_read, + null_mut(), + ) != 0 + { + offset += bytes_read; + if offset >= (BUFFER_SIZE * 2) as u32 { + err = ERROR_MORE_DATA; + } + } else { + err = GetLastError(); + } + } + + if !read_pipe.is_null() { + CloseHandle(read_pipe); + } + + if !h_thread.is_null() { + let thread_err: u32; + match WaitForSingleObject(h_thread, 100) { + WAIT_OBJECT_0 => { + let mut exit_code: u32 = 0; + if GetExitCodeThread(h_thread, &mut exit_code) == 0 { + thread_err = GetLastError(); + } else { + thread_err = exit_code; + } + } + WAIT_TIMEOUT => { + thread_err = WAIT_TIMEOUT; + } + _ => { + thread_err = GetLastError(); + } + } + if err == 0 || err == ERROR_BROKEN_PIPE { + err = thread_err; + } + + CloseHandle(h_thread); + } + + CloseHandle(init_event); + CloseHandle(connect_event); + } + + if err == ERROR_MORE_DATA { + return Err(ExecQueryError::MoreData); + } + if err != 0 { + return Err(ExecQueryError::Code(err)); + } + if offset == 0 { + return Ok(String::new()); + } + + let char_count = (offset as usize) / 2 - 1; + Ok(String::from_utf16_lossy(&buffer[..char_count])) +} diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 25a4b8c12f2..2b2279fb1ee 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -45,7 +45,6 @@ hex = { workspace = true } itertools = { workspace = true } indexmap = { workspace = true } libc = { workspace = true } -nix = { workspace = true } num-complex = { workspace = true } malachite-bigint = { workspace = true } num-traits = { workspace = true } @@ -101,15 +100,8 @@ chrono.workspace = true mac_address = { workspace = true } uuid = { workspace = true, features = ["v1"] } -[target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies] -termios = { workspace = true } - -[target.'cfg(unix)'.dependencies] -rustix = { workspace = true } - # mmap + socket dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -memmap2 = { workspace = true } page_size = { workspace = true } gethostname = { workspace = true } socket2 = { workspace = true, features = ["all"] } @@ -142,26 +134,8 @@ liblzma-sys = { workspace = true } [target.'cfg(windows)'.dependencies] paste = { workspace = true } -schannel = { workspace = true } widestring = { workspace = true } -[target.'cfg(windows)'.dependencies.windows-sys] -workspace = true -features = [ - "Win32_Foundation", - "Win32_Networking_WinSock", - "Win32_NetworkManagement_IpHelper", - "Win32_NetworkManagement_Ndis", - "Win32_Security_Cryptography", - "Win32_Storage_FileSystem", - "Win32_System_Diagnostics_Debug", - "Win32_System_Environment", - "Win32_System_Console", - "Win32_System_IO", - "Win32_System_Memory", - "Win32_System_Threading" -] - [target.'cfg(target_os = "macos")'.dependencies] system-configuration = { workspace = true } diff --git a/crates/stdlib/src/_testconsole.rs b/crates/stdlib/src/_testconsole.rs index 0db508e3da5..78cba3b397d 100644 --- a/crates/stdlib/src/_testconsole.rs +++ b/crates/stdlib/src/_testconsole.rs @@ -5,23 +5,14 @@ mod _testconsole { use crate::vm::{ PyObjectRef, PyResult, VirtualMachine, convert::IntoPyException, function::ArgBytesLike, }; - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; - - type Handle = windows_sys::Win32::Foundation::HANDLE; + use rustpython_host_env::testconsole as host_testconsole; #[pyfunction] fn write_input(file: PyObjectRef, s: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Console::{INPUT_RECORD, KEY_EVENT, WriteConsoleInputW}; - // Get the fd from the file object via fileno() let fd_obj = vm.call_method(&file, "fileno", ())?; let fd: i32 = fd_obj.try_into_value(vm)?; - let handle = unsafe { libc::get_osfhandle(fd) } as Handle; - if handle == INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - let data = s.borrow_buf(); let data = &*data; @@ -33,39 +24,7 @@ mod _testconsole { .chunks_exact(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .collect(); - - let size = wchars.len() as u32; - - // Create INPUT_RECORD array - let mut records: Vec = Vec::with_capacity(wchars.len()); - for &wc in &wchars { - // SAFETY: zeroing and accessing the union field for KEY_EVENT - let mut rec: INPUT_RECORD = unsafe { core::mem::zeroed() }; - rec.EventType = KEY_EVENT as u16; - rec.Event.KeyEvent.bKeyDown = 1; // TRUE - rec.Event.KeyEvent.wRepeatCount = 1; - rec.Event.KeyEvent.uChar.UnicodeChar = wc; - records.push(rec); - } - - let mut total: u32 = 0; - while total < size { - let mut wrote: u32 = 0; - let res = unsafe { - WriteConsoleInputW( - handle, - records[total as usize..].as_ptr(), - size - total, - &mut wrote, - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - total += wrote; - } - - Ok(()) + host_testconsole::write_console_input(fd, &wchars).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index 9ea970767a9..b3af501d83a 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -3,9 +3,10 @@ pub(crate) use decl::module_def; #[allow(static_mut_refs)] // TODO: group code only with static mut refs #[pymodule(name = "faulthandler")] mod decl { + #[cfg(any(unix, windows))] + use crate::vm::frame::Frame; use crate::vm::{ PyObjectRef, PyResult, VirtualMachine, - frame::Frame, function::{ArgIntoFloat, OptionalArg}, }; use alloc::sync::Arc; @@ -13,76 +14,11 @@ mod decl { use core::time::Duration; use parking_lot::{Condvar, Mutex}; #[cfg(any(unix, windows))] + use rustpython_host_env::faulthandler as host_faulthandler; + #[cfg(any(unix, windows))] use rustpython_host_env::os::{get_errno, set_errno}; use std::thread; - /// fault_handler_t - #[cfg(unix)] - struct FaultHandler { - signum: libc::c_int, - enabled: bool, - name: &'static str, - previous: libc::sigaction, - } - - #[cfg(windows)] - struct FaultHandler { - signum: libc::c_int, - enabled: bool, - name: &'static str, - previous: libc::sighandler_t, - } - - #[cfg(unix)] - impl FaultHandler { - const fn new(signum: libc::c_int, name: &'static str) -> Self { - Self { - signum, - enabled: false, - name, - // SAFETY: sigaction is a C struct that can be zero-initialized - previous: unsafe { core::mem::zeroed() }, - } - } - } - - #[cfg(windows)] - impl FaultHandler { - const fn new(signum: libc::c_int, name: &'static str) -> Self { - Self { - signum, - enabled: false, - name, - previous: 0, - } - } - } - - /// faulthandler_handlers[] - /// Number of fatal signals - #[cfg(unix)] - const FAULTHANDLER_NSIGNALS: usize = 5; - #[cfg(windows)] - const FAULTHANDLER_NSIGNALS: usize = 4; - - // Signal handlers use mutable statics matching faulthandler.c implementation. - #[cfg(unix)] - static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [ - FaultHandler::new(libc::SIGBUS, "Bus error"), - FaultHandler::new(libc::SIGILL, "Illegal instruction"), - FaultHandler::new(libc::SIGFPE, "Floating-point exception"), - FaultHandler::new(libc::SIGABRT, "Aborted"), - FaultHandler::new(libc::SIGSEGV, "Segmentation fault"), - ]; - - #[cfg(windows)] - static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [ - FaultHandler::new(libc::SIGILL, "Illegal instruction"), - FaultHandler::new(libc::SIGFPE, "Floating-point exception"), - FaultHandler::new(libc::SIGABRT, "Aborted"), - FaultHandler::new(libc::SIGSEGV, "Segmentation fault"), - ]; - /// fatal_error state struct FatalErrorState { enabled: AtomicBool, @@ -124,7 +60,7 @@ mod decl { #[cfg(any(unix, windows))] fn puts_bytes(fd: i32, s: &[u8]) { - let _ = unsafe { libc::write(fd, s.as_ptr().cast::(), s.len() as _) }; + host_faulthandler::write_fd(fd, s); } // _Py_DumpHexadecimal (traceback.c) @@ -165,14 +101,9 @@ mod decl { } /// Get current thread ID - #[cfg(unix)] - fn current_thread_id() -> u64 { - unsafe { libc::pthread_self() as u64 } - } - - #[cfg(windows)] + #[cfg(any(unix, windows))] fn current_thread_id() -> u64 { - unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() as u64 } + host_faulthandler::current_thread_id() } // write_thread_id (traceback.c:1240-1256) @@ -259,6 +190,7 @@ mod decl { } /// MAX_STRING_LENGTH in traceback.c + #[cfg(any(unix, windows))] const MAX_STRING_LENGTH: usize = 500; /// Truncate a UTF-8 string to at most `max_bytes` without splitting a @@ -434,29 +366,6 @@ mod decl { // Signal handlers - /// faulthandler_disable_fatal_handler (faulthandler.c:310-321) - #[cfg(unix)] - unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) { - if !handler.enabled { - return; - } - handler.enabled = false; - unsafe { - libc::sigaction(handler.signum, &handler.previous, core::ptr::null_mut()); - } - } - - #[cfg(windows)] - unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) { - if !handler.enabled { - return; - } - handler.enabled = false; - unsafe { - libc::signal(handler.signum, handler.previous); - } - } - // faulthandler_fatal_error #[cfg(unix)] extern "C" fn faulthandler_fatal_error(signum: libc::c_int) { @@ -468,20 +377,10 @@ mod decl { let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); - let handler = unsafe { - FAULTHANDLER_HANDLERS - .iter_mut() - .find(|h| h.signum == signum) - }; - - if let Some(h) = handler { - // Disable handler (restores previous) - unsafe { - faulthandler_disable_fatal_handler(h); - } - + if let Some(name) = host_faulthandler::fatal_signal_name(signum) { + host_faulthandler::disable_fatal_signal(signum); puts(fd, "Fatal Python error: "); - puts(fd, h.name); + puts(fd, name); puts(fd, "\n\n"); } else { puts(fd, "Fatal Python error from unexpected signum: "); @@ -498,15 +397,10 @@ mod decl { // We cannot just restore the previous handler because Rust's runtime // may have installed its own SIGSEGV handler (for stack overflow detection) // that doesn't terminate the process on software-raised signals. - unsafe { - libc::signal(signum, libc::SIG_DFL); - libc::raise(signum); - } + host_faulthandler::signal_default_and_raise(signum); // Fallback if raise() somehow didn't terminate the process - unsafe { - libc::_exit(1); - } + host_faulthandler::exit_immediately(1); } // faulthandler_fatal_error for Windows @@ -520,18 +414,10 @@ mod decl { let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); - let handler = unsafe { - FAULTHANDLER_HANDLERS - .iter_mut() - .find(|h| h.signum == signum) - }; - - if let Some(h) = handler { - unsafe { - faulthandler_disable_fatal_handler(h); - } + if let Some(name) = host_faulthandler::fatal_signal_name(signum) { + host_faulthandler::disable_fatal_signal(signum); puts(fd, "Fatal Python error: "); - puts(fd, h.name); + puts(fd, name); puts(fd, "\n\n"); } else { puts(fd, "Fatal Python error from unexpected signum: "); @@ -544,10 +430,7 @@ mod decl { set_errno(save_errno); - unsafe { - libc::signal(signum, libc::SIG_DFL); - libc::raise(signum); - } + host_faulthandler::signal_default_and_raise(signum); // Fallback rustpython_host_env::os::exit(1); @@ -559,20 +442,12 @@ mod decl { #[cfg(windows)] fn faulthandler_ignore_exception(code: u32) -> bool { - // bpo-30557: ignore exceptions which are not errors - if (code & 0x80000000) == 0 { - return true; - } - // bpo-31701: ignore MSC and COM exceptions - if code == 0xE06D7363 || code == 0xE0434352 { - return true; - } - false + host_faulthandler::ignore_exception(code) } #[cfg(windows)] unsafe extern "system" fn faulthandler_exc_handler( - exc_info: *mut windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS, + exc_info: *mut host_faulthandler::ExceptionPointers, ) -> i32 { const EXCEPTION_CONTINUE_SEARCH: i32 = 0; @@ -580,8 +455,7 @@ mod decl { return EXCEPTION_CONTINUE_SEARCH; } - let record = unsafe { &*(*exc_info).ExceptionRecord }; - let code = record.ExceptionCode as u32; + let code = unsafe { host_faulthandler::exception_code(exc_info) }; if faulthandler_ignore_exception(code) { return EXCEPTION_CONTINUE_SEARCH; @@ -590,32 +464,17 @@ mod decl { let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); puts(fd, "Windows fatal exception: "); - match code { - 0xC0000005 => puts(fd, "access violation"), - 0xC000008C => puts(fd, "float divide by zero"), - 0xC0000091 => puts(fd, "float overflow"), - 0xC0000094 => puts(fd, "int divide by zero"), - 0xC0000095 => puts(fd, "integer overflow"), - 0xC0000006 => puts(fd, "page error"), - 0xC00000FD => puts(fd, "stack overflow"), - 0xC000001D => puts(fd, "illegal instruction"), - _ => { - puts(fd, "code "); - dump_hexadecimal(fd, code as u64, 8); - } + if let Some(description) = host_faulthandler::exception_description(code) { + puts(fd, description); + } else { + puts(fd, "code "); + dump_hexadecimal(fd, code as u64, 8); } puts(fd, "\n\n"); // Disable SIGSEGV handler for access violations to avoid double output - if code == 0xC0000005 { - unsafe { - for handler in &mut FAULTHANDLER_HANDLERS { - if handler.signum == libc::SIGSEGV { - faulthandler_disable_fatal_handler(handler); - break; - } - } - } + if host_faulthandler::is_access_violation(code) { + host_faulthandler::disable_fatal_signal(libc::SIGSEGV); } let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed); @@ -631,23 +490,8 @@ mod decl { return true; } - unsafe { - for handler in &mut FAULTHANDLER_HANDLERS { - if handler.enabled { - continue; - } - - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_fatal_error as *const () as libc::sighandler_t; - // SA_NODEFER flag - action.sa_flags = libc::SA_NODEFER; - - if libc::sigaction(handler.signum, &action, &mut handler.previous) != 0 { - return false; - } - - handler.enabled = true; - } + if !host_faulthandler::enable_fatal_handlers(faulthandler_fatal_error, libc::SA_NODEFER) { + return false; } FATAL_ERROR.enabled.store(true, Ordering::Relaxed); @@ -660,31 +504,15 @@ mod decl { return true; } - unsafe { - for handler in &mut FAULTHANDLER_HANDLERS { - if handler.enabled { - continue; - } - - handler.previous = libc::signal( - handler.signum, - faulthandler_fatal_error as *const () as libc::sighandler_t, - ); - - // SIG_ERR is -1 as sighandler_t (which is usize on Windows) - if handler.previous == libc::SIG_ERR as libc::sighandler_t { - return false; - } - - handler.enabled = true; - } + if !host_faulthandler::enable_fatal_handlers(faulthandler_fatal_error, 0) { + return false; } // Register Windows vectored exception handler #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::AddVectoredExceptionHandler; - let h = unsafe { AddVectoredExceptionHandler(1, Some(faulthandler_exc_handler)) }; + let h = + host_faulthandler::add_vectored_exception_handler(Some(faulthandler_exc_handler)); EXC_HANDLER.store(h as usize, Ordering::Relaxed); } @@ -699,22 +527,13 @@ mod decl { return; } - unsafe { - for handler in &mut FAULTHANDLER_HANDLERS { - faulthandler_disable_fatal_handler(handler); - } - } + host_faulthandler::disable_fatal_handlers(); // Remove Windows vectored exception handler #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::RemoveVectoredExceptionHandler; let h = EXC_HANDLER.swap(0, Ordering::Relaxed); - if h != 0 { - unsafe { - RemoveVectoredExceptionHandler(h as *mut core::ffi::c_void); - } - } + host_faulthandler::remove_vectored_exception_handler(h); } } @@ -944,112 +763,27 @@ mod decl { } } - #[cfg(unix)] - mod user_signals { - use parking_lot::Mutex; - - const NSIG: usize = 64; - - #[derive(Clone, Copy)] - pub(super) struct UserSignal { - pub enabled: bool, - pub fd: i32, - pub all_threads: bool, - pub chain: bool, - pub previous: libc::sigaction, - } - - impl Default for UserSignal { - fn default() -> Self { - Self { - enabled: false, - fd: 2, // stderr - all_threads: true, - chain: false, - // SAFETY: sigaction is a C struct that can be zero-initialized - previous: unsafe { core::mem::zeroed() }, - } - } - } - - static USER_SIGNALS: Mutex>> = Mutex::new(None); - - pub(super) fn get_user_signal(signum: usize) -> Option { - let guard = USER_SIGNALS.lock(); - guard.as_ref().and_then(|v| v.get(signum).copied()) - } - - pub(super) fn set_user_signal(signum: usize, signal: UserSignal) { - let mut guard = USER_SIGNALS.lock(); - if guard.is_none() { - *guard = Some(vec![UserSignal::default(); NSIG]); - } - if let Some(ref mut v) = *guard - && signum < v.len() - { - v[signum] = signal; - } - } - - pub(super) fn clear_user_signal(signum: usize) -> Option { - let mut guard = USER_SIGNALS.lock(); - if let Some(ref mut v) = *guard - && signum < v.len() - && v[signum].enabled - { - let old = v[signum]; - v[signum] = UserSignal::default(); - return Some(old); - } - None - } - - pub(super) fn is_enabled(signum: usize) -> bool { - let guard = USER_SIGNALS.lock(); - guard - .as_ref() - .and_then(|v| v.get(signum)) - .is_some_and(|s| s.enabled) - } - } - #[cfg(unix)] extern "C" fn faulthandler_user_signal(signum: libc::c_int) { let save_errno = get_errno(); - let user = match user_signals::get_user_signal(signum as usize) { - Some(u) if u.enabled => u, + let user = match host_faulthandler::get_user_signal(signum as usize) { + Some(u) => u, _ => return, }; faulthandler_dump_traceback(user.fd, user.all_threads); if user.chain { - // Restore the previous handler and re-raise - unsafe { - libc::sigaction(signum, &user.previous, core::ptr::null_mut()); - } set_errno(save_errno); - unsafe { - libc::raise(signum); - } - // Re-install our handler with the same flags as register() - let save_errno2 = get_errno(); - unsafe { - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t; - action.sa_flags = libc::SA_NODEFER; - libc::sigaction(signum, &action, core::ptr::null_mut()); - } - set_errno(save_errno2); + let _ = host_faulthandler::reraise_user_signal(signum, faulthandler_user_signal); } } #[cfg(unix)] fn check_signum(signum: i32, vm: &VirtualMachine) -> PyResult<()> { // Check if it's a fatal signal (faulthandler.c uses faulthandler_handlers array) - let is_fatal = unsafe { FAULTHANDLER_HANDLERS.iter().any(|h| h.signum == signum) }; - if is_fatal { + if host_faulthandler::is_fatal_signal(signum) { return Err(vm.new_runtime_error(format!( "signal {signum} cannot be registered, use enable() instead" ))); @@ -1084,46 +818,19 @@ mod decl { let fd = get_fd_from_file_opt(args.file, vm)?; - let signum = args.signum as usize; - - // Get current handler to save as previous - let previous = if !user_signals::is_enabled(signum) { - unsafe { - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t; - // SA_RESTART by default; SA_NODEFER only when chaining - // (faulthandler.c:860-864) - action.sa_flags = if args.chain { - libc::SA_NODEFER - } else { - libc::SA_RESTART - }; - - let mut prev: libc::sigaction = core::mem::zeroed(); - if libc::sigaction(args.signum, &action, &mut prev) != 0 { - return Err(vm.new_os_error(format!( - "Failed to register signal handler for signal {}", - args.signum - ))); - } - prev - } - } else { - // Already registered, keep previous handler - user_signals::get_user_signal(signum) - .map_or(unsafe { core::mem::zeroed() }, |u| u.previous) - }; - - user_signals::set_user_signal( - signum, - user_signals::UserSignal { - enabled: true, - fd, - all_threads: args.all_threads, - chain: args.chain, - previous, - }, - ); + host_faulthandler::register_user_signal( + args.signum, + fd, + args.all_threads, + args.chain, + faulthandler_user_signal, + ) + .map_err(|_| { + vm.new_os_error(format!( + "Failed to register signal handler for signal {}", + args.signum + )) + })?; Ok(()) } @@ -1132,16 +839,7 @@ mod decl { #[pyfunction] fn unregister(signum: i32, vm: &VirtualMachine) -> PyResult { check_signum(signum, vm)?; - - if let Some(old) = user_signals::clear_user_signal(signum as usize) { - // Restore previous handler - unsafe { - libc::sigaction(signum, &old.previous, core::ptr::null_mut()); - } - Ok(true) - } else { - Ok(false) - } + Ok(host_faulthandler::unregister_user_signal(signum)) } // Test functions for faulthandler testing @@ -1188,10 +886,7 @@ mod decl { #[cfg(not(target_arch = "wasm32"))] { suppress_crash_report(); - - unsafe { - libc::abort(); - } + host_faulthandler::abort_process(); } } @@ -1200,10 +895,7 @@ mod decl { #[cfg(not(target_arch = "wasm32"))] { suppress_crash_report(); - - unsafe { - libc::raise(libc::SIGFPE); - } + host_faulthandler::raise_signal(libc::SIGFPE); } } @@ -1226,28 +918,14 @@ mod decl { fn suppress_crash_report() { #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::{ - SEM_NOGPFAULTERRORBOX, SetErrorMode, - }; - unsafe { - let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); - SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); - } + host_faulthandler::suppress_crash_report(); } #[cfg(unix)] { - // Disable core dumps #[cfg(not(any(target_os = "redox", target_os = "wasi")))] { - use libc::{RLIMIT_CORE, rlimit, setrlimit}; - let rl = rlimit { - rlim_cur: 0, - rlim_max: 0, - }; - unsafe { - let _ = setrlimit(RLIMIT_CORE, &rl); - } + rustpython_host_env::resource::disable_core_dumps(); } } } @@ -1285,11 +963,7 @@ mod decl { #[cfg(windows)] #[pyfunction] fn _raise_exception(args: RaiseExceptionArgs, _vm: &VirtualMachine) { - use windows_sys::Win32::System::Diagnostics::Debug::RaiseException; - suppress_crash_report(); - unsafe { - RaiseException(args.code, args.flags, 0, core::ptr::null()); - } + host_faulthandler::raise_exception(args.code, args.flags); } } diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index 6d27b3cee57..c118ac99e51 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -95,10 +95,7 @@ mod fcntl { mutate_flag: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // Convert to unsigned - handles both positive u32 values and negative i32 values - // that represent the same bit pattern (e.g., TIOCSWINSZ on some platforms). - // First truncate to u32 (takes lower 32 bits), then zero-extend to c_ulong. - let request = (request as u32) as libc::c_ulong; + let request = host_fcntl::normalize_ioctl_request(request); let arg = arg.unwrap_or_else(|| Either::B(0)); match arg { Either::A(buf_kind) => { @@ -158,39 +155,23 @@ mod fcntl { whence: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - macro_rules! try_into_l_type { - ($l_type:path) => { - $l_type - .try_into() - .map_err(|e| vm.new_overflow_error(format!("{e}"))) - }; - } - - let mut l: libc::flock = unsafe { core::mem::zeroed() }; - l.l_type = if cmd == libc::LOCK_UN { - try_into_l_type!(libc::F_UNLCK) - } else if (cmd & libc::LOCK_SH) != 0 { - try_into_l_type!(libc::F_RDLCK) - } else if (cmd & libc::LOCK_EX) != 0 { - try_into_l_type!(libc::F_WRLCK) - } else { - return Err(vm.new_value_error("unrecognized lockf argument")); - }?; - l.l_start = match start { + let start = match start { OptionalArg::Present(s) => s.try_to_primitive(vm)?, OptionalArg::Missing => 0, }; - l.l_len = match len { + let len = match len { OptionalArg::Present(l_) => l_.try_to_primitive(vm)?, OptionalArg::Missing => 0, }; - l.l_whence = match whence { - OptionalArg::Present(w) => w - .try_into() - .map_err(|e| vm.new_overflow_error(format!("{e}")))?, + let whence = match whence { + OptionalArg::Present(w) => w, OptionalArg::Missing => 0, }; - let ret = host_fcntl::lockf(fd, cmd, &l).map_err(|_| vm.new_last_errno_error())?; + let ret = host_fcntl::lockf(fd, cmd, len, start, whence).map_err(|err| match err { + host_fcntl::LockfError::InvalidCmd => vm.new_value_error("unrecognized lockf argument"), + host_fcntl::LockfError::Overflow(e) => vm.new_overflow_error(e), + host_fcntl::LockfError::Io(_) => vm.new_last_errno_error(), + })?; Ok(vm.ctx.new_int(ret).into()) } } diff --git a/crates/stdlib/src/grp.rs b/crates/stdlib/src/grp.rs index 70aa7d4e4c6..34e9929d2c4 100644 --- a/crates/stdlib/src/grp.rs +++ b/crates/stdlib/src/grp.rs @@ -10,8 +10,7 @@ mod grp { exceptions, types::PyStructSequence, }; - use core::ptr::NonNull; - use nix::unistd; + use rustpython_host_env::grp as host_grp; #[pystruct_sequence_data] struct GroupData { @@ -29,15 +28,11 @@ mod grp { impl PyGroup {} impl GroupData { - fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self { - let cstr_lossy = |s: alloc::ffi::CString| { - s.into_string() - .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) - }; + fn from_group(group: host_grp::Group, vm: &VirtualMachine) -> Self { Self { gr_name: group.name, - gr_passwd: cstr_lossy(group.passwd), - gr_gid: group.gid.as_raw(), + gr_passwd: group.passwd, + gr_gid: group.gid, gr_mem: vm .ctx .new_list(group.mem.iter().map(|s| s.to_pyobject(vm)).collect()), @@ -48,11 +43,9 @@ mod grp { #[pyfunction] fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult { let gr_gid = gid.as_bigint(); - let gid = libc::gid_t::try_from(gr_gid) - .map(unistd::Gid::from_raw) - .ok(); + let gid = libc::gid_t::try_from(gr_gid).ok(); let group = gid - .map(unistd::Group::from_gid) + .map(host_grp::getgrgid) .transpose() .map_err(|err| err.into_pyexception(vm))? .flatten(); @@ -63,7 +56,7 @@ mod grp { .into(), ) })?; - Ok(GroupData::from_unistd_group(group, vm)) + Ok(GroupData::from_group(group, vm)) } #[pyfunction] @@ -72,7 +65,7 @@ mod grp { if gr_name.contains('\0') { return Err(exceptions::cstring_error(vm)); } - let group = unistd::Group::from_name(gr_name).map_err(|err| err.into_pyexception(vm))?; + let group = host_grp::getgrnam(gr_name).map_err(|err| err.into_pyexception(vm))?; let group = group.ok_or_else(|| { vm.new_key_error( vm.ctx @@ -80,24 +73,14 @@ mod grp { .into(), ) })?; - Ok(GroupData::from_unistd_group(group, vm)) + Ok(GroupData::from_group(group, vm)) } #[pyfunction] fn getgrall(vm: &VirtualMachine) -> Vec { - // setgrent, getgrent, etc are not thread safe. Could use fgetgrent_r, but this is easier - static GETGRALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); - let _guard = GETGRALL.lock(); - let mut list = Vec::new(); - - unsafe { libc::setgrent() }; - while let Some(ptr) = NonNull::new(unsafe { libc::getgrent() }) { - let group = unistd::Group::from(unsafe { ptr.as_ref() }); - let group = GroupData::from_unistd_group(group, vm).to_pyobject(vm); - list.push(group); - } - unsafe { libc::endgrent() }; - - list + host_grp::getgrall() + .into_iter() + .map(|group| GroupData::from_group(group, vm).to_pyobject(vm)) + .collect() } } diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index 251c9586e18..74e9053fbfb 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -2,55 +2,16 @@ pub(crate) use _locale::module_def; -#[cfg(windows)] -#[repr(C)] -struct lconv { - decimal_point: *mut libc::c_char, - thousands_sep: *mut libc::c_char, - grouping: *mut libc::c_char, - int_curr_symbol: *mut libc::c_char, - currency_symbol: *mut libc::c_char, - mon_decimal_point: *mut libc::c_char, - mon_thousands_sep: *mut libc::c_char, - mon_grouping: *mut libc::c_char, - positive_sign: *mut libc::c_char, - negative_sign: *mut libc::c_char, - int_frac_digits: libc::c_char, - frac_digits: libc::c_char, - p_cs_precedes: libc::c_char, - p_sep_by_space: libc::c_char, - n_cs_precedes: libc::c_char, - n_sep_by_space: libc::c_char, - p_sign_posn: libc::c_char, - n_sign_posn: libc::c_char, - int_p_cs_precedes: libc::c_char, - int_p_sep_by_space: libc::c_char, - int_n_cs_precedes: libc::c_char, - int_n_sep_by_space: libc::c_char, - int_p_sign_posn: libc::c_char, - int_n_sign_posn: libc::c_char, -} - -#[cfg(windows)] -unsafe extern "C" { - fn localeconv() -> *mut lconv; -} - -#[cfg(unix)] -use libc::localeconv; - #[pymodule] mod _locale { use alloc::ffi::CString; - use core::{ffi::CStr, ptr}; + use rustpython_host_env::locale as host_locale; use rustpython_vm::{ PyObjectRef, PyResult, VirtualMachine, builtins::{PyDictRef, PyIntRef, PyListRef, PyTypeRef, PyUtf8StrRef}, convert::ToPyException, function::OptionalArg, }; - #[cfg(windows)] - use windows_sys::Win32::Globalization::GetACP; #[cfg(all( unix, @@ -78,19 +39,11 @@ mod _locale { vm.ctx.new_int(libc::c_char::MAX) } - unsafe fn copy_grouping(group: *const libc::c_char, vm: &VirtualMachine) -> PyListRef { + fn copy_grouping(group: &[libc::c_char], vm: &VirtualMachine) -> PyListRef { let mut group_vec: Vec = Vec::new(); - if group.is_null() { - return vm.ctx.new_list(group_vec); - } - - unsafe { - let mut ptr = group; - while ![0, libc::c_char::MAX].contains(&*ptr) { - let val = vm.ctx.new_int(*ptr); - group_vec.push(val.into()); - ptr = ptr.add(1); - } + for &value in group { + let val = vm.ctx.new_int(value); + group_vec.push(val.into()); } // https://github.com/python/cpython/blob/677320348728ce058fa3579017e985af74a236d4/Modules/_localemodule.c#L80 if !group_vec.is_empty() { @@ -99,47 +52,21 @@ mod _locale { vm.ctx.new_list(group_vec) } - unsafe fn pystr_from_raw_cstr( - vm: &VirtualMachine, - raw_ptr: *const libc::c_char, - ) -> PyObjectRef { - let slice = unsafe { CStr::from_ptr(raw_ptr) }; - + fn pystr_from_bytes(vm: &VirtualMachine, bytes: &[u8]) -> PyObjectRef { // Fast path: ASCII/UTF-8 - if let Ok(s) = slice.to_str() { + if let Ok(s) = core::str::from_utf8(bytes) { return vm.new_pyobj(s); } // On Windows, locale strings use the ANSI code page encoding #[cfg(windows)] { - use windows_sys::Win32::Globalization::{CP_ACP, MultiByteToWideChar}; - let bytes = slice.to_bytes(); - unsafe { - let len = MultiByteToWideChar( - CP_ACP, - 0, - bytes.as_ptr(), - bytes.len() as i32, - ptr::null_mut(), - 0, - ); - if len > 0 { - let mut wide = vec![0u16; len as usize]; - MultiByteToWideChar( - CP_ACP, - 0, - bytes.as_ptr(), - bytes.len() as i32, - wide.as_mut_ptr(), - len, - ); - return vm.new_pyobj(String::from_utf16_lossy(&wide)); - } + if let Some(decoded) = host_locale::decode_ansi_bytes(bytes) { + return vm.new_pyobj(decoded); } } - vm.new_pyobj(String::from_utf8_lossy(slice.to_bytes()).into_owned()) + vm.new_pyobj(String::from_utf8_lossy(bytes).into_owned()) } #[pyattr(name = "Error", once)] @@ -155,73 +82,59 @@ mod _locale { fn strcoll(string1: PyUtf8StrRef, string2: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { let cstr1 = CString::new(string1.as_str()).map_err(|e| e.to_pyexception(vm))?; let cstr2 = CString::new(string2.as_str()).map_err(|e| e.to_pyexception(vm))?; - Ok(vm.new_pyobj(unsafe { libc::strcoll(cstr1.as_ptr(), cstr2.as_ptr()) })) + Ok(vm.new_pyobj(host_locale::strcoll(&cstr1, &cstr2))) } #[pyfunction] fn strxfrm(string: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { // https://github.com/python/cpython/blob/eaae563b6878aa050b4ad406b67728b6b066220e/Modules/_localemodule.c#L390-L442 let n1 = string.byte_len() + 1; - let mut buff = vec![0u8; n1]; - let cstr = CString::new(string.as_str()).map_err(|e| e.to_pyexception(vm))?; - let n2 = unsafe { libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n1) }; - buff = vec![0u8; n2 + 1]; - unsafe { - libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n2 + 1); - } + let buff = host_locale::strxfrm(&cstr, n1); Ok(vm.new_pyobj(String::from_utf8(buff).expect("strxfrm returned invalid utf-8 string"))) } #[pyfunction] fn localeconv(vm: &VirtualMachine) -> PyResult { let result = vm.ctx.new_dict(); + let lc = host_locale::localeconv_data(); - unsafe { - macro_rules! set_string_field { - ($lc:expr, $field:ident) => {{ - result.set_item( - stringify!($field), - pystr_from_raw_cstr(vm, (*$lc).$field), - vm, - )? - }}; - } - - macro_rules! set_int_field { - ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), vm.new_pyobj((*$lc).$field), vm)? }}; - } + macro_rules! set_string_field { + ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), pystr_from_bytes(vm, &$lc.$field), vm)? }}; + } - macro_rules! set_group_field { - ($lc:expr, $field:ident) => {{ - result.set_item( - stringify!($field), - copy_grouping((*$lc).$field, vm).into(), - vm, - )? - }}; - } + macro_rules! set_int_field { + ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), vm.new_pyobj($lc.$field), vm)? }}; + } - let lc = super::localeconv(); - set_group_field!(lc, mon_grouping); - set_group_field!(lc, grouping); - set_int_field!(lc, int_frac_digits); - set_int_field!(lc, frac_digits); - set_int_field!(lc, p_cs_precedes); - set_int_field!(lc, p_sep_by_space); - set_int_field!(lc, n_cs_precedes); - set_int_field!(lc, p_sign_posn); - set_int_field!(lc, n_sign_posn); - set_string_field!(lc, decimal_point); - set_string_field!(lc, thousands_sep); - set_string_field!(lc, int_curr_symbol); - set_string_field!(lc, currency_symbol); - set_string_field!(lc, mon_decimal_point); - set_string_field!(lc, mon_thousands_sep); - set_int_field!(lc, n_sep_by_space); - set_string_field!(lc, positive_sign); - set_string_field!(lc, negative_sign); + macro_rules! set_group_field { + ($lc:expr, $field:ident) => {{ + result.set_item( + stringify!($field), + copy_grouping(&$lc.$field, vm).into(), + vm, + )? + }}; } + + set_group_field!(lc, mon_grouping); + set_group_field!(lc, grouping); + set_int_field!(lc, int_frac_digits); + set_int_field!(lc, frac_digits); + set_int_field!(lc, p_cs_precedes); + set_int_field!(lc, p_sep_by_space); + set_int_field!(lc, n_cs_precedes); + set_int_field!(lc, p_sign_posn); + set_int_field!(lc, n_sign_posn); + set_string_field!(lc, decimal_point); + set_string_field!(lc, thousands_sep); + set_string_field!(lc, int_curr_symbol); + set_string_field!(lc, currency_symbol); + set_string_field!(lc, mon_decimal_point); + set_string_field!(lc, mon_thousands_sep); + set_int_field!(lc, n_sep_by_space); + set_string_field!(lc, positive_sign); + set_string_field!(lc, negative_sign); Ok(result) } @@ -268,37 +181,32 @@ mod _locale { return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); } - unsafe { - let result = match args.locale.flatten() { - None => libc::setlocale(args.category, ptr::null()), - Some(locale) => { - let locale_str = locale.as_str(); - // On Windows, validate encoding name length - #[cfg(windows)] - { - let valid = if args.category == LC_ALL { - check_locale_name_all(locale_str) - } else { - check_locale_name(locale_str) - }; - if !valid { - return Err( - vm.new_exception_msg(error, "unsupported locale setting".into()) - ); - } + let result = match args.locale.flatten() { + None => host_locale::setlocale(args.category, None), + Some(locale) => { + let locale_str = locale.as_str(); + #[cfg(windows)] + { + let valid = if args.category == LC_ALL { + check_locale_name_all(locale_str) + } else { + check_locale_name(locale_str) + }; + if !valid { + return Err( + vm.new_exception_msg(error, "unsupported locale setting".into()) + ); } - let c_locale: CString = - CString::new(locale_str).map_err(|e| e.to_pyexception(vm))?; - libc::setlocale(args.category, c_locale.as_ptr()) } - }; - - if result.is_null() { - return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); + let c_locale: CString = + CString::new(locale_str).map_err(|e| e.to_pyexception(vm))?; + host_locale::setlocale(args.category, Some(&c_locale)) } - - Ok(pystr_from_raw_cstr(vm, result)) - } + }; + let Some(result) = result else { + return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); + }; + Ok(pystr_from_bytes(vm, &result)) } /// Get the current locale encoding. @@ -306,26 +214,21 @@ mod _locale { fn getencoding() -> String { #[cfg(windows)] { - // On Windows, use GetACP() to get the ANSI code page - let acp = unsafe { GetACP() }; + let acp = host_locale::acp(); format!("cp{acp}") } #[cfg(not(windows))] { - // On Unix, use nl_langinfo(CODESET) or fallback to UTF-8 #[cfg(all( unix, not(any(target_os = "ios", target_os = "android", target_os = "redox")) ))] { - unsafe { - let codeset = libc::nl_langinfo(libc::CODESET); - if !codeset.is_null() - && let Ok(s) = CStr::from_ptr(codeset).to_str() - && !s.is_empty() - { - return s.to_string(); - } + if let Some(codeset) = host_locale::nl_langinfo_codeset() + && let Ok(s) = core::str::from_utf8(&codeset) + && !s.is_empty() + { + return s.to_string(); } "UTF-8".to_string() } diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 9cb84b4efa6..d6bb0d89aca 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -23,67 +23,18 @@ mod mmap { }; use core::ops::{Deref, DerefMut}; use crossbeam_utils::atomic::AtomicCell; - use memmap2::{Mmap, MmapMut, MmapOptions}; use num_traits::Signed; - use std::io::{self, Write}; + #[cfg(windows)] + use std::io; + use std::io::Write; - #[cfg(unix)] - use nix::{sys::stat::fstat, unistd}; #[cfg(unix)] use rustpython_host_env::crt_fd; + #[cfg(any(unix, windows))] + use rustpython_host_env::mmap as host_mmap; #[cfg(windows)] - use rustpython_host_env::suppress_iph; - #[cfg(windows)] - use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; - #[cfg(windows)] - use windows_sys::Win32::{ - Foundation::{ - CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, - }, - Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, - System::Memory::{ - CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile, - MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, UnmapViewOfFile, - }, - System::Threading::GetCurrentProcess, - }; - - #[cfg(unix)] - fn validate_advice(vm: &VirtualMachine, advice: i32) -> PyResult { - match advice { - libc::MADV_NORMAL - | libc::MADV_RANDOM - | libc::MADV_SEQUENTIAL - | libc::MADV_WILLNEED - | libc::MADV_DONTNEED => Ok(advice), - #[cfg(any( - target_os = "linux", - target_os = "macos", - target_os = "ios", - target_os = "freebsd" - ))] - libc::MADV_FREE => Ok(advice), - #[cfg(target_os = "linux")] - libc::MADV_DONTFORK - | libc::MADV_DOFORK - | libc::MADV_MERGEABLE - | libc::MADV_UNMERGEABLE - | libc::MADV_HUGEPAGE - | libc::MADV_NOHUGEPAGE - | libc::MADV_REMOVE - | libc::MADV_DONTDUMP - | libc::MADV_DODUMP - | libc::MADV_HWPOISON => Ok(advice), - #[cfg(target_os = "freebsd")] - libc::MADV_NOSYNC - | libc::MADV_AUTOSYNC - | libc::MADV_NOCORE - | libc::MADV_CORE - | libc::MADV_PROTECT => Ok(advice), - _ => Err(vm.new_value_error("Not a valid Advice value")), - } - } + use rustpython_host_env::nt as host_nt; #[repr(C)] #[derive(PartialEq, Eq, Debug)] @@ -197,68 +148,19 @@ mod mmap { vm.ctx.exceptions.os_error.to_owned() } - /// Named file mapping on Windows using raw Win32 APIs. - /// Supports tagname parameter for inter-process shared memory. - #[cfg(windows)] - struct NamedMmap { - map_handle: HANDLE, - view_ptr: *mut u8, - len: usize, - } - - #[cfg(windows)] - // SAFETY: The memory mapping is managed by the OS and is safe to share - // across threads. Access is synchronized by PyMutex in PyMmap. - unsafe impl Send for NamedMmap {} - #[cfg(windows)] - unsafe impl Sync for NamedMmap {} - - #[cfg(windows)] - impl core::fmt::Debug for NamedMmap { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("NamedMmap") - .field("map_handle", &self.map_handle) - .field("view_ptr", &self.view_ptr) - .field("len", &self.len) - .finish() - } - } - - #[cfg(windows)] - impl Drop for NamedMmap { - fn drop(&mut self) { - unsafe { - if !self.view_ptr.is_null() { - UnmapViewOfFile( - windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS { - Value: self.view_ptr as *mut _, - }, - ); - } - if !self.map_handle.is_null() { - CloseHandle(self.map_handle); - } - } - } - } - #[derive(Debug)] enum MmapObj { - Write(MmapMut), - Read(Mmap), + Mapped(host_mmap::MappedFile), #[cfg(windows)] - Named(NamedMmap), + Named(host_mmap::NamedMmap), } impl MmapObj { fn as_slice(&self) -> &[u8] { match self { - Self::Read(mmap) => &mmap[..], - Self::Write(mmap) => &mmap[..], + Self::Mapped(mmap) => mmap.as_slice(), #[cfg(windows)] - Self::Named(named) => unsafe { - core::slice::from_raw_parts(named.view_ptr, named.len) - }, + Self::Named(named) => named.as_slice(), } } } @@ -272,7 +174,7 @@ mod mmap { #[cfg(unix)] fd: AtomicCell, #[cfg(windows)] - handle: AtomicCell, // HANDLE is isize on Windows + handle: AtomicCell, // host_mmap::Handle is isize on Windows offset: i64, size: AtomicCell, pos: AtomicCell, // relative to offset @@ -286,15 +188,13 @@ mod mmap { #[cfg(unix)] { let fd = self.fd.swap(-1); - if fd >= 0 { - unsafe { libc::close(fd) }; - } + host_mmap::close_descriptor(fd); } #[cfg(windows)] { - let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); - if handle != INVALID_HANDLE_VALUE as isize { - unsafe { CloseHandle(handle as HANDLE) }; + let handle = self.handle.swap(host_mmap::INVALID_HANDLE as isize); + if handle != host_mmap::INVALID_HANDLE as isize { + host_mmap::close_handle(handle as host_mmap::Handle); } } } @@ -484,14 +384,11 @@ mod mmap { // fcntl(2) is necessary to force DISKSYNC and get around mmap(2) bug #[cfg(target_os = "macos")] if let Ok(fd) = fd { - use std::os::fd::AsRawFd; - unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_FULLFSYNC) }; + host_mmap::prepare_file_mapping(fd); } if let Ok(fd) = fd { - let metadata = fstat(fd) - .map_err(|err| io::Error::from_raw_os_error(err as i32).to_pyexception(vm))?; - let file_len = metadata.st_size as i64; + let file_len = host_mmap::file_len(fd).map_err(|err| err.to_pyexception(vm))?; if map_size == 0 { if file_len == 0 { @@ -510,22 +407,22 @@ mod mmap { } } - let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); - let (fd, mmap) = || -> std::io::Result<_> { if let Ok(fd) = fd { - let new_fd: crt_fd::Owned = unistd::dup(fd)?.into(); - let mmap = match access { - AccessMode::Default | AccessMode::Write => { - MmapObj::Write(unsafe { mmap_opt.map_mut(&new_fd) }?) - } - AccessMode::Read => MmapObj::Read(unsafe { mmap_opt.map(&new_fd) }?), - AccessMode::Copy => MmapObj::Write(unsafe { mmap_opt.map_copy(&new_fd) }?), - }; + let (new_fd, mmap) = host_mmap::map_file( + fd, + offset, + map_size, + match access { + AccessMode::Default => host_mmap::AccessMode::Default, + AccessMode::Read => host_mmap::AccessMode::Read, + AccessMode::Write => host_mmap::AccessMode::Write, + AccessMode::Copy => host_mmap::AccessMode::Copy, + }, + )?; Ok((Some(new_fd), mmap)) } else { - let mmap = MmapObj::Write(mmap_opt.map_anon()?); + let mmap = host_mmap::map_anon(map_size)?; Ok((None, mmap)) } }() @@ -533,7 +430,7 @@ mod mmap { Ok(Self { closed: AtomicCell::new(false), - mmap: PyMutex::new(Some(mmap)), + mmap: PyMutex::new(Some(MmapObj::Mapped(mmap))), fd: AtomicCell::new(fd.map_or(-1, |fd| fd.into_raw())), offset, size: AtomicCell::new(map_size), @@ -568,163 +465,105 @@ mod mmap { _ => None, }; - // Get file handle from fileno - // fileno -1 or 0 means anonymous mapping - let fh: Option = if fileno != -1 && fileno != 0 { - // Convert CRT file descriptor to Windows HANDLE + // Get file handle from fileno. fileno -1 means anonymous mapping. + let fh: Option = if fileno != -1 { + // Convert CRT file descriptor to a Windows file mapping handle. // Use suppress_iph! to avoid crashes when the fd is invalid. // This is critical because socket fds wrapped via _open_osfhandle // may cause crashes in _get_osfhandle on Windows. // See Python bug https://bugs.python.org/issue30114 - let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; + let handle = host_nt::handle_from_fd(fileno); // Check for invalid handle value (-1 on Windows) - if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { + if host_mmap::is_invalid_handle_value(handle as isize) { return Err(vm.new_os_error(format!("Invalid file descriptor: {fileno}"))); } - Some(handle as HANDLE) + Some(handle as host_mmap::Handle) } else { None }; // Get file size if we have a file handle and map_size is 0 - let mut duplicated_handle: HANDLE = INVALID_HANDLE_VALUE; + let mut duplicated_handle: host_mmap::Handle = host_mmap::INVALID_HANDLE; if let Some(fh) = fh { // Duplicate handle so Python code can close the original - let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let result = unsafe { - DuplicateHandle( - GetCurrentProcess(), - fh, - GetCurrentProcess(), - &mut new_handle, - 0, - 0, // not inheritable - DUPLICATE_SAME_ACCESS, - ) - }; - if result == 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - duplicated_handle = new_handle; + duplicated_handle = + host_mmap::duplicate_handle(fh).map_err(|e| e.to_pyexception(vm))?; // Get file size - let mut high: u32 = 0; - let low = unsafe { GetFileSize(fh, &mut high) }; - if low == u32::MAX { - let err = io::Error::last_os_error(); - if err.raw_os_error() != Some(0) { - unsafe { CloseHandle(duplicated_handle) }; + let file_len = match host_mmap::get_file_len(fh) { + Ok(len) => len, + Err(err) => { + host_mmap::close_handle(duplicated_handle); return Err(err.to_pyexception(vm)); } - } - let file_len = ((high as i64) << 32) | (low as i64); + }; if map_size == 0 { if file_len == 0 { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("cannot mmap an empty file")); } if offset >= file_len { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("mmap offset is greater than file size")); } if file_len - offset > isize::MAX as i64 { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("mmap length is too large")); } map_size = (file_len - offset) as usize; } else { // If map_size > file_len, extend the file (Windows behavior) let required_size = offset.checked_add(map_size as i64).ok_or_else(|| { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); vm.new_overflow_error("mmap size would cause file size overflow") })?; - if required_size > file_len { - // Extend file using SetFilePointerEx + SetEndOfFile - let result = unsafe { - SetFilePointerEx( - duplicated_handle, - required_size, - core::ptr::null_mut(), - FILE_BEGIN, - ) - }; - if result == 0 { - let err = io::Error::last_os_error(); - unsafe { CloseHandle(duplicated_handle) }; - return Err(err.to_pyexception(vm)); - } - let result = unsafe { SetEndOfFile(duplicated_handle) }; - if result == 0 { - let err = io::Error::last_os_error(); - unsafe { CloseHandle(duplicated_handle) }; - return Err(err.to_pyexception(vm)); - } + if required_size > file_len + && let Err(err) = host_mmap::extend_file(duplicated_handle, required_size) + { + host_mmap::close_handle(duplicated_handle); + return Err(err.to_pyexception(vm)); } } } // When tagname is provided, use raw Win32 APIs for named shared memory if let Some(ref tag) = tag_str { - let (fl_protect, desired_access) = match access { - AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE), - AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ), - AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY), - }; - let fh = if let Some(fh) = fh { // Close the duplicated handle - we'll use the original // file handle for CreateFileMappingW - if duplicated_handle != INVALID_HANDLE_VALUE { - unsafe { CloseHandle(duplicated_handle) }; + if duplicated_handle != host_mmap::INVALID_HANDLE { + host_mmap::close_handle(duplicated_handle); } fh } else { - INVALID_HANDLE_VALUE + host_mmap::INVALID_HANDLE }; - let tag_wide: Vec = tag.encode_utf16().chain(core::iter::once(0)).collect(); - - let total_size = (offset as u64) - .checked_add(map_size as u64) - .ok_or_else(|| vm.new_overflow_error("mmap offset plus size would overflow"))?; - let size_hi = (total_size >> 32) as u32; - let size_lo = total_size as u32; - - let map_handle = unsafe { - CreateFileMappingW( - fh, - core::ptr::null(), - fl_protect, - size_hi, - size_lo, - tag_wide.as_ptr(), - ) - }; - if map_handle.is_null() { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - - let off_hi = (offset as u64 >> 32) as u32; - let off_lo = offset as u32; - - let view = - unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) }; - if view.Value.is_null() { - unsafe { CloseHandle(map_handle) }; - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - - let named = NamedMmap { - map_handle, - view_ptr: view.Value as *mut u8, - len: map_size, - }; + let named = host_mmap::create_named_mapping( + fh, + tag, + match access { + AccessMode::Default => host_mmap::AccessMode::Default, + AccessMode::Read => host_mmap::AccessMode::Read, + AccessMode::Write => host_mmap::AccessMode::Write, + AccessMode::Copy => host_mmap::AccessMode::Copy, + }, + offset, + map_size, + ) + .map_err(|err| { + if err.raw_os_error() == Some(libc::EOVERFLOW) { + vm.new_overflow_error("mmap offset plus size would overflow") + } else { + err.to_pyexception(vm) + } + })?; return Ok(Self { closed: AtomicCell::new(false), mmap: PyMutex::new(Some(MmapObj::Named(named))), - handle: AtomicCell::new(INVALID_HANDLE_VALUE as isize), + handle: AtomicCell::new(host_mmap::INVALID_HANDLE as isize), offset, size: AtomicCell::new(map_size), pos: AtomicCell::new(0), @@ -733,34 +572,14 @@ mod mmap { }); } - let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); - - let (handle, mmap) = if duplicated_handle != INVALID_HANDLE_VALUE { - // Safety: We just duplicated this handle and it's valid - let owned_handle = - unsafe { OwnedHandle::from_raw_handle(duplicated_handle as RawHandle) }; - - let mmap_result = match access { - AccessMode::Default | AccessMode::Write => { - unsafe { mmap_opt.map_mut(&owned_handle) }.map(MmapObj::Write) - } - AccessMode::Read => unsafe { mmap_opt.map(&owned_handle) }.map(MmapObj::Read), - AccessMode::Copy => { - unsafe { mmap_opt.map_copy(&owned_handle) }.map(MmapObj::Write) - } - }; - - let mmap = mmap_result.map_err(|e| e.to_pyexception(vm))?; - - // Keep the handle alive - let raw = owned_handle.as_raw_handle() as isize; - core::mem::forget(owned_handle); - (raw, mmap) + let (handle, mmap) = if duplicated_handle != host_mmap::INVALID_HANDLE { + let mmap = Self::create_mmap_windows(duplicated_handle, offset, map_size, &access) + .map_err(|e| e.to_pyexception(vm))?; + (duplicated_handle as isize, mmap) } else { // Anonymous mapping - let mmap = mmap_opt.map_anon().map_err(|e| e.to_pyexception(vm))?; - (INVALID_HANDLE_VALUE as isize, MmapObj::Write(mmap)) + let mmap = host_mmap::map_anon(map_size).map_err(|e| e.to_pyexception(vm))?; + (host_mmap::INVALID_HANDLE as isize, MmapObj::Mapped(mmap)) }; Ok(Self { @@ -855,12 +674,9 @@ mod mmap { fn as_bytes_mut(&self) -> BorrowedValueMut<'_, [u8]> { PyMutexGuard::map(self.mmap.lock(), |m| { match m.as_mut().expect("mmap closed or invalid") { - MmapObj::Read(_) => panic!("mmap can't modify a readonly memory map."), - MmapObj::Write(mmap) => &mut mmap[..], + MmapObj::Mapped(mmap) => mmap.as_mut_slice(), #[cfg(windows)] - MmapObj::Named(named) => unsafe { - core::slice::from_raw_parts_mut(named.view_ptr, named.len) - }, + MmapObj::Named(named) => named.as_mut_slice(), } }) .into() @@ -898,12 +714,9 @@ mod mmap { } match self.check_valid(vm)?.deref_mut().as_mut().unwrap() { - MmapObj::Write(mmap) => Ok(f(&mut mmap[..])), + MmapObj::Mapped(mmap) => Ok(f(mmap.as_mut_slice())), #[cfg(windows)] - MmapObj::Named(named) => Ok(f(unsafe { - core::slice::from_raw_parts_mut(named.view_ptr, named.len) - })), - _ => unreachable!("already checked"), + MmapObj::Named(named) => Ok(f(named.as_mut_slice())), } } @@ -1010,18 +823,15 @@ mod mmap { } match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(_mmap) => {} - MmapObj::Write(mmap) => { + MmapObj::Mapped(mmap) => { mmap.flush_range(offset, size) .map_err(|e| e.to_pyexception(vm))?; } #[cfg(windows)] MmapObj::Named(named) => { - let ptr = unsafe { named.view_ptr.add(offset) }; - let result = unsafe { FlushViewOfFile(ptr as *const _, size) }; - if result == 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } + named + .flush_range(offset, size) + .map_err(|e| e.to_pyexception(vm))?; } } @@ -1032,22 +842,18 @@ mod mmap { #[pymethod] fn madvise(&self, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> { let (option, start, length) = options.values(self.__len__(), vm)?; - let advice = validate_advice(vm, option)?; + if !host_mmap::validate_advice(option) { + return Err(vm.new_value_error("Not a valid Advice value")); + } let guard = self.check_valid(vm)?; let mmap = guard.deref().as_ref().unwrap(); - let ptr = match mmap { - MmapObj::Read(m) => m.as_ptr(), - MmapObj::Write(m) => m.as_ptr(), - }; - - // Apply madvise to the specified range (start, length) - let ptr_with_offset = unsafe { ptr.add(start) }; - let result = - unsafe { libc::madvise(ptr_with_offset as *mut libc::c_void, length, advice) }; - if result != 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); + match mmap { + MmapObj::Mapped(m) => m.madvise_range(start, length, option), + #[cfg(windows)] + MmapObj::Named(_) => unreachable!("unix-only method"), } + .map_err(|e| e.to_pyexception(vm))?; Ok(()) } @@ -1202,7 +1008,7 @@ mod mmap { return Err(vm.new_os_error("mmap: cannot resize a named memory mapping")); } - let is_anonymous = handle == INVALID_HANDLE_VALUE as isize; + let is_anonymous = host_mmap::is_invalid_handle_value(handle); if is_anonymous { // For anonymous mmap, we need to: @@ -1214,19 +1020,16 @@ mod mmap { let copy_size = core::cmp::min(old_size, newsize); // Create new anonymous mmap - let mut new_mmap_opts = MmapOptions::new(); - let mut new_mmap = new_mmap_opts - .len(newsize) - .map_anon() - .map_err(|e| e.to_pyexception(vm))?; + let mut new_mmap = + host_mmap::map_anon(newsize).map_err(|e| e.to_pyexception(vm))?; // Copy data from old mmap to new mmap if let Some(old_mmap) = mmap_guard.as_ref() { let src = &old_mmap.as_slice()[..copy_size]; - new_mmap[..copy_size].copy_from_slice(src); + new_mmap.as_mut_slice()[..copy_size].copy_from_slice(src); } - *mmap_guard = Some(MmapObj::Write(new_mmap)); + *mmap_guard = Some(MmapObj::Mapped(new_mmap)); self.size.store(newsize); } else { // File-backed mmap resize @@ -1234,34 +1037,26 @@ mod mmap { // Drop the current mmap to release the file mapping *mmap_guard = None; - // Resize the file let required_size = self.offset + newsize as i64; - let result = unsafe { - SetFilePointerEx( - handle as HANDLE, - required_size, - core::ptr::null_mut(), - FILE_BEGIN, - ) - }; - if result == 0 { + if let Err(err) = host_mmap::extend_file(handle as host_mmap::Handle, required_size) + { // Restore original mmap on error - let err = io::Error::last_os_error(); - self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); - return Err(err.to_pyexception(vm)); - } - - let result = unsafe { SetEndOfFile(handle as HANDLE) }; - if result == 0 { - let err = io::Error::last_os_error(); - self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); + self.try_restore_mmap( + &mut mmap_guard, + handle as host_mmap::Handle, + self.size.load(), + ); return Err(err.to_pyexception(vm)); } // Create new mmap with the new size - let new_mmap = - Self::create_mmap_windows(handle as HANDLE, self.offset, newsize, &self.access) - .map_err(|e| e.to_pyexception(vm))?; + let new_mmap = Self::create_mmap_windows( + handle as host_mmap::Handle, + self.offset, + newsize, + &self.access, + ) + .map_err(|e| e.to_pyexception(vm))?; *mmap_guard = Some(new_mmap); self.size.store(newsize); @@ -1319,7 +1114,7 @@ mod mmap { #[pymethod] fn size(&self, vm: &VirtualMachine) -> std::io::Result { let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(self.fd.load())? }; - let file_len = fstat(fd)?.st_size; + let file_len = host_mmap::file_len(fd)?; Ok(PyInt::from(file_len).into_ref(&vm.ctx)) } @@ -1327,20 +1122,13 @@ mod mmap { #[pymethod] fn size(&self, vm: &VirtualMachine) -> PyResult { let handle = self.handle.load(); - if handle == INVALID_HANDLE_VALUE as isize { + if host_mmap::is_invalid_handle_value(handle) { // Anonymous mapping, return the mmap size return Ok(PyInt::from(self.__len__()).into_ref(&vm.ctx)); } - let mut high: u32 = 0; - let low = unsafe { GetFileSize(handle as HANDLE, &mut high) }; - if low == u32::MAX { - let err = io::Error::last_os_error(); - if err.raw_os_error() != Some(0) { - return Err(err.to_pyexception(vm)); - } - } - let file_len = ((high as i64) << 32) | (low as i64); + let file_len = host_mmap::get_file_len(handle as host_mmap::Handle) + .map_err(|e| e.to_pyexception(vm))?; Ok(PyInt::from(file_len).into_ref(&vm.ctx)) } @@ -1426,39 +1214,35 @@ mod mmap { impl PyMmap { #[cfg(windows)] fn create_mmap_windows( - handle: HANDLE, + handle: host_mmap::Handle, offset: i64, size: usize, access: &AccessMode, ) -> io::Result { - use std::fs::File; - - // Create an owned handle wrapper for memmap2 - // We need to create a File from the handle - let file = unsafe { File::from_raw_handle(handle as RawHandle) }; - - let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset as u64).len(size); - - let result = match access { - AccessMode::Default | AccessMode::Write => { - unsafe { mmap_opt.map_mut(&file) }.map(MmapObj::Write) - } - AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MmapObj::Read), - AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MmapObj::Write), - }; - - // Don't close the file handle - we're borrowing it - core::mem::forget(file); - - result + host_mmap::map_handle( + handle, + offset, + size, + match access { + AccessMode::Default => host_mmap::AccessMode::Default, + AccessMode::Read => host_mmap::AccessMode::Read, + AccessMode::Write => host_mmap::AccessMode::Write, + AccessMode::Copy => host_mmap::AccessMode::Copy, + }, + ) + .map(MmapObj::Mapped) } /// Try to restore mmap after a failed resize operation. /// Returns true if restoration succeeded, false otherwise. /// If restoration fails, marks the mmap as closed. #[cfg(windows)] - fn try_restore_mmap(&self, mmap_guard: &mut Option, handle: HANDLE, size: usize) { + fn try_restore_mmap( + &self, + mmap_guard: &mut Option, + handle: host_mmap::Handle, + size: usize, + ) { match Self::create_mmap_windows(handle, self.offset, size, &self.access) { Ok(mmap) => *mmap_guard = Some(mmap), Err(_) => self.closed.store(true), diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index 36f3991022b..475ed7f6c16 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -6,18 +6,12 @@ mod _multiprocessing { use crate::vm::{ Context, FromArgs, Py, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyDict, PyType, PyTypeRef}, + convert::ToPyException, function::{ArgBytesLike, FuncArgs, KwArgs}, types::Constructor, }; use core::sync::atomic::{AtomicI32, AtomicU32, Ordering}; - use windows_sys::Win32::Foundation::{ - CloseHandle, ERROR_TOO_MANY_POSTS, HANDLE, INVALID_HANDLE_VALUE, WAIT_FAILED, - WAIT_OBJECT_0, WAIT_TIMEOUT, - }; - use windows_sys::Win32::Networking::WinSock::{self, SOCKET}; - use windows_sys::Win32::System::Threading::{ - CreateSemaphoreW, GetCurrentThreadId, INFINITE, ReleaseSemaphore, WaitForSingleObjectEx, - }; + use rustpython_host_env::multiprocessing as host_multiprocessing; // These match the values in Lib/multiprocessing/synchronize.py const RECURSIVE_MUTEX: i32 = 0; @@ -26,7 +20,8 @@ mod _multiprocessing { macro_rules! ismine { ($self:expr) => { $self.count.load(Ordering::Acquire) > 0 - && $self.last_tid.load(Ordering::Acquire) == unsafe { GetCurrentThreadId() } + && $self.last_tid.load(Ordering::Acquire) + == host_multiprocessing::current_thread_id() }; } @@ -56,54 +51,7 @@ mod _multiprocessing { count: AtomicI32, } - #[derive(Debug)] - struct SemHandle { - raw: HANDLE, - } - - unsafe impl Send for SemHandle {} - unsafe impl Sync for SemHandle {} - - impl SemHandle { - fn create(value: i32, maxvalue: i32, vm: &VirtualMachine) -> PyResult { - let handle = - unsafe { CreateSemaphoreW(core::ptr::null(), value, maxvalue, core::ptr::null()) }; - if handle == 0 as HANDLE { - return Err(vm.new_last_os_error()); - } - Ok(Self { raw: handle }) - } - - #[inline] - fn as_raw(&self) -> HANDLE { - self.raw - } - } - - impl Drop for SemHandle { - fn drop(&mut self) { - if self.raw != 0 as HANDLE && self.raw != INVALID_HANDLE_VALUE { - unsafe { - CloseHandle(self.raw); - } - } - } - } - - /// _GetSemaphoreValue - get value of semaphore by briefly acquiring and releasing - fn get_semaphore_value(handle: HANDLE) -> Result { - match unsafe { WaitForSingleObjectEx(handle, 0, 0) } { - WAIT_OBJECT_0 => { - let mut previous: i32 = 0; - if unsafe { ReleaseSemaphore(handle, 1, &mut previous) } == 0 { - return Err(()); - } - Ok(previous + 1) - } - WAIT_TIMEOUT => Ok(0), - _ => Err(()), - } - } + type SemHandle = host_multiprocessing::SemHandle; #[pyclass(with(Constructor), flags(BASETYPE))] impl SemLock { @@ -147,13 +95,13 @@ mod _multiprocessing { let full_msecs: u32 = if !blocking { 0 } else if timeout_obj.as_ref().is_none_or(|o| vm.is_none(o)) { - INFINITE + host_multiprocessing::INFINITE_TIMEOUT } else { let timeout: f64 = timeout_obj.unwrap().try_float(vm)?.to_f64(); let timeout = timeout * 1000.0; // convert to ms if timeout < 0.0 { 0 - } else if timeout >= 0.5 * INFINITE as f64 { + } else if timeout >= 0.5 * host_multiprocessing::INFINITE_TIMEOUT as f64 { return Err(vm.new_overflow_error("timeout is too large")); } else { (timeout + 0.5) as u32 @@ -167,14 +115,14 @@ mod _multiprocessing { } // Check whether we can acquire without blocking - match unsafe { WaitForSingleObjectEx(self.handle.as_raw(), 0, 0) } { - WAIT_OBJECT_0 => { + match host_multiprocessing::wait_for_single_object(self.handle.as_raw(), 0) { + x if x == host_multiprocessing::wait_object_0() => { self.last_tid - .store(unsafe { GetCurrentThreadId() }, Ordering::Release); + .store(host_multiprocessing::current_thread_id(), Ordering::Release); self.count.fetch_add(1, Ordering::Release); return Ok(true); } - WAIT_FAILED => return Err(vm.new_last_os_error()), + x if x == host_multiprocessing::wait_failed() => return Err(vm.new_last_os_error()), _ => {} } @@ -183,7 +131,7 @@ mod _multiprocessing { let poll_ms: u32 = 100; let mut elapsed: u32 = 0; loop { - let wait_ms = if full_msecs == INFINITE { + let wait_ms = if full_msecs == host_multiprocessing::INFINITE_TIMEOUT { poll_ms } else { let remaining = full_msecs.saturating_sub(elapsed); @@ -194,22 +142,26 @@ mod _multiprocessing { }; let handle = self.handle.as_raw(); - let res = vm.allow_threads(|| unsafe { WaitForSingleObjectEx(handle, wait_ms, 0) }); + let res = vm.allow_threads(|| { + host_multiprocessing::wait_for_single_object(handle, wait_ms) + }); match res { - WAIT_OBJECT_0 => { + x if x == host_multiprocessing::wait_object_0() => { self.last_tid - .store(unsafe { GetCurrentThreadId() }, Ordering::Release); + .store(host_multiprocessing::current_thread_id(), Ordering::Release); self.count.fetch_add(1, Ordering::Release); return Ok(true); } - WAIT_TIMEOUT => { + x if x == host_multiprocessing::wait_timeout() => { vm.check_signals()?; - if full_msecs != INFINITE { + if full_msecs != host_multiprocessing::INFINITE_TIMEOUT { elapsed = elapsed.saturating_add(wait_ms); } } - WAIT_FAILED => return Err(vm.new_last_os_error()), + x if x == host_multiprocessing::wait_failed() => { + return Err(vm.new_last_os_error()); + } _ => { return Err(vm.new_runtime_error(format!( "WaitForSingleObject() gave unrecognized value {res}" @@ -234,9 +186,8 @@ mod _multiprocessing { } } - if unsafe { ReleaseSemaphore(self.handle.as_raw(), 1, core::ptr::null_mut()) } == 0 { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - if err == ERROR_TOO_MANY_POSTS { + if let Err(err) = host_multiprocessing::release_semaphore(self.handle.as_raw()) { + if host_multiprocessing::is_too_many_posts(err) { return Err(vm.new_value_error("semaphore or lock released too many times")); } return Err(vm.new_last_os_error()); @@ -273,9 +224,7 @@ mod _multiprocessing { ) -> PyResult { // On Windows, _rebuild receives the handle directly (no sem_open) let zelf = Self { - handle: SemHandle { - raw: handle as HANDLE, - }, + handle: SemHandle::from_raw(handle as host_multiprocessing::RawHandle), kind, maxvalue, name, @@ -308,13 +257,14 @@ mod _multiprocessing { #[pymethod] fn _get_value(&self, vm: &VirtualMachine) -> PyResult { - get_semaphore_value(self.handle.as_raw()).map_err(|_| vm.new_last_os_error()) + host_multiprocessing::get_semaphore_value(self.handle.as_raw()) + .map_err(|_| vm.new_last_os_error()) } #[pymethod] fn _is_zero(&self, vm: &VirtualMachine) -> PyResult { - let val = - get_semaphore_value(self.handle.as_raw()).map_err(|_| vm.new_last_os_error())?; + let val = host_multiprocessing::get_semaphore_value(self.handle.as_raw()) + .map_err(|_| vm.new_last_os_error())?; Ok(val == 0) } @@ -346,7 +296,8 @@ mod _multiprocessing { return Err(vm.new_value_error("invalid value")); } - let handle = SemHandle::create(args.value, args.maxvalue, vm)?; + let handle = + SemHandle::create(args.value, args.maxvalue).map_err(|e| e.to_pyexception(vm))?; let name = if args.unlink { None } else { Some(args.name) }; Ok(Self { @@ -372,37 +323,22 @@ mod _multiprocessing { #[pyfunction] fn closesocket(socket: usize, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { WinSock::closesocket(socket as SOCKET) }; - if res != 0 { - Err(vm.new_last_os_error()) - } else { - Ok(()) - } + host_multiprocessing::close_socket(socket as host_multiprocessing::RawSocket) + .map_err(|_| vm.new_last_os_error()) } #[pyfunction] fn recv(socket: usize, size: usize, vm: &VirtualMachine) -> PyResult> { - let mut buf = vec![0u8; size]; - let n_read = - unsafe { WinSock::recv(socket as SOCKET, buf.as_mut_ptr() as *mut _, size as i32, 0) }; - if n_read < 0 { - Err(vm.new_last_os_error()) - } else { - buf.truncate(n_read as usize); - Ok(buf) - } + host_multiprocessing::recv_socket(socket as host_multiprocessing::RawSocket, size) + .map_err(|_| vm.new_last_os_error()) } #[pyfunction] fn send(socket: usize, buf: ArgBytesLike, vm: &VirtualMachine) -> PyResult { - let ret = buf.with_ref(|b| unsafe { - WinSock::send(socket as SOCKET, b.as_ptr() as *const _, b.len() as i32, 0) - }); - if ret < 0 { - Err(vm.new_last_os_error()) - } else { - Ok(ret) - } + buf.with_ref(|b| { + host_multiprocessing::send_socket(socket as host_multiprocessing::RawSocket, b) + }) + .map_err(|_| vm.new_last_os_error()) } } @@ -417,17 +353,19 @@ mod _multiprocessing { function::{FuncArgs, KwArgs}, types::Constructor, }; - use alloc::ffi::CString; use core::sync::atomic::{AtomicI32, AtomicU64, Ordering}; + #[cfg(target_vendor = "apple")] use libc::sem_t; - use nix::errno::Errno; + use rustpython_host_env::multiprocessing::{ + self as host_multiprocessing, SemError, TryAcquireStatus, WaitStatus, + }; /// Error type for sem_timedwait operations #[cfg(target_vendor = "apple")] enum SemWaitError { Timeout, SignalException(PyBaseExceptionRef), - OsError(Errno), + OsError(SemError), } /// macOS fallback for sem_timedwait using select + sem_trywait polling @@ -441,60 +379,19 @@ mod _multiprocessing { let mut delay: u64 = 0; loop { - // poll: try to acquire - if unsafe { libc::sem_trywait(sem) } == 0 { - return Ok(()); - } - let err = Errno::last(); - if err != Errno::EAGAIN { - return Err(SemWaitError::OsError(err)); - } - - // get current time - let mut now = libc::timeval { - tv_sec: 0, - tv_usec: 0, - }; - if unsafe { libc::gettimeofday(&mut now, core::ptr::null_mut()) } < 0 { - return Err(SemWaitError::OsError(Errno::last())); - } - - // check for timeout - let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000; - #[allow(clippy::unnecessary_cast)] - let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64; - - if now_usec >= deadline_usec { - return Err(SemWaitError::Timeout); - } - - // calculate how much time is left - let difference = (deadline_usec - now_usec) as u64; - - // check delay not too long -- maximum is 20 msecs - delay += 1000; - if delay > 20000 { - delay = 20000; - } - if delay > difference { - delay = difference; + match vm.allow_threads(|| { + host_multiprocessing::sem_timedwait_poll_step(sem, deadline, delay) + }) { + Ok(host_multiprocessing::PollWaitStep::Acquired) => return Ok(()), + Ok(host_multiprocessing::PollWaitStep::Timeout) => { + return Err(SemWaitError::Timeout); + } + Ok(host_multiprocessing::PollWaitStep::Continue(next_delay)) => { + delay = next_delay; + } + Err(err) => return Err(SemWaitError::OsError(err)), } - // sleep using select - let mut tv_delay = libc::timeval { - tv_sec: (delay / 1_000_000) as _, - tv_usec: (delay % 1_000_000) as _, - }; - vm.allow_threads(|| unsafe { - libc::select( - 0, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut tv_delay, - ) - }); - // check for signals - preserve the exception (e.g., KeyboardInterrupt) if let Err(exc) = vm.check_signals() { return Err(SemWaitError::SignalException(exc)); @@ -510,7 +407,8 @@ mod _multiprocessing { macro_rules! ismine { ($self:expr) => { $self.count.load(Ordering::Acquire) > 0 - && $self.last_tid.load(Ordering::Acquire) == current_thread_id() + && $self.last_tid.load(Ordering::Acquire) + == host_multiprocessing::current_thread_id() }; } @@ -540,70 +438,7 @@ mod _multiprocessing { count: AtomicI32, // int } - #[derive(Debug)] - struct SemHandle { - raw: *mut sem_t, - } - - unsafe impl Send for SemHandle {} - unsafe impl Sync for SemHandle {} - - impl SemHandle { - fn create( - name: &str, - value: u32, - unlink: bool, - vm: &VirtualMachine, - ) -> PyResult<(Self, Option)> { - let cname = semaphore_name(vm, name)?; - // SEM_CREATE(name, val, max) sem_open(name, O_CREAT | O_EXCL, 0600, val) - let raw = unsafe { - libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) - }; - if raw == libc::SEM_FAILED { - let err = Errno::last(); - return Err(os_error(vm, err)); - } - if unlink { - // SEM_UNLINK(name) sem_unlink(name) - unsafe { - libc::sem_unlink(cname.as_ptr()); - } - Ok((Self { raw }, None)) - } else { - Ok((Self { raw }, Some(name.to_owned()))) - } - } - - fn open_existing(name: &str, vm: &VirtualMachine) -> PyResult { - let cname = semaphore_name(vm, name)?; - let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) }; - if raw == libc::SEM_FAILED { - let err = Errno::last(); - return Err(os_error(vm, err)); - } - Ok(Self { raw }) - } - - #[inline] - fn as_ptr(&self) -> *mut sem_t { - self.raw - } - } - - impl Drop for SemHandle { - fn drop(&mut self) { - // Guard against default/uninitialized state. - // Note: SEM_FAILED is (sem_t*)-1, not null, but valid handles are never null - // and SEM_FAILED is never stored (error is returned immediately on sem_open failure). - if !self.raw.is_null() { - // SEM_CLOSE(sem) sem_close(sem) - unsafe { - libc::sem_close(self.raw); - } - } - } - } + type SemHandle = host_multiprocessing::SemHandle; #[pyclass(with(Constructor), flags(BASETYPE))] impl SemLock { @@ -659,54 +494,26 @@ mod _multiprocessing { let timeout_obj = timeout_obj.unwrap(); // This accepts both int and float, converting to f64 let timeout: f64 = timeout_obj.try_float(vm)?.to_f64(); - let timeout = if timeout < 0.0 { 0.0 } else { timeout }; - - let mut tv = libc::timeval { - tv_sec: 0, - tv_usec: 0, - }; - let res = unsafe { libc::gettimeofday(&mut tv, core::ptr::null_mut()) }; - if res < 0 { - return Err(vm.new_os_error("gettimeofday failed".to_string())); - } - - // deadline calculation: - // long sec = (long) timeout; - // long nsec = (long) (1e9 * (timeout - sec) + 0.5); - // deadline.tv_sec = now.tv_sec + sec; - // deadline.tv_nsec = now.tv_usec * 1000 + nsec; - // deadline.tv_sec += (deadline.tv_nsec / 1000000000); - // deadline.tv_nsec %= 1000000000; - let sec = timeout as libc::c_long; - let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long; - let mut deadline = libc::timespec { - tv_sec: tv.tv_sec + sec as libc::time_t, - tv_nsec: (tv.tv_usec as libc::c_long * 1000 + nsec) as _, - }; - deadline.tv_sec += (deadline.tv_nsec / 1_000_000_000) as libc::time_t; - deadline.tv_nsec %= 1_000_000_000; - Some(deadline) + Some( + host_multiprocessing::deadline_from_timeout(timeout) + .map_err(|_| vm.new_os_error("gettimeofday failed".to_string()))?, + ) } else { None }; // Check whether we can acquire without releasing the GIL and blocking - let mut res; - loop { - res = unsafe { libc::sem_trywait(self.handle.as_ptr()) }; - if res >= 0 { - break; - } - let err = Errno::last(); - if err == Errno::EINTR { - vm.check_signals()?; - continue; + let try_status = loop { + match host_multiprocessing::sem_trywait_status(self.handle.as_ptr()) { + TryAcquireStatus::Interrupted => { + vm.check_signals()?; + } + status => break status, } - break; - } + }; // if (res < 0 && errno == EAGAIN && blocking) - if res < 0 && Errno::last() == Errno::EAGAIN && blocking { + if matches!(try_status, TryAcquireStatus::WouldBlock) && blocking { // Couldn't acquire immediately, need to block. // // Save errno inside the allow_threads closure, before @@ -715,49 +522,20 @@ mod _multiprocessing { #[cfg(not(target_vendor = "apple"))] { - let mut saved_errno; loop { let sem_ptr = self.handle.as_ptr(); // Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS - let (r, e) = if let Some(ref dl) = deadline { - vm.allow_threads(|| { - let r = unsafe { libc::sem_timedwait(sem_ptr, dl) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) - }) - } else { - vm.allow_threads(|| { - let r = unsafe { libc::sem_wait(sem_ptr) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) - }) - }; - res = r; - saved_errno = e; - - if res >= 0 { - break; - } - if saved_errno == Errno::EINTR { - vm.check_signals()?; - continue; + match vm.allow_threads(|| { + host_multiprocessing::sem_wait_status(sem_ptr, deadline.as_ref()) + }) { + WaitStatus::Acquired => break, + WaitStatus::Interrupted => { + vm.check_signals()?; + continue; + } + WaitStatus::TimedOut => return Ok(false), + WaitStatus::Error(err) => return Err(os_error(vm, err)), } - break; - } - if res < 0 { - return handle_wait_error(vm, saved_errno); } } #[cfg(target_vendor = "apple")] @@ -778,50 +556,35 @@ mod _multiprocessing { } } else { // No timeout: use sem_wait (available on macOS) - let mut saved_errno; loop { let sem_ptr = self.handle.as_ptr(); - let (r, e) = vm.allow_threads(|| { - let r = unsafe { libc::sem_wait(sem_ptr) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) - }); - res = r; - saved_errno = e; - if res >= 0 { - break; + match vm.allow_threads(|| { + host_multiprocessing::sem_wait_status(sem_ptr, None) + }) { + WaitStatus::Acquired => break, + WaitStatus::Interrupted => { + vm.check_signals()?; + continue; + } + WaitStatus::TimedOut => return Ok(false), + WaitStatus::Error(err) => return Err(os_error(vm, err)), } - if saved_errno == Errno::EINTR { - vm.check_signals()?; - continue; - } - break; - } - if res < 0 { - return handle_wait_error(vm, saved_errno); } } } - } else if res < 0 { + } else if !matches!(try_status, TryAcquireStatus::Acquired) { // Non-blocking path failed, or blocking=false - let err = Errno::last(); - match err { - Errno::EAGAIN | Errno::ETIMEDOUT => return Ok(false), - Errno::EINTR => { - return vm.check_signals().map(|_| false); - } - _ => return Err(os_error(vm, err)), + match try_status { + TryAcquireStatus::WouldBlock => return Ok(false), + TryAcquireStatus::Interrupted => return vm.check_signals().map(|_| false), + TryAcquireStatus::Error(err) => return Err(os_error(vm, err)), + TryAcquireStatus::Acquired => unreachable!(), } } self.count.fetch_add(1, Ordering::Release); - self.last_tid.store(current_thread_id(), Ordering::Release); + self.last_tid + .store(host_multiprocessing::current_thread_id(), Ordering::Release); Ok(true) } @@ -849,11 +612,9 @@ mod _multiprocessing { #[cfg(not(target_vendor = "apple"))] { // Linux: use sem_getvalue - let mut sval: libc::c_int = 0; - let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } + let sval = + unsafe { host_multiprocessing::get_semaphore_value(self.handle.as_ptr()) } + .map_err(|err| os_error(vm, err))?; if sval >= self.maxvalue { return Err(vm.new_value_error("semaphore or lock released too many times")); } @@ -864,27 +625,29 @@ mod _multiprocessing { // We will only check properly the maxvalue == 1 case if self.maxvalue == 1 { // make sure that already locked - if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { - if Errno::last() != Errno::EAGAIN { - return Err(os_error(vm, Errno::last())); + match host_multiprocessing::sem_trywait_status(self.handle.as_ptr()) { + TryAcquireStatus::WouldBlock => {} + TryAcquireStatus::Acquired => { + if let Err(err) = + host_multiprocessing::sem_post(self.handle.as_ptr()) + { + return Err(os_error(vm, err)); + } + return Err( + vm.new_value_error("semaphore or lock released too many times") + ); } - // it is already locked as expected - } else { - // it was not locked so undo wait and raise - if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { - return Err(os_error(vm, Errno::last())); + TryAcquireStatus::Interrupted => { + return Err(os_error(vm, SemError::Interrupted)); } - return Err( - vm.new_value_error("semaphore or lock released too many times") - ); + TryAcquireStatus::Error(err) => return Err(os_error(vm, err)), } } } } - let res = unsafe { libc::sem_post(self.handle.as_ptr()) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_post(self.handle.as_ptr()) { + return Err(os_error(vm, err)); } self.count.fetch_sub(1, Ordering::Release); @@ -926,7 +689,7 @@ mod _multiprocessing { let Some(ref name_str) = name else { return Err(vm.new_value_error("cannot rebuild SemLock without name")); }; - let handle = SemHandle::open_existing(name_str, vm)?; + let handle = SemHandle::open_existing(name_str).map_err(|err| os_error(vm, err))?; // return newsemlockobject(type, handle, kind, maxvalue, name_copy); let zelf = Self { handle, @@ -976,14 +739,8 @@ mod _multiprocessing { #[cfg(not(target_vendor = "apple"))] { // Linux: use sem_getvalue - let mut sval: libc::c_int = 0; - let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } - // some posix implementations use negative numbers to indicate - // the number of waiting threads - Ok(if sval < 0 { 0 } else { sval }) + unsafe { host_multiprocessing::get_semaphore_value(self.handle.as_ptr()) } + .map_err(|err| os_error(vm, err)) } #[cfg(target_vendor = "apple")] { @@ -1004,15 +761,17 @@ mod _multiprocessing { { // macOS: HAVE_BROKEN_SEM_GETVALUE // Try to acquire - if EAGAIN, value is 0 - if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { - if Errno::last() == Errno::EAGAIN { - return Ok(true); + match host_multiprocessing::sem_trywait_status(self.handle.as_ptr()) { + TryAcquireStatus::WouldBlock => return Ok(true), + TryAcquireStatus::Interrupted => { + return Err(os_error(vm, SemError::Interrupted)); } - return Err(os_error(vm, Errno::last())); + TryAcquireStatus::Error(err) => return Err(os_error(vm, err)), + TryAcquireStatus::Acquired => {} } // Successfully acquired - undo and return false - if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_post(self.handle.as_ptr()) { + return Err(os_error(vm, err)); } Ok(false) } @@ -1027,14 +786,7 @@ mod _multiprocessing { class.set_attr(ctx.intern_str("SEMAPHORE"), ctx.new_int(SEMAPHORE).into()); // SEM_VALUE_MAX from system, or INT_MAX if negative // We use a reasonable default - let sem_value_max: i32 = unsafe { - let val = libc::sysconf(libc::_SC_SEM_VALUE_MAX); - if val < 0 || val > i32::MAX as libc::c_long { - i32::MAX - } else { - val as i32 - } - }; + let sem_value_max = host_multiprocessing::sem_value_max(); class.set_attr( ctx.intern_str("SEM_VALUE_MAX"), ctx.new_int(sem_value_max).into(), @@ -1057,7 +809,14 @@ mod _multiprocessing { } let value = args.value as u32; - let (handle, name) = SemHandle::create(&args.name, value, args.unlink, vm)?; + let (handle, name) = + SemHandle::create(&args.name, value, args.unlink).map_err(|err| { + if err == SemError::InvalidInput && args.name.contains('\0') { + vm.new_value_error("embedded null character") + } else { + os_error(vm, err) + } + })?; // return newsemlockobject(type, handle, kind, maxvalue, name_copy); Ok(Self { @@ -1075,12 +834,13 @@ mod _multiprocessing { // _PyMp_sem_unlink. #[pyfunction] fn sem_unlink(name: String, vm: &VirtualMachine) -> PyResult<()> { - let cname = semaphore_name(vm, &name)?; - let res = unsafe { libc::sem_unlink(cname.as_ptr()) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } - Ok(()) + host_multiprocessing::sem_unlink(&name).map_err(|err| { + if err == SemError::InvalidInput && name.contains('\0') { + vm.new_value_error("embedded null character") + } else { + os_error(vm, err) + } + }) } /// Module-level flags dict. @@ -1111,40 +871,16 @@ mod _multiprocessing { flags } - fn semaphore_name(vm: &VirtualMachine, name: &str) -> PyResult { - // POSIX semaphore names must start with / - let mut full = String::with_capacity(name.len() + 1); - if !name.starts_with('/') { - full.push('/'); - } - full.push_str(name); - CString::new(full).map_err(|_| vm.new_value_error("embedded null character")) - } - - fn handle_wait_error(vm: &VirtualMachine, saved_errno: Errno) -> PyResult { - match saved_errno { - Errno::EAGAIN | Errno::ETIMEDOUT => Ok(false), - Errno::EINTR => vm.check_signals().map(|_| false), - _ => Err(os_error(vm, saved_errno)), - } - } - - fn os_error(vm: &VirtualMachine, err: Errno) -> PyBaseExceptionRef { + fn os_error(vm: &VirtualMachine, err: SemError) -> PyBaseExceptionRef { // _PyMp_SetError maps to PyErr_SetFromErrno let exc_type = match err { - Errno::EEXIST => vm.ctx.exceptions.file_exists_error.to_owned(), - Errno::ENOENT => vm.ctx.exceptions.file_not_found_error.to_owned(), + SemError::AlreadyExists => vm.ctx.exceptions.file_exists_error.to_owned(), + SemError::NotFound => vm.ctx.exceptions.file_not_found_error.to_owned(), _ => vm.ctx.exceptions.os_error.to_owned(), }; - vm.new_os_subtype_error(exc_type, Some(err as i32), err.desc().to_owned()) + vm.new_os_subtype_error(exc_type, Some(err.raw_os_error()), err.description()) .upcast() } - - /// Get current thread identifier. - /// PyThread_get_thread_ident on Unix (pthread_self). - fn current_thread_id() -> u64 { - unsafe { libc::pthread_self() as u64 } - } } #[cfg(all(not(unix), not(windows)))] diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 24cbe8a40a3..f559726c224 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -4102,35 +4102,30 @@ mod windows { #[pyfunction] fn enum_certificates(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult> { - use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; - use windows_sys::Win32::Security::Cryptography; - - // TODO: check every store for it, not just 2 of them: - // https://github.com/python/cpython/blob/3.8/Modules/_ssl.c#L5603-L5610 - let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; - let stores = open_fns - .iter() - .filter_map(|open| open(store_name.as_str()).ok()) - .collect::>(); - let certs = stores.iter().flat_map(|s| s.certs()).map(|c| { - let cert = vm.ctx.new_bytes(c.to_der().to_owned()); - let enc_type = unsafe { - let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; - (*ptr).dwCertEncodingType - }; - let enc_type = match enc_type { - Cryptography::X509_ASN_ENCODING => vm.new_pyobj(ascii!("x509_asn")), - Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj(ascii!("pkcs_7_asn")), - other => vm.new_pyobj(other), + let certs = rustpython_host_env::cert_store::enum_certificates(store_name.as_str()); + let certs = certs.entries.into_iter().map(|c| { + let cert = vm.ctx.new_bytes(c.der); + let enc_type = match c.encoding { + rustpython_host_env::cert_store::EncodingType::X509Asn => { + vm.new_pyobj(ascii!("x509_asn")) + } + rustpython_host_env::cert_store::EncodingType::Pkcs7Asn => { + vm.new_pyobj(ascii!("pkcs_7_asn")) + } + rustpython_host_env::cert_store::EncodingType::Other(other) => vm.new_pyobj(other), }; - let usage: PyObjectRef = match c.valid_uses().map_err(|e| e.to_pyexception(vm))? { - ValidUses::All => vm.ctx.new_bool(true).into(), - ValidUses::Oids(oids) => PyFrozenSet::from_iter( - vm, - oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), - )? - .into_ref(&vm.ctx) - .into(), + let usage: PyObjectRef = match c.valid_uses.map_err(|e| e.to_pyexception(vm))? { + rustpython_host_env::cert_store::CertificateUses::All => { + vm.ctx.new_bool(true).into() + } + rustpython_host_env::cert_store::CertificateUses::Oids(oids) => { + PyFrozenSet::from_iter( + vm, + oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), + )? + .into_ref(&vm.ctx) + .into() + } }; Ok(vm.new_tuple((cert, enc_type, usage)).into()) }); diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index bc1fb62341e..7ff51c4c26a 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -17,127 +17,45 @@ mod _overlapped { protocol::PyBuffer, types::{Constructor, Destructor}, }; - use windows_sys::Win32::{ - Foundation::{self, GetLastError, HANDLE}, - Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, - System::IO::OVERLAPPED, + use rustpython_host_env::{ + overlapped as host_overlapped, winapi as host_winapi, windows as host_windows, }; pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { let _ = vm.import("_socket", 0)?; - initialize_winsock_extensions(vm)?; + host_overlapped::initialize_winsock_extensions() + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))?; __module_exec(vm, module); Ok(()) } #[pyattr] - use windows_sys::Win32::{ - Foundation::{ - ERROR_IO_PENDING, ERROR_NETNAME_DELETED, ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY, - ERROR_PORT_UNREACHABLE, ERROR_SEM_TIMEOUT, - }, - Networking::WinSock::{ - SO_UPDATE_ACCEPT_CONTEXT, SO_UPDATE_CONNECT_CONTEXT, TF_REUSE_SOCKET, - }, - System::Threading::INFINITE, - }; + const ERROR_IO_PENDING: u32 = host_winapi::ERROR_IO_PENDING; + #[pyattr] + const ERROR_NETNAME_DELETED: u32 = host_winapi::ERROR_NETNAME_DELETED; + #[pyattr] + const ERROR_OPERATION_ABORTED: u32 = host_winapi::ERROR_OPERATION_ABORTED; + #[pyattr] + const ERROR_PIPE_BUSY: u32 = host_winapi::ERROR_PIPE_BUSY; + #[pyattr] + const ERROR_PORT_UNREACHABLE: u32 = host_winapi::ERROR_PORT_UNREACHABLE; + #[pyattr] + const ERROR_SEM_TIMEOUT: u32 = host_winapi::ERROR_SEM_TIMEOUT; + #[pyattr] + const SO_UPDATE_ACCEPT_CONTEXT: i32 = host_overlapped::SO_UPDATE_ACCEPT_CONTEXT_VALUE; + #[pyattr] + const SO_UPDATE_CONNECT_CONTEXT: i32 = host_overlapped::SO_UPDATE_CONNECT_CONTEXT_VALUE; + #[pyattr] + const TF_REUSE_SOCKET: u32 = host_overlapped::TF_REUSE_SOCKET_FLAG; + #[pyattr] + const INFINITE: u32 = host_winapi::INFINITE_TIMEOUT; #[pyattr] - const INVALID_HANDLE_VALUE: isize = - unsafe { core::mem::transmute(windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE) }; + const INVALID_HANDLE_VALUE: isize = host_overlapped::INVALID_HANDLE_VALUE_ISIZE; #[pyattr] const NULL: isize = 0; - // Function pointers for Winsock extension functions - static ACCEPT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static CONNECT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static DISCONNECT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static TRANSMIT_FILE: std::sync::OnceLock = std::sync::OnceLock::new(); - - fn initialize_winsock_extensions(vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{ - INVALID_SOCKET, IPPROTO_TCP, SIO_GET_EXTENSION_FUNCTION_POINTER, SOCK_STREAM, - SOCKET_ERROR, WSAGetLastError, WSAIoctl, closesocket, socket, - }; - - // GUIDs for extension functions - const WSAID_ACCEPTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0xb5367df1, - data2: 0xcbac, - data3: 0x11cf, - data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], - }; - const WSAID_CONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0x25a207b9, - data2: 0xddf3, - data3: 0x4660, - data4: [0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e], - }; - const WSAID_DISCONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0x7fda2e11, - data2: 0x8630, - data3: 0x436f, - data4: [0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57], - }; - const WSAID_TRANSMITFILE: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0xb5367df0, - data2: 0xcbac, - data3: 0x11cf, - data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], - }; - - // Check all four locks to prevent partial initialization - if ACCEPT_EX.get().is_some() - && CONNECT_EX.get().is_some() - && DISCONNECT_EX.get().is_some() - && TRANSMIT_FILE.get().is_some() - { - return Ok(()); - } - - let s = unsafe { socket(AF_INET as i32, SOCK_STREAM, IPPROTO_TCP) }; - if s == INVALID_SOCKET { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - - let mut dw_bytes: u32 = 0; - - macro_rules! get_extension { - ($guid:expr, $lock:expr) => {{ - let mut func_ptr: usize = 0; - let ret = unsafe { - WSAIoctl( - s, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &$guid as *const _ as *const _, - core::mem::size_of_val(&$guid) as u32, - &mut func_ptr as *mut _ as *mut _, - core::mem::size_of::() as u32, - &mut dw_bytes, - core::ptr::null_mut(), - None, - ) - }; - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - unsafe { closesocket(s) }; - return Err(set_from_windows_err(err, vm)); - } - let _ = $lock.set(func_ptr); - }}; - } - - get_extension!(WSAID_ACCEPTEX, ACCEPT_EX); - get_extension!(WSAID_CONNECTEX, CONNECT_EX); - get_extension!(WSAID_DISCONNECTEX, DISCONNECT_EX); - get_extension!(WSAID_TRANSMITFILE, TRANSMIT_FILE); - - unsafe { closesocket(s) }; - Ok(()) - } - #[pyattr] #[pyclass(name, traverse)] #[derive(PyPayload)] @@ -146,8 +64,8 @@ mod _overlapped { } struct OverlappedInner { - overlapped: OVERLAPPED, - handle: HANDLE, + overlapped: host_overlapped::OverlappedIo, + handle: host_overlapped::Handle, error: u32, data: OverlappedData, } @@ -223,7 +141,7 @@ mod _overlapped { result: Option, // The actual read buffer allocated_buffer: PyBytesRef, - address: SOCKADDR_IN6, + address: host_overlapped::SocketAddrV6, address_length: i32, } @@ -242,7 +160,7 @@ mod _overlapped { result: Option, /* Buffer passed by the user */ user_buffer: PyBuffer, - address: SOCKADDR_IN6, + address: host_overlapped::SocketAddrV6, address_length: i32, } @@ -270,16 +188,9 @@ mod _overlapped { } } - fn mark_as_completed(ov: &mut OVERLAPPED) { - ov.Internal = 0; - if !ov.hEvent.is_null() { - unsafe { windows_sys::Win32::System::Threading::SetEvent(ov.hEvent) }; - } - } - fn set_from_windows_err(err: u32, vm: &VirtualMachine) -> PyBaseExceptionRef { let err = if err == 0 { - unsafe { GetLastError() } + host_winapi::get_last_error() } else { err }; @@ -292,51 +203,16 @@ mod _overlapped { exc.upcast() } - fn HasOverlappedIoCompleted(overlapped: &OVERLAPPED) -> bool { - overlapped.Internal != (Foundation::STATUS_PENDING as usize) - } - /// Parse a Python address tuple to SOCKADDR fn parse_address(addr_obj: &PyTupleRef, vm: &VirtualMachine) -> PyResult<(Vec, i32)> { - use windows_sys::Win32::Networking::WinSock::{WSAGetLastError, WSAStringToAddressW}; - match addr_obj.len() { 2 => { // IPv4: (host, port) let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?; let port: u16 = addr_obj[1].clone().try_to_value(vm)?; - - let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; - addr.sin_family = AF_INET; - let host_wide: Vec = host.as_wtf8().encode_wide().chain([0]).collect(); - let mut addr_len = core::mem::size_of::() as i32; - - let ret = unsafe { - WSAStringToAddressW( - host_wide.as_ptr(), - AF_INET as i32, - core::ptr::null(), - &mut addr as *mut _ as *mut SOCKADDR, - &mut addr_len, - ) - }; - - if ret < 0 { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - - // Restore port (WSAStringToAddressW overwrites it) - addr.sin_port = port.to_be(); - - let bytes = unsafe { - core::slice::from_raw_parts( - &addr as *const _ as *const u8, - core::mem::size_of::(), - ) - }; - Ok((bytes.to_vec(), addr_len)) + host_overlapped::parse_address_v4_wide(&host_wide, port) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } 4 => { // IPv6: (host, port, flowinfo, scope_id) @@ -344,71 +220,33 @@ mod _overlapped { let port: u16 = addr_obj[1].clone().try_to_value(vm)?; let flowinfo: u32 = addr_obj[2].clone().try_to_value(vm)?; let scope_id: u32 = addr_obj[3].clone().try_to_value(vm)?; - - let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; - addr.sin6_family = AF_INET6; - let host_wide: Vec = host.as_wtf8().encode_wide().chain([0]).collect(); - let mut addr_len = core::mem::size_of::() as i32; - - let ret = unsafe { - WSAStringToAddressW( - host_wide.as_ptr(), - AF_INET6 as i32, - core::ptr::null(), - &mut addr as *mut _ as *mut SOCKADDR, - &mut addr_len, - ) - }; - - if ret < 0 { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - - // Restore fields that WSAStringToAddressW might overwrite - addr.sin6_port = port.to_be(); - addr.sin6_flowinfo = flowinfo; - addr.Anonymous.sin6_scope_id = scope_id; - - let bytes = unsafe { - core::slice::from_raw_parts( - &addr as *const _ as *const u8, - core::mem::size_of::(), - ) - }; - Ok((bytes.to_vec(), addr_len)) + host_overlapped::parse_address_v6_wide(&host_wide, port, flowinfo, scope_id) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } _ => Err(vm.new_value_error("illegal address_as_bytes argument")), } } /// Parse a SOCKADDR_IN6 (which can also hold IPv4 addresses) to a Python address tuple - fn unparse_address(addr: &SOCKADDR_IN6, _addr_len: i32, vm: &VirtualMachine) -> PyResult { - use core::net::{Ipv4Addr, Ipv6Addr}; - - unsafe { - let family = addr.sin6_family; - if family == AF_INET { - // IPv4 address stored in SOCKADDR_IN6 structure - let addr_in = &*(addr as *const SOCKADDR_IN6 as *const SOCKADDR_IN); - let ip_bytes = addr_in.sin_addr.S_un.S_un_b; - let ip_str = - Ipv4Addr::new(ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4) - .to_string(); - let port = u16::from_be(addr_in.sin_port); - Ok((ip_str, port).to_pyobject(vm)) - } else if family == AF_INET6 { - // IPv6 address - let ip_bytes = addr.sin6_addr.u.Byte; - let ip_str = Ipv6Addr::from(ip_bytes).to_string(); - let port = u16::from_be(addr.sin6_port); - let flowinfo = u32::from_be(addr.sin6_flowinfo); - let scope_id = addr.Anonymous.sin6_scope_id; - Ok((ip_str, port, flowinfo, scope_id).to_pyobject(vm)) - } else { - Err(vm.new_value_error("recvfrom returned unsupported address family")) - } + fn unparse_address( + addr: &host_overlapped::SocketAddrV6, + addr_len: i32, + vm: &VirtualMachine, + ) -> PyResult { + match host_overlapped::unparse_address( + addr as *const _ as *const host_overlapped::SocketAddrRaw, + addr_len, + ) + .map_err(|_| vm.new_value_error("recvfrom returned unsupported address family"))? + { + host_overlapped::SocketAddress::V4 { host, port } => Ok((host, port).to_pyobject(vm)), + host_overlapped::SocketAddress::V6 { + host, + port, + flowinfo, + scope_id, + } => Ok((host, port, flowinfo, scope_id).to_pyobject(vm)), } } @@ -423,7 +261,7 @@ mod _overlapped { #[pygetset] fn pending(&self, _vm: &VirtualMachine) -> bool { let inner = self.inner.lock(); - !HasOverlappedIoCompleted(&inner.overlapped) + !host_overlapped::has_overlapped_io_completed(&inner.overlapped) && !matches!(inner.data, OverlappedData::NotStarted) } @@ -448,25 +286,17 @@ mod _overlapped { ) { return Ok(()); } - let ret = if !HasOverlappedIoCompleted(&inner.overlapped) { - unsafe { - windows_sys::Win32::System::IO::CancelIoEx(inner.handle, &inner.overlapped) - } - } else { - 1 - }; - // CancelIoEx returns ERROR_NOT_FOUND if the I/O completed in-between - if ret == 0 && unsafe { GetLastError() } != Foundation::ERROR_NOT_FOUND { - return Err(set_from_windows_err(0, vm)); + if !host_overlapped::has_overlapped_io_completed(&inner.overlapped) { + host_overlapped::cancel_overlapped(inner.handle, &inner.overlapped).map_err( + |err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm), + )?; } Ok(()) } #[pymethod] fn getresult(zelf: &Py, wait: OptionalArg, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{ - ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_SUCCESS, - }; + use host_winapi::{ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); let wait = wait.unwrap_or(false); @@ -479,22 +309,10 @@ mod _overlapped { return Err(vm.new_value_error("operation failed to start")); } - // Get the result - let mut transferred: u32 = 0; - let ret = unsafe { - windows_sys::Win32::System::IO::GetOverlappedResult( - inner.handle, - &inner.overlapped, - &mut transferred, - if wait { 1 } else { 0 }, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let result = + host_overlapped::get_overlapped_result(inner.handle, &inner.overlapped, wait); + let transferred = result.transferred; + let err = result.error; inner.error = err; // Handle errors @@ -566,10 +384,9 @@ mod _overlapped { // ReadFile #[pymethod] fn ReadFile(zelf: &Py, handle: isize, size: u32, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Storage::FileSystem::ReadFile; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -581,30 +398,20 @@ mod _overlapped { let buf = vec![0u8; core::cmp::max(size, 1) as usize]; let buf = vm.ctx.new_bytes(buf); - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; inner.data = OverlappedData::Read(buf.clone()); - let mut nread: u32 = 0; - let ret = unsafe { - ReadFile( - handle as HANDLE, - buf.as_bytes().as_ptr() as *mut _, - size, - &mut nread, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_read_file( + handle as host_overlapped::Handle, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -623,17 +430,16 @@ mod _overlapped { buf: PyBuffer, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Storage::FileSystem::ReadFile; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; let buf_len = buf.desc.len; if buf_len > u32::MAX as usize { return Err(vm.new_value_error("buffer too large")); @@ -641,33 +447,23 @@ mod _overlapped { // For async read, buffer must be contiguous - we can't use a temporary copy // because Windows writes data directly to the buffer after this call returns - let Some(contiguous) = buf.as_contiguous_mut() else { + let Some(mut contiguous) = buf.as_contiguous_mut() else { return Err(vm.new_buffer_error("buffer is not contiguous")); }; inner.data = OverlappedData::ReadInto(buf.clone()); - let mut nread: u32 = 0; - let ret = unsafe { - ReadFile( - handle as HANDLE, - contiguous.as_ptr() as *mut _, - buf_len as u32, - &mut nread, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_read_file( + handle as host_overlapped::Handle, + contiguous.as_mut_ptr(), + buf_len as u32, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -687,10 +483,9 @@ mod _overlapped { flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -704,37 +499,21 @@ mod _overlapped { let buf = vec![0u8; core::cmp::max(size, 1) as usize]; let buf = vm.ctx.new_bytes(buf); - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; inner.data = OverlappedData::Read(buf.clone()); - let wsabuf = WSABUF { - buf: buf.as_bytes().as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - - let ret = unsafe { - WSARecv( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv( + handle as usize, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut flags, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -754,10 +533,9 @@ mod _overlapped { flags: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -765,46 +543,30 @@ mod _overlapped { } let mut flags = flags; - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; let buf_len = buf.desc.len; if buf_len > u32::MAX as usize { return Err(vm.new_value_error("buffer too large")); } - let Some(contiguous) = buf.as_contiguous_mut() else { + let Some(mut contiguous) = buf.as_contiguous_mut() else { return Err(vm.new_buffer_error("buffer is not contiguous")); }; inner.data = OverlappedData::ReadInto(buf.clone()); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut nread: u32 = 0; - - let ret = unsafe { - WSARecv( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv( + handle as usize, + contiguous.as_mut_ptr(), + buf_len as u32, + &mut flags, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -823,15 +585,14 @@ mod _overlapped { buf: PyBuffer, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Storage::FileSystem::WriteFile; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; let buf_len = buf.desc.len; if buf_len > u32::MAX as usize { return Err(vm.new_value_error("buffer too large")); @@ -845,22 +606,12 @@ mod _overlapped { inner.data = OverlappedData::Write(buf.clone()); - let mut written: u32 = 0; - let ret = unsafe { - WriteFile( - handle as HANDLE, - contiguous.as_ptr() as *const _, - buf_len as u32, - &mut written, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_write_file( + handle as host_overlapped::Handle, + contiguous.as_ptr(), + buf_len as u32, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -881,15 +632,14 @@ mod _overlapped { flags: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASend}; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; let buf_len = buf.desc.len; if buf_len > u32::MAX as usize { return Err(vm.new_value_error("buffer too large")); @@ -901,29 +651,13 @@ mod _overlapped { inner.data = OverlappedData::Write(buf.clone()); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut written: u32 = 0; - - let ret = unsafe { - WSASend( - handle as _, - &wsabuf, - 1, - &mut written, - flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_send( + handle as usize, + contiguous.as_ptr(), + buf_len as u32, + flags, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -943,8 +677,7 @@ mod _overlapped { accept_socket: isize, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -952,46 +685,20 @@ mod _overlapped { } // Buffer size: local address + remote address - let size = core::mem::size_of::() + 16; + let size = core::mem::size_of::() + 16; let buf = vec![0u8; size * 2]; let buf = vm.ctx.new_bytes(buf); - inner.handle = listen_socket as HANDLE; + inner.handle = listen_socket as host_overlapped::Handle; inner.data = OverlappedData::Accept(buf.clone()); - let mut bytes_received: u32 = 0; - - type AcceptExFn = unsafe extern "system" fn( - sListenSocket: usize, - sAcceptSocket: usize, - lpOutputBuffer: *mut core::ffi::c_void, - dwReceiveDataLength: u32, - dwLocalAddressLength: u32, - dwRemoteAddressLength: u32, - lpdwBytesReceived: *mut u32, - lpOverlapped: *mut OVERLAPPED, - ) -> i32; - - let accept_ex: AcceptExFn = unsafe { core::mem::transmute(*ACCEPT_EX.get().unwrap()) }; - - let ret = unsafe { - accept_ex( - listen_socket as _, - accept_socket as _, - buf.as_bytes().as_ptr() as *mut _, - 0, - size as u32, - size as u32, - &mut bytes_received, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_accept_ex( + listen_socket as usize, + accept_socket as usize, + buf.as_bytes().as_ptr() as *mut u8, + size as u32, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1011,8 +718,7 @@ mod _overlapped { address: PyTupleRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1021,46 +727,22 @@ mod _overlapped { let (addr_bytes, addr_len) = parse_address(&address, vm)?; - inner.handle = socket as HANDLE; + inner.handle = socket as host_overlapped::Handle; // Store addr_bytes in OverlappedData to keep it alive during async operation inner.data = OverlappedData::Connect(addr_bytes); - type ConnectExFn = unsafe extern "system" fn( - s: usize, - name: *const SOCKADDR, - namelen: i32, - lpSendBuffer: *const core::ffi::c_void, - dwSendDataLength: u32, - lpdwBytesSent: *mut u32, - lpOverlapped: *mut OVERLAPPED, - ) -> i32; - - let connect_ex: ConnectExFn = - unsafe { core::mem::transmute(*CONNECT_EX.get().unwrap()) }; - // Get pointer to the stored address data let addr_ptr = match &inner.data { OverlappedData::Connect(bytes) => bytes.as_ptr(), _ => unreachable!(), }; - let ret = unsafe { - connect_ex( - socket as _, - addr_ptr as *const SOCKADDR, - addr_len, - core::ptr::null(), - 0, - core::ptr::null_mut(), - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_connect_ex( + socket as usize, + addr_ptr as *const host_overlapped::SocketAddrRaw, + addr_len, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1080,34 +762,18 @@ mod _overlapped { flags: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = socket as HANDLE; + inner.handle = socket as host_overlapped::Handle; inner.data = OverlappedData::Disconnect; - type DisconnectExFn = unsafe extern "system" fn( - s: usize, - lpOverlapped: *mut OVERLAPPED, - dwFlags: u32, - dwReserved: u32, - ) -> i32; - - let disconnect_ex: DisconnectExFn = - unsafe { core::mem::transmute(*DISCONNECT_EX.get().unwrap()) }; - - let ret = unsafe { disconnect_ex(socket as _, &mut inner.overlapped, flags, 0) }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = + host_overlapped::start_disconnect_ex(socket as usize, flags, &mut inner.overlapped); inner.error = err; match err { @@ -1136,49 +802,25 @@ mod _overlapped { flags: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = socket as HANDLE; + inner.handle = socket as host_overlapped::Handle; inner.data = OverlappedData::TransmitFile; - inner.overlapped.Anonymous.Anonymous.Offset = offset; - inner.overlapped.Anonymous.Anonymous.OffsetHigh = offset_high; - - type TransmitFileFn = unsafe extern "system" fn( - hSocket: usize, - hFile: HANDLE, - nNumberOfBytesToWrite: u32, - nNumberOfBytesPerSend: u32, - lpOverlapped: *mut OVERLAPPED, - lpTransmitBuffers: *const core::ffi::c_void, - dwReserved: u32, - ) -> i32; - - let transmit_file: TransmitFileFn = - unsafe { core::mem::transmute(*TRANSMIT_FILE.get().unwrap()) }; - - let ret = unsafe { - transmit_file( - socket as _, - file as HANDLE, - count_to_write, - count_per_send, - &mut inner.overlapped, - core::ptr::null(), - flags, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_transmit_file( + socket as usize, + file as host_overlapped::Handle, + count_to_write, + count_per_send, + flags, + offset, + offset_high, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1193,31 +835,25 @@ mod _overlapped { // ConnectNamedPipe #[pymethod] fn ConnectNamedPipe(zelf: &Py, pipe: isize, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, ERROR_SUCCESS, - }; - use windows_sys::Win32::System::Pipes::ConnectNamedPipe; + use host_winapi::{ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { return Err(vm.new_value_error("operation already attempted")); } - inner.handle = pipe as HANDLE; + inner.handle = pipe as host_overlapped::Handle; inner.data = OverlappedData::ConnectNamedPipe; - let ret = unsafe { ConnectNamedPipe(pipe as HANDLE, &mut inner.overlapped) }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_connect_named_pipe( + pipe as host_overlapped::Handle, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_PIPE_CONNECTED => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Ok(true) } ERROR_SUCCESS | ERROR_IO_PENDING => Ok(false), @@ -1238,8 +874,7 @@ mod _overlapped { address: PyTupleRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASendTo}; + use host_winapi::{ERROR_IO_PENDING, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1248,7 +883,7 @@ mod _overlapped { let (addr_bytes, addr_len) = parse_address(&address, vm)?; - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; let buf_len = buf.desc.len; if buf_len > u32::MAX as usize { return Err(vm.new_value_error("buffer too large")); @@ -1264,37 +899,21 @@ mod _overlapped { address: addr_bytes, }); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut written: u32 = 0; - // Get pointer to the stored address data let addr_ptr = match &inner.data { OverlappedData::WriteTo(wt) => wt.address.as_ptr(), _ => unreachable!(), }; - let ret = unsafe { - WSASendTo( - handle as _, - &wsabuf, - 1, - &mut written, - flags, - addr_ptr as *const SOCKADDR, - addr_len, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_send_to( + handle as usize, + contiguous.as_ptr(), + buf_len as u32, + flags, + addr_ptr as *const host_overlapped::SocketAddrRaw, + addr_len, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1315,10 +934,9 @@ mod _overlapped { flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1332,10 +950,10 @@ mod _overlapped { let buf = vec![0u8; core::cmp::max(size, 1) as usize]; let buf = vm.ctx.new_bytes(buf); - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; - let address: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; - let address_length = core::mem::size_of::() as i32; + let address: host_overlapped::SocketAddrV6 = unsafe { core::mem::zeroed() }; + let address_length = core::mem::size_of::() as i32; inner.data = OverlappedData::ReadFrom(OverlappedReadFrom { result: None, @@ -1344,45 +962,29 @@ mod _overlapped { address_length, }); - let wsabuf = WSABUF { - buf: buf.as_bytes().as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - // Get mutable reference to address in inner.data let (addr_ptr, addr_len_ptr) = match &mut inner.data { OverlappedData::ReadFrom(rf) => ( - &mut rf.address as *mut SOCKADDR_IN6, + &mut rf.address as *mut host_overlapped::SocketAddrV6, &mut rf.address_length as *mut i32, ), _ => unreachable!(), }; - let ret = unsafe { - WSARecvFrom( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - addr_ptr as *mut SOCKADDR, - addr_len_ptr, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv_from( + handle as usize, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut flags, + addr_ptr as *mut host_overlapped::SocketAddrRaw, + addr_len_ptr, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -1403,10 +1005,9 @@ mod _overlapped { flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::{ + use host_winapi::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1414,9 +1015,9 @@ mod _overlapped { } let mut flags = flags.unwrap_or(0); - inner.handle = handle as HANDLE; + inner.handle = handle as host_overlapped::Handle; - let Some(contiguous) = buf.as_contiguous_mut() else { + let Some(mut contiguous) = buf.as_contiguous_mut() else { return Err(vm.new_buffer_error("buffer is not contiguous")); }; @@ -1425,8 +1026,8 @@ mod _overlapped { return Err(vm.new_value_error("buffer too large")); } - let address: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; - let address_length = core::mem::size_of::() as i32; + let address: host_overlapped::SocketAddrV6 = unsafe { core::mem::zeroed() }; + let address_length = core::mem::size_of::() as i32; inner.data = OverlappedData::ReadFromInto(OverlappedReadFromInto { result: None, @@ -1435,45 +1036,29 @@ mod _overlapped { address_length, }); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - // Get mutable reference to address in inner.data let (addr_ptr, addr_len_ptr) = match &mut inner.data { OverlappedData::ReadFromInto(rfi) => ( - &mut rfi.address as *mut SOCKADDR_IN6, + &mut rfi.address as *mut host_overlapped::SocketAddrV6, &mut rfi.address_length as *mut i32, ), _ => unreachable!(), }; - let ret = unsafe { - WSARecvFrom( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - addr_ptr as *mut SOCKADDR, - addr_len_ptr, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv_from( + handle as usize, + contiguous.as_mut_ptr(), + size, + &mut flags, + addr_ptr as *mut host_overlapped::SocketAddrRaw, + addr_len_ptr, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -1492,26 +1077,20 @@ mod _overlapped { let mut event = event.unwrap_or(INVALID_HANDLE_VALUE); if event == INVALID_HANDLE_VALUE { - event = unsafe { - windows_sys::Win32::System::Threading::CreateEventW( - core::ptr::null(), - Foundation::TRUE, - Foundation::FALSE, - core::ptr::null(), - ) as isize - }; - if event == NULL { - return Err(set_from_windows_err(0, vm)); - } + event = host_winapi::create_event_w(true, false, core::ptr::null()) + .map(|handle| handle as isize) + .map_err(|err| { + set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm) + })?; } - let mut overlapped: OVERLAPPED = unsafe { core::mem::zeroed() }; + let mut overlapped: host_overlapped::OverlappedIo = unsafe { core::mem::zeroed() }; if event != NULL { - overlapped.hEvent = event as HANDLE; + overlapped.hEvent = event as host_overlapped::Handle; } let inner = OverlappedInner { overlapped, - handle: NULL as HANDLE, + handle: NULL as host_overlapped::Handle, error: 0, data: OverlappedData::None, }; @@ -1523,35 +1102,18 @@ mod _overlapped { impl Destructor for Overlapped { fn del(zelf: &Py, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Foundation::{ - ERROR_NOT_FOUND, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, - }; - use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult}; + use host_winapi::{ERROR_NOT_FOUND, ERROR_OPERATION_ABORTED, ERROR_SUCCESS}; let mut inner = zelf.inner.lock(); - let olderr = unsafe { GetLastError() }; + let olderr = host_winapi::get_last_error(); // Cancel pending I/O and wait for completion - if !HasOverlappedIoCompleted(&inner.overlapped) + if !host_overlapped::has_overlapped_io_completed(&inner.overlapped) && !matches!(inner.data, OverlappedData::NotStarted) { - let cancelled = unsafe { CancelIoEx(inner.handle, &inner.overlapped) } != 0; - let mut transferred: u32 = 0; - let ret = unsafe { - GetOverlappedResult( - inner.handle, - &inner.overlapped, - &mut transferred, - if cancelled { 1 } else { 0 }, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; - match err { + match host_overlapped::cancel_overlapped_for_drop(inner.handle, &inner.overlapped) + .error + { ERROR_SUCCESS | ERROR_NOT_FOUND | ERROR_OPERATION_ABORTED => {} _ => { let msg = format!( @@ -1569,14 +1131,12 @@ mod _overlapped { // Close the event handle if !inner.overlapped.hEvent.is_null() { - unsafe { - Foundation::CloseHandle(inner.overlapped.hEvent); - } + let _ = host_winapi::close_handle(inner.overlapped.hEvent); inner.overlapped.hEvent = core::ptr::null_mut(); } // Restore last error - unsafe { Foundation::SetLastError(olderr) }; + host_windows::set_last_error(olderr); Ok(()) } @@ -1584,30 +1144,8 @@ mod _overlapped { #[pyfunction] fn ConnectPipe(address: String, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_OVERLAPPED, OPEN_EXISTING, - }; - - let address_wide: Vec = address.encode_utf16().chain(core::iter::once(0)).collect(); - - let handle = unsafe { - CreateFileW( - address_wide.as_ptr(), - GENERIC_READ | GENERIC_WRITE, - 0, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - core::ptr::null_mut(), - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(set_from_windows_err(0, vm)); - } - - Ok(handle as isize) + host_overlapped::connect_pipe(&address) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1618,53 +1156,26 @@ mod _overlapped { concurrency: u32, vm: &VirtualMachine, ) -> PyResult { - let r = unsafe { - windows_sys::Win32::System::IO::CreateIoCompletionPort( - handle as HANDLE, - port as HANDLE, - key, - concurrency, - ) as isize - }; - if r == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(r) + host_overlapped::create_io_completion_port(handle, port, key, concurrency) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn GetQueuedCompletionStatus(port: isize, msecs: u32, vm: &VirtualMachine) -> PyResult { - let mut bytes_transferred = 0; - let mut completion_key = 0; - let mut overlapped: *mut OVERLAPPED = core::ptr::null_mut(); - let ret = unsafe { - windows_sys::Win32::System::IO::GetQueuedCompletionStatus( - port as HANDLE, - &mut bytes_transferred, - &mut completion_key, - &mut overlapped, - msecs, - ) - }; - let err = if ret != 0 { - Foundation::ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; - if overlapped.is_null() { - if err == Foundation::WAIT_TIMEOUT { - return Ok(vm.ctx.none()); - } - return Err(set_from_windows_err(err, vm)); + match host_overlapped::get_queued_completion_status(port, msecs) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))? + { + host_overlapped::WaitResult::Timeout => Ok(vm.ctx.none()), + host_overlapped::WaitResult::Queued(status) => Ok(vm + .ctx + .new_tuple(vec![ + status.error.to_pyobject(vm), + status.bytes_transferred.to_pyobject(vm), + status.completion_key.to_pyobject(vm), + status.overlapped.to_pyobject(vm), + ]) + .into()), } - - let value = vm.ctx.new_tuple(vec![ - err.to_pyobject(vm), - bytes_transferred.to_pyobject(vm), - completion_key.to_pyobject(vm), - (overlapped as usize).to_pyobject(vm), - ]); - Ok(value.into()) } #[pyfunction] @@ -1675,64 +1186,8 @@ mod _overlapped { address: usize, vm: &VirtualMachine, ) -> PyResult<()> { - let ret = unsafe { - windows_sys::Win32::System::IO::PostQueuedCompletionStatus( - port as HANDLE, - bytes, - key, - address as *mut OVERLAPPED, - ) - }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) - } - - // Registry to track callback data for proper cleanup - // Uses Arc for reference counting to prevent use-after-free when callback - // and UnregisterWait race - the data stays alive until both are done - static WAIT_CALLBACK_REGISTRY: std::sync::OnceLock< - std::sync::Mutex>>, - > = std::sync::OnceLock::new(); - - fn wait_callback_registry() -> &'static std::sync::Mutex< - std::collections::HashMap>, - > { - WAIT_CALLBACK_REGISTRY - .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) - } - - // Callback data for RegisterWaitWithQueue - // Uses Arc to ensure the data stays alive while callback is executing - struct PostCallbackData { - completion_port: HANDLE, - overlapped: *mut OVERLAPPED, - } - - // SAFETY: The pointers are handles/addresses passed from Python and are - // only used to call Windows APIs. They are not dereferenced as Rust pointers. - unsafe impl Send for PostCallbackData {} - unsafe impl Sync for PostCallbackData {} - - unsafe extern "system" fn post_to_queue_callback( - parameter: *mut core::ffi::c_void, - timer_or_wait_fired: bool, - ) { - // Reconstruct Arc from raw pointer - this gives us ownership of one reference - // The Arc prevents use-after-free since we own a reference count - let data = unsafe { alloc::sync::Arc::from_raw(parameter as *const PostCallbackData) }; - - unsafe { - let _ = windows_sys::Win32::System::IO::PostQueuedCompletionStatus( - data.completion_port, - if timer_or_wait_fired { 1 } else { 0 }, - 0, - data.overlapped, - ); - } - // Arc is dropped here, decrementing refcount - // Memory is freed only when all references (callback + registry) are gone + host_overlapped::post_queued_completion_status(port, bytes, key, address) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1743,193 +1198,45 @@ mod _overlapped { timeout: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::{ - RegisterWaitForSingleObject, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE, - }; - - let data = alloc::sync::Arc::new(PostCallbackData { - completion_port: completion_port as HANDLE, - overlapped: overlapped as *mut OVERLAPPED, - }); - - // Create raw pointer for the callback - this increments refcount - let data_ptr = alloc::sync::Arc::into_raw(data.clone()); - - let mut new_wait_object: HANDLE = core::ptr::null_mut(); - let ret = unsafe { - RegisterWaitForSingleObject( - &mut new_wait_object, - object as HANDLE, - Some(post_to_queue_callback), - data_ptr as *mut _, - timeout, - WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, - ) - }; - - if ret == 0 { - // Registration failed - reconstruct Arc to drop the extra reference - unsafe { - let _ = alloc::sync::Arc::from_raw(data_ptr); - } - return Err(set_from_windows_err(0, vm)); - } - - // Store in registry for cleanup tracking - let wait_handle = new_wait_object as isize; - if let Ok(mut registry) = wait_callback_registry().lock() { - registry.insert(wait_handle, data); - } - - Ok(wait_handle) - } - - // Helper to cleanup callback data when unregistering - // Just removes from registry - Arc ensures memory stays alive if callback is running - fn cleanup_wait_callback_data(wait_handle: isize) { - if let Ok(mut registry) = wait_callback_registry().lock() { - // Removing from registry drops one Arc reference - // If callback already ran, this frees the memory - // If callback is still pending/running, it holds the other reference - registry.remove(&wait_handle); - } + host_overlapped::register_wait_with_queue(object, completion_port, overlapped, timeout) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn UnregisterWait(wait_handle: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::UnregisterWait; - - let ret = unsafe { UnregisterWait(wait_handle as HANDLE) }; - // Cleanup callback data regardless of UnregisterWait result - // (callback may have already fired, or may never fire) - cleanup_wait_callback_data(wait_handle); - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_overlapped::unregister_wait(wait_handle) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn UnregisterWaitEx(wait_handle: isize, event: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::UnregisterWaitEx; - - let ret = unsafe { UnregisterWaitEx(wait_handle as HANDLE, event as HANDLE) }; - // Cleanup callback data regardless of UnregisterWaitEx result - cleanup_wait_callback_data(wait_handle); - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_overlapped::unregister_wait_ex(wait_handle, event) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn BindLocal(socket: isize, family: i32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{ - INADDR_ANY, SOCKET_ERROR, WSAGetLastError, bind, - }; - - let ret = if family == AF_INET as i32 { - let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.S_un.S_addr = INADDR_ANY; - unsafe { - bind( - socket as _, - &addr as *const _ as *const SOCKADDR, - core::mem::size_of::() as i32, - ) - } - } else if family == AF_INET6 as i32 { - // in6addr_any is all zeros, which we have from zeroed() - let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; - addr.sin6_family = AF_INET6; - addr.sin6_port = 0; - unsafe { - bind( - socket as _, - &addr as *const _ as *const SOCKADDR, - core::mem::size_of::() as i32, - ) - } - } else { + if family != host_overlapped::AF_INET_FAMILY && family != host_overlapped::AF_INET6_FAMILY { return Err(vm.new_value_error("expected tuple of length 2 or 4")); - }; - - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); } - Ok(()) + host_overlapped::bind_local(socket, family) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn FormatMessage(error_code: u32, _vm: &VirtualMachine) -> String { - use windows_sys::Win32::Foundation::LocalFree; - use windows_sys::Win32::System::Diagnostics::Debug::{ - FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, - FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, - }; - - // LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1 - const LANG_NEUTRAL: u32 = 0; - const SUBLANG_DEFAULT: u32 = 1; - - let mut buffer: *mut u16 = core::ptr::null_mut(); - - let len = unsafe { - FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS, - core::ptr::null(), - error_code, - (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL, - &mut buffer as *mut _ as *mut u16, - 0, - core::ptr::null(), - ) - }; - - if len == 0 || buffer.is_null() { - if !buffer.is_null() { - unsafe { LocalFree(buffer as *mut _) }; - } - return format!("unknown error code {error_code}"); - } - - // Convert to Rust string, trimming trailing whitespace - let slice = unsafe { core::slice::from_raw_parts(buffer, len as usize) }; - let msg = String::from_utf16_lossy(slice).trim_end().to_string(); - - unsafe { LocalFree(buffer as *mut _) }; - - msg + host_overlapped::format_message(error_code) } #[pyfunction] fn WSAConnect(socket: isize, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{SOCKET_ERROR, WSAConnect, WSAGetLastError}; - let (addr_bytes, addr_len) = parse_address(&address, vm)?; - - let ret = unsafe { - WSAConnect( - socket as _, - addr_bytes.as_ptr() as *const SOCKADDR, - addr_len, - core::ptr::null(), - core::ptr::null_mut(), - core::ptr::null(), - core::ptr::null(), - ) - }; - - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - Ok(()) + host_overlapped::wsa_connect( + socket, + addr_bytes.as_ptr() as *const host_overlapped::SocketAddrRaw, + addr_len, + ) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1946,37 +1253,24 @@ mod _overlapped { let name_wide: Option> = name.map(|n| n.encode_utf16().chain(core::iter::once(0)).collect()); - let name_ptr = name_wide.as_ref().map_or(core::ptr::null(), |n| n.as_ptr()); - - let event = unsafe { - windows_sys::Win32::System::Threading::CreateEventW( - core::ptr::null(), - if manual_reset { 1 } else { 0 }, - if initial_state { 1 } else { 0 }, - name_ptr, - ) as isize - }; - if event == NULL { - return Err(set_from_windows_err(0, vm)); - } - Ok(event) + host_winapi::create_event_w( + manual_reset, + initial_state, + name_wide.as_ref().map_or(core::ptr::null(), |n| n.as_ptr()), + ) + .map(|h| h as isize) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn SetEvent(handle: isize, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { windows_sys::Win32::System::Threading::SetEvent(handle as HANDLE) }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_winapi::set_event(handle as host_winapi::Handle) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn ResetEvent(handle: isize, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { windows_sys::Win32::System::Threading::ResetEvent(handle as HANDLE) }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_winapi::reset_event(handle as host_winapi::Handle) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } } diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 059e07e2d36..0371d12e3c2 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -4,19 +4,13 @@ use crate::vm::{ builtins::PyListRef, function::ArgSequence, ospath::OsPath, - stdlib::posix, {PyObjectRef, PyResult, TryFromObject, VirtualMachine}, }; -use itertools::Itertools; -use nix::{ - errno::Errno, - unistd::{self, Pid}, -}; +use rustpython_host_env::posix as host_posix; use std::{ io::prelude::*, os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}, }; -use unistd::{Gid, Uid}; use alloc::ffi::CString; @@ -48,7 +42,8 @@ mod _posixsubprocess { let extra_groups = args .groups_list .as_ref() - .map(|l| Vec::::try_from_borrowed_object(vm, l.as_object())) + .map(|l| Vec::::try_from_borrowed_object(vm, l.as_object())) + .map(|res| res.map(|groups| groups.into_iter().map(|gid| gid.0).collect::>())) .transpose()?; let argv = args.args.iter().collect::>(); let envp = args.env_list.as_ref().map(CharPtrVec::from_iter); @@ -57,9 +52,9 @@ mod _posixsubprocess { envp: envp.as_deref(), extra_groups: extra_groups.as_deref(), }; - match unsafe { nix::unistd::fork() }.map_err(|err| err.into_pyexception(vm))? { - nix::unistd::ForkResult::Child => exec(&args, procargs, vm), - nix::unistd::ForkResult::Parent { child } => Ok(child.as_raw()), + match host_posix::fork().map_err(|err| err.into_pyexception(vm))? { + 0 => exec(&args, procargs, vm), + child => Ok(child), } } } @@ -143,7 +138,7 @@ impl TryFromObject for Fd { impl Write for Fd { fn write(&mut self, buf: &[u8]) -> std::io::Result { - Ok(unistd::write(self, buf)?) + host_posix::write_fd(self.as_fd(), buf) } fn flush(&mut self) -> std::io::Result<()> { @@ -201,6 +196,45 @@ impl AsRawFd for MaybeFd { } } +#[derive(Copy, Clone)] +struct RawUid(u32); + +#[derive(Copy, Clone)] +struct RawGid(u32); + +fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult { + use core::cmp::Ordering; + let i = obj + .try_to_ref::(vm) + .map_err(|_| { + vm.new_type_error(format!( + "an integer is required (got type {})", + obj.class().name() + )) + })? + .try_to_primitive::(vm)?; + + match i.cmp(&-1) { + Ordering::Greater => Ok(i + .try_into() + .map_err(|_| vm.new_overflow_error(format!("{typ_name} is larger than maximum")))?), + Ordering::Less => Err(vm.new_overflow_error(format!("{typ_name} is less than minimum"))), + Ordering::Equal => Ok(-1i32 as u32), + } +} + +impl TryFromObject for RawUid { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + try_from_id(vm, obj, "uid").map(Self) + } +} + +impl TryFromObject for RawGid { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + try_from_id(vm, obj, "gid").map(Self) + } +} + // impl gen_args! { @@ -221,9 +255,9 @@ gen_args! { restore_signals: bool, call_setsid: bool, pgid_to_set: libc::pid_t, - gid: Option, + gid: Option, groups_list: Option, - uid: Option, + uid: Option, child_umask: i32, preexec_fn: Option, } @@ -232,7 +266,7 @@ gen_args! { struct ProcArgs<'a> { argv: &'a CharPtrSlice<'a>, envp: Option<&'a CharPtrSlice<'a>>, - extra_groups: Option<&'a [Gid]>, + extra_groups: Option<&'a [u32]>, } fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, vm: &VirtualMachine) -> ! { @@ -246,7 +280,8 @@ fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, vm: &VirtualMachine) -> let _ = write!(pipe, "SubprocessError:0:{}", ctx.as_msg()); } else { // errno is written in hex format - let _ = write!(pipe, "OSError:{:x}:{}", e as i32, ctx.as_msg()); + let errno = e.raw_os_error().unwrap_or(0); + let _ = write!(pipe, "OSError:{errno:x}:{}", ctx.as_msg()); } rustpython_host_env::os::exit(255) } @@ -276,94 +311,34 @@ fn exec_inner( procargs: ProcArgs<'_>, ctx: &mut ExecErrorContext, vm: &VirtualMachine, -) -> nix::Result { - for &fd in args.fds_to_keep.as_slice() { - if fd.as_raw_fd() != args.errpipe_write.as_raw_fd() { - posix::set_inheritable(fd, true)? - } - } - - for &fd in &[args.p2cwrite, args.c2pread, args.errread] { - if let MaybeFd::Valid(fd) = fd { - unistd::close(fd)?; - } - } - unistd::close(args.errpipe_read)?; - - let c2pwrite = match args.c2pwrite { - MaybeFd::Valid(c2pwrite) if c2pwrite.as_raw_fd() == 0 => { - let fd = unistd::dup(c2pwrite)?; - posix::set_inheritable(fd.as_fd(), true)?; - MaybeFd::Valid(fd.into()) - } - fd => fd, - }; - - let mut errwrite = args.errwrite; - loop { - match errwrite { - MaybeFd::Valid(fd) if fd.as_raw_fd() == 0 || fd.as_raw_fd() == 1 => { - let fd = unistd::dup(fd)?; - posix::set_inheritable(fd.as_fd(), true)?; - errwrite = MaybeFd::Valid(fd.into()); - } - _ => break, - } - } - - fn dup_into_stdio(fd: MaybeFd, io_fd: i32, dup2_stdio: F) -> nix::Result<()> - where - F: Fn(Fd) -> nix::Result<()>, - { - match fd { - MaybeFd::Valid(fd) if fd.as_raw_fd() == io_fd => { - posix::set_inheritable(fd.as_fd(), true) - } - MaybeFd::Valid(fd) => dup2_stdio(fd), - MaybeFd::Invalid => Ok(()), - } - } - dup_into_stdio(args.p2cread, 0, unistd::dup2_stdin)?; - dup_into_stdio(c2pwrite, 1, unistd::dup2_stdout)?; - dup_into_stdio(errwrite, 2, unistd::dup2_stderr)?; +) -> std::io::Result { + host_posix::setup_child_fds( + args.fds_to_keep.as_slice(), + args.errpipe_write.as_fd(), + args.p2cread.as_raw_fd(), + args.p2cwrite.as_raw_fd(), + args.c2pread.as_raw_fd(), + args.c2pwrite.as_raw_fd(), + args.errread.as_raw_fd(), + args.errwrite.as_raw_fd(), + args.errpipe_read.as_raw_fd(), + )?; if let Some(ref cwd) = args.cwd { - unistd::chdir(cwd.s.as_c_str()).inspect_err(|_| *ctx = ExecErrorContext::ChDir)? + host_posix::chdir(cwd.s.as_c_str()).inspect_err(|_| *ctx = ExecErrorContext::ChDir)? } - if args.child_umask >= 0 { - unsafe { libc::umask(args.child_umask as libc::mode_t) }; - } + host_posix::set_umask(args.child_umask); if args.restore_signals { - unsafe { - libc::signal(libc::SIGPIPE, libc::SIG_DFL); - libc::signal(libc::SIGXFSZ, libc::SIG_DFL); - } + host_posix::restore_signals(); } - if args.call_setsid { - unistd::setsid()?; - } - - if args.pgid_to_set > -1 { - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(args.pgid_to_set))?; - } - - if let Some(_groups) = procargs.extra_groups { - #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] - unistd::setgroups(_groups)?; - } - - if let Some(gid) = args.gid.filter(|x| x.as_raw() != u32::MAX) { - let ret = unsafe { libc::setregid(gid.as_raw(), gid.as_raw()) }; - nix::Error::result(ret)?; - } - - if let Some(uid) = args.uid.filter(|x| x.as_raw() != u32::MAX) { - let ret = unsafe { libc::setreuid(uid.as_raw(), uid.as_raw()) }; - nix::Error::result(ret)?; - } + host_posix::setsid_if_needed(args.call_setsid)?; + host_posix::setpgid_if_needed(args.pgid_to_set)?; + host_posix::setgroups_if_needed(procargs.extra_groups)?; + host_posix::setregid_if_needed(args.gid.map(|gid| gid.0))?; + host_posix::setreuid_if_needed(args.uid.map(|uid| uid.0))?; // Call preexec_fn after all process setup but before closing FDs if let Some(ref preexec_fn) = args.preexec_fn { @@ -372,7 +347,7 @@ fn exec_inner( Err(_e) => { // Cannot safely stringify exception after fork *ctx = ExecErrorContext::PreExec; - return Err(Errno::UnknownErrno); + return Err(std::io::Error::from_raw_os_error(0)); } } } @@ -380,158 +355,13 @@ fn exec_inner( *ctx = ExecErrorContext::Exec; if args.close_fds { - close_fds(KeepFds { - above: 2, - keep: &args.fds_to_keep, - }); + host_posix::close_fds(2, args.fds_to_keep.as_slice()); } - let mut first_err = None; - for exec in args.exec_list.as_slice() { - // not using nix's versions of these functions because those allocate the char-ptr array, - // and we can't allocate - if let Some(envp) = procargs.envp { - unsafe { libc::execve(exec.s.as_ptr(), procargs.argv.as_ptr(), envp.as_ptr()) }; - } else { - unsafe { libc::execv(exec.s.as_ptr(), procargs.argv.as_ptr()) }; - } - let e = Errno::last(); - if e != Errno::ENOENT && e != Errno::ENOTDIR && first_err.is_none() { - first_err = Some(e) - } - } - Err(first_err.unwrap_or_else(Errno::last)) -} - -#[derive(Copy, Clone)] -struct KeepFds<'a> { - above: i32, - keep: &'a [BorrowedFd<'a>], -} - -impl KeepFds<'_> { - fn should_keep(self, fd: i32) -> bool { - fd > self.above - && self - .keep - .binary_search_by_key(&fd, BorrowedFd::as_raw_fd) - .is_err() - } -} - -fn close_fds(keep: KeepFds<'_>) { - #[cfg(not(target_os = "redox"))] - if close_dir_fds(keep).is_ok() { - return; - } - #[cfg(target_os = "redox")] - if close_filetable_fds(keep).is_ok() { - return; - } - close_fds_brute_force(keep) -} - -#[cfg(not(target_os = "redox"))] -fn close_dir_fds(keep: KeepFds<'_>) -> nix::Result<()> { - use nix::{dir::Dir, fcntl::OFlag}; - - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_vendor = "apple", - ))] - let fd_dir_name = c"/dev/fd"; - - #[cfg(any(target_os = "linux", target_os = "android"))] - let fd_dir_name = c"/proc/self/fd"; - - let mut dir = Dir::open( - fd_dir_name, - OFlag::O_RDONLY | OFlag::O_DIRECTORY, - nix::sys::stat::Mode::empty(), - )?; - let dirfd = dir.as_raw_fd(); - 'outer: for e in dir.iter() { - let e = e?; - let mut parser = IntParser::default(); - for &c in e.file_name().to_bytes() { - if parser.feed(c).is_err() { - continue 'outer; - } - } - let fd = parser.num; - if fd != dirfd && keep.should_keep(fd) { - let _ = unistd::close(fd); - } - } - Ok(()) -} - -#[cfg(target_os = "redox")] -fn close_filetable_fds(keep: KeepFds<'_>) -> nix::Result<()> { - use nix::fcntl; - use std::os::fd::{FromRawFd, OwnedFd}; - let filetable = fcntl::open( - c"/scheme/thisproc/current/filetable", - fcntl::OFlag::O_RDONLY, - nix::sys::stat::Mode::empty(), - )?; - let read_one = || -> nix::Result<_> { - let mut byte = 0; - let n = nix::unistd::read(&filetable, std::slice::from_mut(&mut byte))?; - Ok((n > 0).then_some(byte)) - }; - while let Some(c) = read_one()? { - let mut parser = IntParser::default(); - if parser.feed(c).is_err() { - continue; - } - let done = loop { - let Some(c) = read_one()? else { break true }; - if parser.feed(c).is_err() { - break false; - } - }; - - let fd = parser.num as i32; - if fd != filetable.as_raw_fd() && keep.should_keep(fd) { - let _ = unistd::close(fd); - } - if done { - break; - } - } - Ok(()) -} - -fn close_fds_brute_force(keep: KeepFds<'_>) { - let max_fd = nix::unistd::sysconf(nix::unistd::SysconfVar::OPEN_MAX) - .ok() - .flatten() - .unwrap_or(256) as i32; - let fds = itertools::chain![ - Some(keep.above), - keep.keep.iter().map(BorrowedFd::as_raw_fd), - Some(max_fd) - ]; - for fd in fds.tuple_windows().flat_map(|(start, end)| start + 1..end) { - unsafe { libc::close(fd) }; - } -} - -#[derive(Default)] -struct IntParser { - num: i32, -} - -struct NonDigit; -impl IntParser { - fn feed(&mut self, c: u8) -> Result<(), NonDigit> { - let digit = (c as char).to_digit(10).ok_or(NonDigit)?; - self.num *= 10; - self.num += digit as i32; - Ok(()) - } + let err = host_posix::exec_replace( + args.exec_list.as_slice(), + procargs.argv.as_ptr(), + procargs.envp.map(CharPtrSlice::as_ptr), + ); + Err(std::io::Error::from_raw_os_error(err as i32)) } diff --git a/crates/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs index c984a294775..bac708435c9 100644 --- a/crates/stdlib/src/resource.rs +++ b/crates/stdlib/src/resource.rs @@ -10,7 +10,7 @@ mod resource { convert::{ToPyException, ToPyObject}, types::PyStructSequence, }; - use core::mem; + use rustpython_host_env::resource as host_resource; use std::io; #[cfg_attr(target_os = "android", expect(deprecated))] @@ -92,8 +92,8 @@ mod resource { #[pyclass(with(PyStructSequence))] impl PyRUsage {} - impl From for RUsageData { - fn from(rusage: libc::rusage) -> Self { + impl From for RUsageData { + fn from(rusage: host_resource::RUsage) -> Self { let tv = |tv: libc::timeval| tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0); Self { ru_utime: tv(rusage.ru_utime), @@ -118,14 +118,7 @@ mod resource { #[pyfunction] fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult { - let res = unsafe { - let mut rusage = mem::MaybeUninit::::uninit(); - if libc::getrusage(who, rusage.as_mut_ptr()) == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(rusage.assume_init()) - } - }; + let res = host_resource::getrusage(who); res.map(RUsageData::from).map_err(|e| { if e.kind() == io::ErrorKind::InvalidInput { vm.new_value_error("invalid who parameter") @@ -175,13 +168,7 @@ mod resource { return Err(vm.new_value_error("invalid resource specified")); } - let rlimit = unsafe { - let mut rlimit = mem::MaybeUninit::::uninit(); - if libc::getrlimit(resource as _, rlimit.as_mut_ptr()) == -1 { - return Err(vm.new_last_errno_error()); - } - rlimit.assume_init() - }; + let rlimit = host_resource::getrlimit(resource).map_err(|_| vm.new_last_errno_error())?; Ok(Limits(rlimit)) } @@ -193,13 +180,7 @@ mod resource { return Err(vm.new_value_error("invalid resource specified")); } - let res = unsafe { - if libc::setrlimit(resource as _, &limits.0) == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - }; + let res = host_resource::setrlimit(resource, limits.0); res.map_err(|e| match e.kind() { io::ErrorKind::InvalidInput => { diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index 0110e07339f..3ec4e62259a 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -43,7 +43,7 @@ mod decl { #[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")] pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { #[cfg(windows)] - crate::vm::windows::init_winsock(); + rustpython_host_env::windows::init_winsock(); #[cfg(unix)] { @@ -166,7 +166,6 @@ mod decl { stdlib::_io::Fildes, }; use core::{convert::TryFrom, time::Duration}; - use libc::pollfd; use num_traits::{Signed, ToPrimitive}; use std::time::Instant; @@ -216,34 +215,7 @@ mod decl { #[derive(Default, Debug, PyPayload)] pub(crate) struct PyPoll { // keep sorted - fds: PyMutex>, - } - - #[inline] - fn search(fds: &[pollfd], fd: i32) -> Result { - fds.binary_search_by_key(&fd, |pfd| pfd.fd) - } - - fn insert_fd(fds: &mut Vec, fd: i32, events: i16) { - match search(fds, fd) { - Ok(i) => fds[i].events = events, - Err(i) => fds.insert( - i, - pollfd { - fd, - events, - revents: 0, - }, - ), - } - } - - fn get_fd_mut(fds: &mut [pollfd], fd: i32) -> Option<&mut pollfd> { - search(fds, fd).ok().map(move |i| &mut fds[i]) - } - - fn remove_fd(fds: &mut Vec, fd: i32) -> Option { - search(fds, fd).ok().map(|i| fds.remove(i)) + fds: PyMutex>, } // new EventMask type @@ -281,7 +253,7 @@ mod decl { OptionalArg::Present(event_mask) => event_mask.0, OptionalArg::Missing => DEFAULT_EVENTS, }; - insert_fd(&mut self.fds.lock(), fd, mask); + host_select::insert_poll_fd(&mut self.fds.lock(), fd, mask); } #[pymethod] @@ -293,7 +265,7 @@ mod decl { ) -> PyResult<()> { let mut fds = self.fds.lock(); // CPython raises KeyError if fd is not registered, match that behavior - let pfd = get_fd_mut(&mut fds, fd) + let pfd = host_select::get_poll_fd_mut(&mut fds, fd) .ok_or_else(|| vm.new_key_error(vm.ctx.new_int(fd).into()))?; pfd.events = eventmask.0; Ok(()) @@ -301,7 +273,7 @@ mod decl { #[pymethod] fn unregister(&self, Fildes(fd): Fildes, vm: &VirtualMachine) -> PyResult<()> { - let removed = remove_fd(&mut self.fds.lock(), fd); + let removed = host_select::remove_poll_fd(&mut self.fds.lock(), fd); removed .map(drop) .ok_or_else(|| vm.new_key_error(vm.ctx.new_int(fd).into())) @@ -323,13 +295,12 @@ mod decl { let deadline = timeout.map(|d| Instant::now() + d); let mut poll_timeout = timeout_ms; loop { - let res = vm.allow_threads(|| unsafe { - libc::poll(fds.as_mut_ptr(), fds.len() as _, poll_timeout) - }); - match nix::Error::result(res) { + match vm.allow_threads(|| host_select::poll_fds(&mut fds, poll_timeout)) { Ok(_) => break, - Err(nix::Error::EINTR) => vm.check_signals()?, - Err(e) => return Err(e.into_pyexception(vm)), + Err(err) if err.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()? + } + Err(err) => return Err(err.into_pyexception(vm)), } if let Some(d) = deadline { if let Some(remaining) = d.checked_duration_since(Instant::now()) { @@ -379,8 +350,7 @@ mod decl { types::Constructor, }; use core::ops::Deref; - use rustix::event::epoll::{self, EventData, EventFlags}; - use std::os::fd::{AsRawFd, IntoRawFd, OwnedFd}; + use std::os::fd::{AsRawFd, OwnedFd}; use std::time::Instant; #[pyclass(module = "select", name = "epoll")] @@ -422,7 +392,7 @@ mod decl { #[pyclass(with(Constructor))] impl PyEpoll { fn new() -> std::io::Result { - let epoll_fd = epoll::create(epoll::CreateFlags::CLOEXEC)?; + let epoll_fd = host_select::epoll::create()?; let epoll_fd = Some(epoll_fd).into(); Ok(Self { epoll_fd }) } @@ -431,7 +401,7 @@ mod decl { fn close(&self) -> std::io::Result<()> { let fd = self.epoll_fd.write().take(); if let Some(fd) = fd { - nix::unistd::close(fd.into_raw_fd())?; + host_select::epoll::close(fd)?; } Ok(()) } @@ -468,26 +438,28 @@ mod decl { vm: &VirtualMachine, ) -> PyResult<()> { let events = match eventmask { - OptionalArg::Present(mask) => EventFlags::from_bits_retain(mask), - OptionalArg::Missing => EventFlags::IN | EventFlags::PRI | EventFlags::OUT, + OptionalArg::Present(mask) => mask, + OptionalArg::Missing => (host_select::epoll::EventFlags::IN + | host_select::epoll::EventFlags::PRI + | host_select::epoll::EventFlags::OUT) + .bits(), }; let epoll_fd = &*self.get_epoll(vm)?; - let data = EventData::new_u64(fd.as_raw_fd() as u64); - epoll::add(epoll_fd, fd, data, events).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::add(epoll_fd, fd, fd.as_raw_fd() as u64, events) + .map_err(|e| e.into_pyexception(vm)) } #[pymethod] fn modify(&self, fd: Fildes, eventmask: u32, vm: &VirtualMachine) -> PyResult<()> { - let events = EventFlags::from_bits_retain(eventmask); let epoll_fd = &*self.get_epoll(vm)?; - let data = EventData::new_u64(fd.as_raw_fd() as u64); - epoll::modify(epoll_fd, fd, data, events).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::modify(epoll_fd, fd, fd.as_raw_fd() as u64, eventmask) + .map_err(|e| e.into_pyexception(vm)) } #[pymethod] fn unregister(&self, fd: Fildes, vm: &VirtualMachine) -> PyResult<()> { let epoll_fd = &*self.get_epoll(vm)?; - epoll::delete(epoll_fd, fd).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::delete(epoll_fd, fd).map_err(|e| e.into_pyexception(vm)) } #[pymethod] @@ -495,11 +467,10 @@ mod decl { let poll::TimeoutArg(timeout) = args.timeout; let maxevents = args.maxevents; - let mut poll_timeout = - timeout - .map(rustix::event::Timespec::try_from) - .transpose() - .map_err(|_| vm.new_overflow_error("timeout is too large"))?; + let mut poll_timeout = timeout + .map(host_select::epoll::Timespec::try_from) + .transpose() + .map_err(|_| vm.new_overflow_error("timeout is too large"))?; let deadline = timeout.map(|d| Instant::now() + d); let maxevents = match maxevents { @@ -512,22 +483,19 @@ mod decl { _ => maxevents as usize, }; - let mut events = Vec::::with_capacity(maxevents); + let mut events = Vec::::with_capacity(maxevents); let epoll = &*self.get_epoll(vm)?; loop { - events.clear(); match vm.allow_threads(|| { - epoll::wait( - epoll, - rustix::buffer::spare_capacity(&mut events), - poll_timeout.as_ref(), - ) + host_select::epoll::wait(epoll, &mut events, poll_timeout.as_ref()) }) { Ok(_) => break, - Err(rustix::io::Errno::INTR) => vm.check_signals()?, - Err(e) => return Err(e.into_pyexception(vm)), + Err(host_select::epoll::WaitError::Interrupted) => vm.check_signals()?, + Err(host_select::epoll::WaitError::Io(e)) => { + return Err(e.into_pyexception(vm)); + } } if let Some(deadline) = deadline { if let Some(new_timeout) = deadline.checked_duration_since(Instant::now()) { diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index ecedb136f53..f11fa4c8402 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -23,11 +23,15 @@ mod _socket { utils::ToCString, }; use rustpython_host_env::os::ErrorExt; + #[cfg(any(unix, windows))] + use rustpython_host_env::socket as host_socket; + #[cfg(windows)] + use rustpython_host_env::windows as host_windows; #[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")] pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { #[cfg(windows)] - crate::vm::windows::init_winsock(); + host_windows::init_winsock(); __module_exec(vm, module); Ok(()) @@ -50,72 +54,36 @@ mod _socket { #[cfg(unix)] use libc as c; - #[cfg(windows)] mod c { - pub(super) use windows_sys::Win32::NetworkManagement::IpHelper::{ - if_indextoname, if_nametoindex, - }; - - pub(super) use windows_sys::Win32::Networking::WinSock::{ - INADDR_ANY, INADDR_BROADCAST, INADDR_LOOPBACK, INADDR_NONE, - }; - - pub(super) use windows_sys::Win32::Networking::WinSock::{ - AF_APPLETALK, AF_DECnet, AF_IPX, AF_LINK, AI_ADDRCONFIG, AI_ALL, AI_CANONNAME, - AI_NUMERICSERV, AI_V4MAPPED, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_HDRINCL, - IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_OPTIONS, IP_RECVDSTADDR, - IP_TOS, IP_TTL, IPPORT_RESERVED, IPPROTO_AH, IPPROTO_DSTOPTS, IPPROTO_EGP, IPPROTO_ESP, - IPPROTO_FRAGMENT, IPPROTO_GGP, IPPROTO_HOPOPTS, IPPROTO_ICMP, IPPROTO_ICMPV6, - IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IP, IPPROTO_IP as IPPROTO_IPIP, IPPROTO_IPV4, - IPPROTO_IPV6, IPPROTO_ND, IPPROTO_NONE, IPPROTO_PIM, IPPROTO_PUP, IPPROTO_RAW, - IPPROTO_ROUTING, IPPROTO_TCP, IPPROTO_UDP, IPV6_CHECKSUM, IPV6_DONTFRAG, IPV6_HOPLIMIT, - IPV6_HOPOPTS, IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS, - IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_PKTINFO, IPV6_RECVRTHDR, IPV6_RECVTCLASS, - IPV6_RTHDR, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, MSG_BCAST, MSG_CTRUNC, - MSG_DONTROUTE, MSG_MCAST, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, NI_DGRAM, - NI_MAXHOST, NI_MAXSERV, NI_NAMEREQD, NI_NOFQDN, NI_NUMERICHOST, NI_NUMERICSERV, - RCVALL_IPLEVEL, RCVALL_OFF, RCVALL_ON, RCVALL_SOCKETLEVELONLY, SD_BOTH as SHUT_RDWR, - SD_RECEIVE as SHUT_RD, SD_SEND as SHUT_WR, SIO_KEEPALIVE_VALS, SIO_LOOPBACK_FAST_PATH, - SIO_RCVALL, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF, - SO_REUSEADDR, SO_SNDBUF, SO_TYPE, SO_USELOOPBACK, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, - SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SOMAXCONN, TCP_NODELAY, WSAEBADF, - WSAECONNRESET, WSAENOTSOCK, WSAEWOULDBLOCK, - }; - - pub(super) use windows_sys::Win32::Networking::WinSock::{ - INVALID_SOCKET, SOCKET_ERROR, WSA_FLAG_OVERLAPPED, WSADuplicateSocketW, - WSAGetLastError, WSAIoctl, WSAPROTOCOL_INFOW, WSASocketW, - }; - - pub(super) use windows_sys::Win32::Networking::WinSock::{ - SO_REUSEADDR as SO_EXCLUSIVEADDRUSE, getprotobyname, getservbyname, getservbyport, - getsockopt, setsockopt, - }; - - pub(super) use windows_sys::Win32::Networking::WinSock::{ - WSA_NOT_ENOUGH_MEMORY as EAI_MEMORY, WSAEAFNOSUPPORT as EAI_FAMILY, - WSAEINVAL as EAI_BADFLAGS, WSAESOCKTNOSUPPORT as EAI_SOCKTYPE, - WSAHOST_NOT_FOUND as EAI_NODATA, WSAHOST_NOT_FOUND as EAI_NONAME, - WSANO_RECOVERY as EAI_FAIL, WSATRY_AGAIN as EAI_AGAIN, - WSATYPE_NOT_FOUND as EAI_SERVICE, + pub(super) use rustpython_host_env::socket::{ + AF_APPLETALK, AF_DECnet, AF_INET, AF_INET6, AF_IPX, AF_LINK, AF_UNSPEC, AI_ADDRCONFIG, + AI_ALL, AI_CANONNAME, AI_NUMERICHOST, AI_NUMERICSERV, AI_PASSIVE, AI_V4MAPPED, + EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NODATA, EAI_NONAME, + EAI_SERVICE, EAI_SOCKTYPE, INADDR_ANY, INADDR_BROADCAST, INADDR_LOOPBACK, INADDR_NONE, + IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_HDRINCL, IP_MULTICAST_IF, IP_MULTICAST_LOOP, + IP_MULTICAST_TTL, IP_OPTIONS, IP_RECVDSTADDR, IP_TOS, IP_TTL, IPPORT_RESERVED, + IPPROTO_AH, IPPROTO_DSTOPTS, IPPROTO_EGP, IPPROTO_ESP, IPPROTO_FRAGMENT, IPPROTO_GGP, + IPPROTO_HOPOPTS, IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IP, + IPPROTO_IP as IPPROTO_IPIP, IPPROTO_IPV4, IPPROTO_IPV6, IPPROTO_ND, IPPROTO_NONE, + IPPROTO_PIM, IPPROTO_PUP, IPPROTO_RAW, IPPROTO_ROUTING, IPPROTO_TCP, IPPROTO_UDP, + IPV6_CHECKSUM, IPV6_DONTFRAG, IPV6_HOPLIMIT, IPV6_HOPOPTS, IPV6_JOIN_GROUP, + IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, + IPV6_PKTINFO, IPV6_RECVRTHDR, IPV6_RECVTCLASS, IPV6_RTHDR, IPV6_TCLASS, + IPV6_UNICAST_HOPS, IPV6_V6ONLY, MSG_BCAST, MSG_CTRUNC, MSG_DONTROUTE, MSG_MCAST, + MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, NI_DGRAM, NI_MAXHOST, NI_MAXSERV, + NI_NAMEREQD, NI_NOFQDN, NI_NUMERICHOST, NI_NUMERICSERV, RCVALL_IPLEVEL, RCVALL_OFF, + RCVALL_ON, RCVALL_SOCKETLEVELONLY, SD_BOTH as SHUT_RDWR, SD_RECEIVE as SHUT_RD, + SD_SEND as SHUT_WR, SIO_KEEPALIVE_VALS, SIO_LOOPBACK_FAST_PATH, SIO_RCVALL, + SO_BROADCAST, SO_ERROR, SO_EXCLUSIVEADDRUSE, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, + SO_RCVBUF, SO_REUSEADDR, SO_SNDBUF, SO_TYPE, SO_USELOOPBACK, SOCK_DGRAM, SOCK_RAW, + SOCK_RDM, SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SOMAXCONN, TCP_NODELAY, WSAEBADF, + WSAENOTSOCK, WSAEWOULDBLOCK, getprotobyname, getservbyname, getservbyport, }; - - pub(super) const IF_NAMESIZE: usize = - windows_sys::Win32::NetworkManagement::Ndis::IF_MAX_STRING_SIZE as _; - pub(super) const AF_UNSPEC: i32 = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as _; - pub(super) const AF_INET: i32 = windows_sys::Win32::Networking::WinSock::AF_INET as _; - pub(super) const AF_INET6: i32 = windows_sys::Win32::Networking::WinSock::AF_INET6 as _; - pub(super) const AI_PASSIVE: i32 = windows_sys::Win32::Networking::WinSock::AI_PASSIVE as _; - pub(super) const AI_NUMERICHOST: i32 = - windows_sys::Win32::Networking::WinSock::AI_NUMERICHOST as _; - pub(super) const FROM_PROTOCOL_INFO: i32 = -1; } - // constants #[pyattr(name = "has_ipv6")] const HAS_IPV6: bool = true; - #[pyattr] // put IPPROTO_MAX later use c::{ @@ -842,7 +810,7 @@ mod _socket { #[cfg(windows)] #[pyattr] - use windows_sys::Win32::Networking::WinSock::{ + use host_socket::{ IPPROTO_CBT, IPPROTO_ICLFXBM, IPPROTO_IGP, IPPROTO_L2TP, IPPROTO_PGM, IPPROTO_RDP, IPPROTO_SCTP, IPPROTO_ST, }; @@ -913,9 +881,6 @@ mod _socket { }; } - #[cfg(windows)] - use windows_sys::Win32::NetworkManagement::IpHelper; - fn get_raw_sock(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { #[cfg(unix)] type CastFrom = libc::c_long; @@ -1255,11 +1220,7 @@ mod _socket { } let cstr = alloc::ffi::CString::new(ifname) .map_err(|_| vm.new_os_error("invalid interface name".to_owned()))?; - let idx = unsafe { libc::if_nametoindex(cstr.as_ptr()) }; - if idx == 0 { - return Err(io::Error::last_os_error().into()); - } - idx as i32 + host_socket::if_nametoindex_checked(cstr.as_c_str())? as i32 }; // Create sockaddr_can @@ -1487,7 +1448,7 @@ mod _socket { use crate::vm::builtins::PyBytes; if let Ok(bytes) = fileno_obj.clone().downcast::() { let bytes_data = bytes.as_bytes(); - let expected_size = core::mem::size_of::(); + let expected_size = host_socket::protocol_info_size(); if bytes_data.len() != expected_size { return Err(vm @@ -1497,37 +1458,11 @@ mod _socket { .into()); } - let mut info: c::WSAPROTOCOL_INFOW = unsafe { core::mem::zeroed() }; - unsafe { - core::ptr::copy_nonoverlapping( - bytes_data.as_ptr(), - &mut info as *mut c::WSAPROTOCOL_INFOW as *mut u8, - expected_size, - ); - } - - let fd = unsafe { - c::WSASocketW( - c::FROM_PROTOCOL_INFO, - c::FROM_PROTOCOL_INFO, - c::FROM_PROTOCOL_INFO, - &info, - 0, - c::WSA_FLAG_OVERLAPPED, - ) - }; - - if fd == c::INVALID_SOCKET { - return Err(Self::wsa_error().into()); - } - - crate::vm::stdlib::nt::raw_set_handle_inheritable(fd as _, false)?; - - family = info.iAddressFamily; - socket_kind = info.iSocketType; - proto = info.iProtocol; - - sock = unsafe { sock_from_raw_unchecked(fd as RawSocket) }; + let shared = host_socket::socket_from_share_data(bytes_data)?; + family = shared.family; + socket_kind = shared.socket_type; + proto = shared.protocol; + sock = unsafe { sock_from_raw_unchecked(shared.raw as RawSocket) }; return Ok(zelf.init_inner(family, socket_kind, proto, sock)?); } @@ -1888,6 +1823,8 @@ mod _socket { #[cfg(target_os = "linux")] #[pymethod] fn sendmsg_afalg(&self, args: SendmsgAfalgArgs, vm: &VirtualMachine) -> PyResult { + use std::os::fd::BorrowedFd; + let msg = args.msg; let op = args.op; let iv = args.iv; @@ -1902,100 +1839,17 @@ mod _socket { OptionalArg::Missing => None, }; - // Build control messages for AF_ALG - let mut control_buf = Vec::new(); - - // Add ALG_SET_OP control message - { - let op_bytes = op.to_ne_bytes(); - let space = - unsafe { libc::CMSG_SPACE(core::mem::size_of::() as u32) } as usize; - let old_len = control_buf.len(); - control_buf.resize(old_len + space, 0u8); - - let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; - unsafe { - (*cmsg).cmsg_len = libc::CMSG_LEN(core::mem::size_of::() as u32) as _; - (*cmsg).cmsg_level = libc::SOL_ALG; - (*cmsg).cmsg_type = libc::ALG_SET_OP; - let data = libc::CMSG_DATA(cmsg); - core::ptr::copy_nonoverlapping(op_bytes.as_ptr(), data, op_bytes.len()); - } - } - - // Add ALG_SET_IV control message if iv is provided - if let Some(iv_data) = iv { - let iv_bytes = iv_data.borrow_buf(); - // struct af_alg_iv { __u32 ivlen; __u8 iv[]; } - let iv_struct_size = 4 + iv_bytes.len(); - let space = unsafe { libc::CMSG_SPACE(iv_struct_size as u32) } as usize; - let old_len = control_buf.len(); - control_buf.resize(old_len + space, 0u8); - - let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; - unsafe { - (*cmsg).cmsg_len = libc::CMSG_LEN(iv_struct_size as u32) as _; - (*cmsg).cmsg_level = libc::SOL_ALG; - (*cmsg).cmsg_type = libc::ALG_SET_IV; - let data = libc::CMSG_DATA(cmsg); - // Write ivlen - let ivlen = (iv_bytes.len() as u32).to_ne_bytes(); - core::ptr::copy_nonoverlapping(ivlen.as_ptr(), data, 4); - // Write iv - core::ptr::copy_nonoverlapping(iv_bytes.as_ptr(), data.add(4), iv_bytes.len()); - } - } - - // Add ALG_SET_AEAD_ASSOCLEN control message if assoclen is provided - if let Some(assoclen_val) = assoclen { - let assoclen_bytes = assoclen_val.to_ne_bytes(); - let space = - unsafe { libc::CMSG_SPACE(core::mem::size_of::() as u32) } as usize; - let old_len = control_buf.len(); - control_buf.resize(old_len + space, 0u8); - - let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; - unsafe { - (*cmsg).cmsg_len = libc::CMSG_LEN(core::mem::size_of::() as u32) as _; - (*cmsg).cmsg_level = libc::SOL_ALG; - (*cmsg).cmsg_type = libc::ALG_SET_AEAD_ASSOCLEN; - let data = libc::CMSG_DATA(cmsg); - core::ptr::copy_nonoverlapping( - assoclen_bytes.as_ptr(), - data, - assoclen_bytes.len(), - ); - } - } - - // Build buffers let buffers = msg.iter().map(|buf| buf.borrow_buf()).collect::>(); - let iovecs: Vec = buffers + let buffers = buffers .iter() - .map(|buf| libc::iovec { - iov_base: buf.as_ptr() as *mut _, - iov_len: buf.len(), - }) - .collect(); - - // Set up msghdr - let mut msghdr: libc::msghdr = unsafe { core::mem::zeroed() }; - msghdr.msg_iov = iovecs.as_ptr() as *mut _; - msghdr.msg_iovlen = iovecs.len() as _; - if !control_buf.is_empty() { - msghdr.msg_control = control_buf.as_mut_ptr() as *mut _; - msghdr.msg_controllen = control_buf.len() as _; - } + .map(|buf| io::IoSlice::new(buf)) + .collect::>(); + let iv = iv.map(|iv| iv.borrow_buf().to_vec()); self.sock_op(vm, SelectKind::Write, || { let sock = self.sock()?; - let fd = sock_fileno(&sock); - let ret = unsafe { libc::sendmsg(fd as libc::c_int, &msghdr, flags) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } + let fd = unsafe { BorrowedFd::borrow_raw(sock_fileno(&sock)) }; + host_socket::sendmsg_afalg(fd, &buffers, op, iv.as_deref(), assoclen, flags) }) .map_err(|e| e.into_pyexception(vm)) } @@ -2012,8 +1866,6 @@ mod _socket { flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use core::mem::MaybeUninit; - if bufsize < 0 { return Err(vm.new_value_error("negative buffer size in recvmsg")); } @@ -2026,62 +1878,29 @@ mod _socket { let ancbufsize = ancbufsize as usize; let flags = flags.unwrap_or(0); - // Allocate buffers - let mut data_buf: Vec> = vec![MaybeUninit::uninit(); bufsize]; - let mut anc_buf: Vec> = vec![MaybeUninit::uninit(); ancbufsize]; - let mut addr_storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; - - // Set up iovec - let mut iov = [libc::iovec { - iov_base: data_buf.as_mut_ptr().cast(), - iov_len: bufsize, - }]; - - // Set up msghdr - let mut msg: libc::msghdr = unsafe { core::mem::zeroed() }; - msg.msg_name = (&mut addr_storage as *mut libc::sockaddr_storage).cast(); - msg.msg_namelen = core::mem::size_of::() as libc::socklen_t; - msg.msg_iov = iov.as_mut_ptr(); - msg.msg_iovlen = 1; - if ancbufsize > 0 { - msg.msg_control = anc_buf.as_mut_ptr().cast(); - msg.msg_controllen = ancbufsize as _; - } - - let n = self + let msg = self .sock_op(vm, SelectKind::Read, || { let sock = self.sock()?; - let fd = sock_fileno(&sock); - let ret = unsafe { libc::recvmsg(fd as libc::c_int, &mut msg, flags) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } + let fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(sock_fileno(&sock)) }; + host_socket::recvmsg(fd, bufsize, ancbufsize, flags) }) .map_err(|e| e.into_pyexception(vm))?; - // Build data bytes - let data = unsafe { - data_buf.set_len(n); - core::mem::transmute::>, Vec>(data_buf) - }; - // Build ancdata list - let ancdata = Self::parse_ancillary_data(&msg, vm); + let ancdata = Self::parse_ancillary_data(&msg.ancdata, vm); // Build address tuple - let address = if msg.msg_namelen > 0 { + let address = if let Some(address) = msg.address { let storage: socket2::SockAddrStorage = - unsafe { core::mem::transmute(addr_storage) }; - let addr = unsafe { socket2::SockAddr::new(storage, msg.msg_namelen) }; + unsafe { core::mem::transmute(address.storage) }; + let addr = unsafe { socket2::SockAddr::new(storage, address.len as _) }; get_addr_tuple(&addr, vm) } else { vm.ctx.none() }; Ok(vm.ctx.new_tuple(vec![ - vm.ctx.new_bytes(data).into(), + vm.ctx.new_bytes(msg.data).into(), ancdata, vm.ctx.new_int(msg.msg_flags).into(), address, @@ -2090,35 +1909,18 @@ mod _socket { /// Parse ancillary data from a received message header #[cfg(all(unix, not(target_os = "redox")))] - fn parse_ancillary_data(msg: &libc::msghdr, vm: &VirtualMachine) -> PyObjectRef { + fn parse_ancillary_data( + control: &[host_socket::AncillaryMessage], + vm: &VirtualMachine, + ) -> PyObjectRef { let mut result = Vec::new(); - - // Calculate buffer end for truncation handling - let ctrl_buf = msg.msg_control as *const u8; - let ctrl_end = unsafe { ctrl_buf.add(msg.msg_controllen as _) }; - - let mut cmsg: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(msg) }; - while !cmsg.is_null() { - let cmsg_ref = unsafe { &*cmsg }; - let data_ptr = unsafe { libc::CMSG_DATA(cmsg) }; - - // Calculate data length, respecting buffer truncation - let data_len_from_cmsg = - cmsg_ref.cmsg_len as usize - (data_ptr as usize - cmsg as usize); - let available = ctrl_end as usize - data_ptr as usize; - let data_len = data_len_from_cmsg.min(available); - - let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) }; - + for cmsg in control { let tuple = vm.ctx.new_tuple(vec![ - vm.ctx.new_int(cmsg_ref.cmsg_level).into(), - vm.ctx.new_int(cmsg_ref.cmsg_type).into(), - vm.ctx.new_bytes(data.to_vec()).into(), + vm.ctx.new_int(cmsg.level).into(), + vm.ctx.new_int(cmsg.kind).into(), + vm.ctx.new_bytes(cmsg.data.clone()).into(), ]); - result.push(tuple.into()); - - cmsg = unsafe { libc::CMSG_NXTHDR(msg, cmsg) }; } vm.ctx.new_list(result).into() @@ -2130,53 +1932,32 @@ mod _socket { cmsgs: &[(i32, i32, ArgBytesLike)], vm: &VirtualMachine, ) -> PyResult> { - use core::{mem, ptr}; - if cmsgs.is_empty() { return Ok(vec![]); } - - let capacity = cmsgs + let data = cmsgs .iter() - .map(|(_, _, buf)| buf.len()) - .try_fold(0, |sum, len| { - let space = checked_cmsg_space(len).ok_or_else(|| { - vm.new_os_error("ancillary data item too large".to_owned()) - })?; - usize::checked_add(sum, space) - .ok_or_else(|| vm.new_os_error("too much ancillary data".to_owned())) - })?; - - let mut cmsg_buffer = vec![0u8; capacity]; - - // make a dummy msghdr so we can use the CMSG_* apis - let mut mhdr = unsafe { mem::zeroed::() }; - mhdr.msg_control = cmsg_buffer.as_mut_ptr().cast(); - mhdr.msg_controllen = capacity as _; + .map(|(lvl, typ, buf)| { + let data = buf.borrow_buf(); + (*lvl, *typ, data.to_vec()) + }) + .collect::>(); + let data_refs = data + .iter() + .map(|(lvl, typ, data)| (*lvl, *typ, data.as_slice())) + .collect::>(); - let mut pmhdr: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(&mhdr) }; - for (lvl, typ, buf) in cmsgs { - if pmhdr.is_null() { - return Err(vm.new_runtime_error( - "unexpected NULL result from CMSG_FIRSTHDR/CMSG_NXTHDR", - )); + host_socket::pack_ancillary_messages(&data_refs).map_err(|err| match err { + host_socket::AncillaryPackError::ItemTooLarge => { + vm.new_os_error("ancillary data item too large".to_owned()) } - let data = &*buf.borrow_buf(); - assert_eq!(data.len(), buf.len()); - // Safe because we know that pmhdr is valid, and we initialized it with - // sufficient space - unsafe { - (*pmhdr).cmsg_level = *lvl; - (*pmhdr).cmsg_type = *typ; - (*pmhdr).cmsg_len = libc::CMSG_LEN(data.len() as _) as _; - ptr::copy_nonoverlapping(data.as_ptr(), libc::CMSG_DATA(pmhdr), data.len()); + host_socket::AncillaryPackError::TooMuchData => { + vm.new_os_error("too much ancillary data".to_owned()) } - - // Safe because mhdr is valid - pmhdr = unsafe { libc::CMSG_NXTHDR(&mhdr, pmhdr) }; - } - - Ok(cmsg_buffer) + host_socket::AncillaryPackError::UnexpectedNullHeader => { + vm.new_runtime_error("unexpected NULL result from CMSG_FIRSTHDR/CMSG_NXTHDR") + } + }) } #[pymethod] @@ -2270,20 +2051,7 @@ mod _socket { let fd = sock_fileno(&sock); let buflen = buflen.unwrap_or(0); if buflen == 0 { - let mut flag: libc::c_int = 0; - let mut flagsize = core::mem::size_of::() as _; - let ret = unsafe { - c::getsockopt( - fd as _, - level, - name, - &mut flag as *mut libc::c_int as *mut _, - &mut flagsize, - ) - }; - if ret < 0 { - return Err(rustpython_host_env::os::errno_io_error().into()); - } + let flag = host_socket::getsockopt_int(fd as _, level, name)?; Ok(vm.ctx.new_int(flag).into()) } else { if buflen <= 0 || buflen > 1024 { @@ -2291,21 +2059,7 @@ mod _socket { .new_os_error("getsockopt buflen out of range".to_owned()) .into()); } - let mut buf = vec![0u8; buflen as usize]; - let mut buflen = buflen as _; - let ret = unsafe { - c::getsockopt( - fd as _, - level, - name, - buf.as_mut_ptr() as *mut _, - &mut buflen, - ) - }; - if ret < 0 { - return Err(rustpython_host_env::os::errno_io_error().into()); - } - buf.truncate(buflen as usize); + let buf = host_socket::getsockopt_bytes(fd as _, level, name, buflen as usize)?; Ok(vm.ctx.new_bytes(buf).into()) } } @@ -2321,33 +2075,23 @@ mod _socket { ) -> Result<(), IoOrPyException> { let sock = self.sock()?; let fd = sock_fileno(&sock); - let ret = match (value, optlen) { - (Some(Either::A(b)), OptionalArg::Missing) => b.with_ref(|b| unsafe { - c::setsockopt(fd as _, level, name, b.as_ptr() as *const _, b.len() as _) - }), - (Some(Either::B(ref val)), OptionalArg::Missing) => unsafe { - c::setsockopt( - fd as _, - level, - name, - val as *const i32 as *const _, - core::mem::size_of::() as _, - ) - }, - (None, OptionalArg::Present(optlen)) => unsafe { - c::setsockopt(fd as _, level, name, core::ptr::null(), optlen as _) - }, + match (value, optlen) { + (Some(Either::A(b)), OptionalArg::Missing) => { + b.with_ref(|b| host_socket::setsockopt_bytes(fd as _, level, name, b))? + } + (Some(Either::B(val)), OptionalArg::Missing) => { + host_socket::setsockopt_int(fd as _, level, name, val)? + } + (None, OptionalArg::Present(optlen)) => { + host_socket::setsockopt_none(fd as _, level, name, optlen)? + } _ => { return Err(vm .new_type_error("expected the value arg xor the optlen arg") .into()); } - }; - if ret < 0 { - Err(rustpython_host_env::os::errno_io_error().into()) - } else { - Ok(()) } + Ok(()) } #[pymethod] @@ -2365,11 +2109,6 @@ mod _socket { Ok(self.sock()?.shutdown(how)?) } - #[cfg(windows)] - fn wsa_error() -> io::Error { - io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() }) - } - #[cfg(windows)] #[pymethod] fn ioctl( @@ -2383,7 +2122,6 @@ mod _socket { let sock = self.sock()?; let fd = sock_fileno(&sock); - let mut recv: u32 = 0; // Convert cmd to u32, returning ValueError for invalid/negative values let cmd_int = cmd @@ -2403,23 +2141,7 @@ mod _socket { .into()); } let option_val: u32 = TryFromObject::try_from_object(vm, option)?; - let ret = unsafe { - c::WSAIoctl( - fd as _, - cmd, - &option_val as *const u32 as *const _, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - 0, - &mut recv, - core::ptr::null_mut(), - None, - ) - }; - if ret == c::SOCKET_ERROR { - return Err(Self::wsa_error().into()); - } - Ok(recv) + host_socket::ioctl_u32(fd as _, cmd, option_val).map_err(Into::into) } c::SIO_KEEPALIVE_VALS => { let tuple: PyTupleRef = option @@ -2433,36 +2155,18 @@ mod _socket { .into()); } - #[repr(C)] - struct TcpKeepalive { - onoff: u32, - keepalivetime: u32, - keepaliveinterval: u32, - } - - let ka = TcpKeepalive { + let ka = host_socket::TcpKeepalive { onoff: TryFromObject::try_from_object(vm, tuple[0].clone())?, keepalivetime: TryFromObject::try_from_object(vm, tuple[1].clone())?, keepaliveinterval: TryFromObject::try_from_object(vm, tuple[2].clone())?, }; - let ret = unsafe { - c::WSAIoctl( - fd as _, - cmd, - &ka as *const TcpKeepalive as *const _, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - 0, - &mut recv, - core::ptr::null_mut(), - None, - ) - }; - if ret == c::SOCKET_ERROR { - return Err(Self::wsa_error().into()); + if cmd != c::SIO_KEEPALIVE_VALS { + return Err(vm + .new_value_error(format!("invalid ioctl command {}", cmd)) + .into()); } - Ok(recv) + host_socket::ioctl_keepalive(fd as _, ka).map_err(Into::into) } _ => Err(vm .new_value_error(format!("invalid ioctl command {cmd}")) @@ -2475,24 +2179,7 @@ mod _socket { fn share(&self, process_id: u32, _vm: &VirtualMachine) -> Result, IoOrPyException> { let sock = self.sock()?; let fd = sock_fileno(&sock); - - let mut info: MaybeUninit = MaybeUninit::uninit(); - - let ret = unsafe { c::WSADuplicateSocketW(fd as _, process_id, info.as_mut_ptr()) }; - - if ret == c::SOCKET_ERROR { - return Err(Self::wsa_error().into()); - } - - let info = unsafe { info.assume_init() }; - let bytes = unsafe { - core::slice::from_raw_parts( - &info as *const c::WSAPROTOCOL_INFOW as *const u8, - core::mem::size_of::(), - ) - }; - - Ok(bytes.to_vec()) + host_socket::share_socket(fd as _, process_id).map_err(Into::into) } #[pygetset(name = "type")] @@ -2607,19 +2294,7 @@ mod _socket { let ifname = if ifindex == 0 { String::new() } else { - let mut buf = [0u8; libc::IF_NAMESIZE]; - let ret = unsafe { - libc::if_indextoname( - ifindex as libc::c_uint, - buf.as_mut_ptr() as *mut libc::c_char, - ) - }; - if ret.is_null() { - String::new() - } else { - let nul_pos = memchr::memchr(b'\0', &buf).unwrap_or(buf.len()); - String::from_utf8_lossy(&buf[..nul_pos]).into_owned() - } + host_socket::if_indextoname_checked(ifindex as u32).unwrap_or_default() }; return vm.ctx.new_tuple(vec![vm.ctx.new_str(ifname).into()]).into(); } @@ -2655,8 +2330,8 @@ mod _socket { #[cfg(all(unix, not(any(target_os = "redox", target_os = "android"))))] #[pyfunction] - fn sethostname(hostname: PyUtf8StrRef) -> nix::Result<()> { - nix::unistd::sethostname(hostname.as_str()) + fn sethostname(hostname: PyUtf8StrRef) -> std::io::Result<()> { + host_socket::sethostname(hostname.as_str()) } #[pyfunction] @@ -2783,20 +2458,13 @@ mod _socket { ) -> io::Result { #[cfg(unix)] { - use nix::poll::*; use std::os::fd::AsFd; - let events = match kind { - SelectKind::Read => PollFlags::POLLIN, - SelectKind::Write => PollFlags::POLLOUT, - SelectKind::Connect => PollFlags::POLLOUT | PollFlags::POLLERR, + let kind = match kind { + SelectKind::Read => host_socket::PollKind::Read, + SelectKind::Write => host_socket::PollKind::Write, + SelectKind::Connect => host_socket::PollKind::Connect, }; - let mut pollfd = [PollFd::new(sock.as_fd(), events)]; - let timeout = match interval { - Some(d) => d.try_into().unwrap_or(PollTimeout::MAX), - None => PollTimeout::NONE, - }; - let ret = poll(&mut pollfd, timeout)?; - Ok(ret == 0) + host_socket::poll_socket(sock.as_fd(), kind, interval) } #[cfg(windows)] { @@ -3095,29 +2763,44 @@ mod _socket { #[cfg(not(target_os = "redox"))] #[pyfunction] fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult { - let name = name.to_cstring(vm)?; - // in case 'if_nametoindex' does not set errno - rustpython_host_env::os::set_errno(libc::ENODEV); - let ret = unsafe { c::if_nametoindex(name.as_ptr() as _) }; - if ret == 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(ret) + #[cfg(windows)] + { + let name = name.to_cstring(vm)?; + host_socket::if_nametoindex_checked(&name).map_err(|_| vm.new_last_errno_error()) + } + #[cfg(not(windows))] + { + let name = name.to_cstring(vm)?; + // in case 'if_nametoindex' does not set errno + rustpython_host_env::os::set_errno(libc::ENODEV); + let ret = unsafe { c::if_nametoindex(name.as_ptr() as _) }; + if ret == 0 { + Err(vm.new_last_errno_error()) + } else { + Ok(ret) + } } } #[cfg(not(target_os = "redox"))] #[pyfunction] fn if_indextoname(index: IfIndex, vm: &VirtualMachine) -> PyResult { - let mut buf = [0; c::IF_NAMESIZE + 1]; - // in case 'if_indextoname' does not set errno - rustpython_host_env::os::set_errno(libc::ENXIO); - let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) }; - if ret.is_null() { - Err(vm.new_last_errno_error()) - } else { - let buf = unsafe { ffi::CStr::from_ptr(buf.as_ptr() as _) }; - Ok(buf.to_string_lossy().into_owned()) + #[cfg(windows)] + { + host_socket::if_indextoname_checked(index).map_err(|_| vm.new_last_errno_error()) + } + #[cfg(not(windows))] + { + let mut buf = [0; c::IF_NAMESIZE + 1]; + // in case 'if_indextoname' does not set errno + rustpython_host_env::os::set_errno(libc::ENXIO); + let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) }; + if ret.is_null() { + Err(vm.new_last_errno_error()) + } else { + let buf = unsafe { ffi::CStr::from_ptr(buf.as_ptr() as _) }; + Ok(buf.to_string_lossy().into_owned()) + } } } @@ -3136,75 +2819,22 @@ mod _socket { fn if_nameindex(vm: &VirtualMachine) -> PyResult> { #[cfg(not(windows))] { - let list = nix::net::if_::if_nameindex() + let list = host_socket::if_nameindex() .map_err(|err| err.into_pyexception(vm))? - .to_slice() - .iter() - .map(|iface| { - let tup: (u32, String) = - (iface.index(), iface.name().to_string_lossy().into_owned()); - tup.to_pyobject(vm) - }) + .into_iter() + .map(|tup| tup.to_pyobject(vm)) .collect(); Ok(list) } #[cfg(windows)] { - use windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH; - - let table = MibTable::get_raw().map_err(|err| err.into_pyexception(vm))?; - let list = table.as_slice().iter().map(|entry| { - let name = - get_name(&entry.InterfaceLuid).map_err(|err| err.into_pyexception(vm))?; - let tup = (entry.InterfaceIndex, name.to_string_lossy()); - Ok(tup.to_pyobject(vm)) - }); - let list = list.collect::>()?; - return Ok(list); - - fn get_name(luid: &NET_LUID_LH) -> io::Result { - let mut buf = [0; c::IF_NAMESIZE + 1]; - let ret = unsafe { - IpHelper::ConvertInterfaceLuidToNameW(luid, buf.as_mut_ptr(), buf.len()) - }; - if ret == 0 { - Ok(widestring::WideCString::from_ustr_truncate( - widestring::WideStr::from_slice(&buf[..]), - )) - } else { - Err(io::Error::from_raw_os_error(ret as i32)) - } - } - struct MibTable { - ptr: core::ptr::NonNull, - } - impl MibTable { - fn get_raw() -> io::Result { - let mut ptr = core::ptr::null_mut(); - let ret = unsafe { IpHelper::GetIfTable2Ex(IpHelper::MibIfTableRaw, &mut ptr) }; - if ret == 0 { - let ptr = unsafe { core::ptr::NonNull::new_unchecked(ptr) }; - Ok(Self { ptr }) - } else { - Err(io::Error::from_raw_os_error(ret as i32)) - } - } - } - impl MibTable { - fn as_slice(&self) -> &[IpHelper::MIB_IF_ROW2] { - unsafe { - let p = self.ptr.as_ptr(); - let ptr = &raw const (*p).Table as *const IpHelper::MIB_IF_ROW2; - core::slice::from_raw_parts(ptr, (*p).NumEntries as usize) - } - } - } - impl Drop for MibTable { - fn drop(&mut self) { - unsafe { IpHelper::FreeMibTable(self.ptr.as_ptr() as *mut _) }; - } - } + let list = host_socket::if_nameindex() + .map_err(|err| err.into_pyexception(vm))? + .into_iter() + .map(|tup| tup.to_pyobject(vm)) + .collect(); + Ok(list) } } @@ -3326,7 +2956,7 @@ mod _socket { } #[cfg(windows)] { - windows_sys::Win32::Networking::WinSock::INVALID_SOCKET as RawSocket + host_socket::INVALID_RAW_SOCKET as RawSocket } }; @@ -3341,19 +2971,14 @@ mod _socket { let strerr = { #[cfg(unix)] { - let s = match err_kind { - SocketError::GaiError => unsafe { - ffi::CStr::from_ptr(libc::gai_strerror(err.error_num())) - }, - SocketError::HError => unsafe { - ffi::CStr::from_ptr(libc::hstrerror(err.error_num())) - }, - }; - s.to_str().unwrap() + match err_kind { + SocketError::GaiError => host_socket::gai_error_string(err.error_num()), + SocketError::HError => host_socket::h_error_string(err.error_num()), + } } #[cfg(windows)] { - "getaddrinfo failed" + "getaddrinfo failed".to_owned() } }; let exception_cls = match err_kind { @@ -3433,7 +3058,7 @@ mod _socket { let newsock = sock.try_clone()?; let fd = into_sock_fileno(newsock); #[cfg(windows)] - crate::vm::stdlib::nt::raw_set_handle_inheritable(fd as _, false)?; + host_socket::set_socket_inheritable(fd as _, false)?; Ok(fd) } @@ -3443,18 +3068,7 @@ mod _socket { } fn close_inner(x: RawSocket) -> io::Result<()> { - #[cfg(unix)] - use libc::close; - #[cfg(windows)] - use windows_sys::Win32::Networking::WinSock::closesocket as close; - let ret = unsafe { close(x as _) }; - if ret < 0 { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() != Some(errcode!(ECONNRESET)) { - return Err(err); - } - } - Ok(()) + host_socket::close_socket_ignore_connreset(x as _) } enum SocketError { @@ -3462,45 +3076,17 @@ mod _socket { GaiError, } - #[cfg(all(unix, not(target_os = "redox")))] - fn checked_cmsg_len(len: usize) -> Option { - // SAFETY: CMSG_LEN is always safe - let cmsg_len = |length| unsafe { libc::CMSG_LEN(length) }; - if len as u64 > (i32::MAX as u64 - cmsg_len(0) as u64) { - return None; - } - let res = cmsg_len(len as _) as usize; - if res > i32::MAX as usize || res < len { - return None; - } - Some(res) - } - - #[cfg(all(unix, not(target_os = "redox")))] - fn checked_cmsg_space(len: usize) -> Option { - // SAFETY: CMSG_SPACE is always safe - let cmsg_space = |length| unsafe { libc::CMSG_SPACE(length) }; - if len as u64 > (i32::MAX as u64 - cmsg_space(1) as u64) { - return None; - } - let res = cmsg_space(len as _) as usize; - if res > i32::MAX as usize || res < len { - return None; - } - Some(res) - } - #[cfg(all(unix, not(target_os = "redox")))] #[pyfunction(name = "CMSG_LEN")] fn cmsg_len(length: usize, vm: &VirtualMachine) -> PyResult { - checked_cmsg_len(length) + host_socket::checked_cmsg_len(length) .ok_or_else(|| vm.new_overflow_error("CMSG_LEN() argument out of range")) } #[cfg(all(unix, not(target_os = "redox")))] #[pyfunction(name = "CMSG_SPACE")] fn cmsg_space(length: usize, vm: &VirtualMachine) -> PyResult { - checked_cmsg_space(length) + host_socket::checked_cmsg_space(length) .ok_or_else(|| vm.new_overflow_error("CMSG_SPACE() argument out of range")) } } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index a7aacf2d1a6..6e06e4e9efb 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1372,26 +1372,17 @@ mod _ssl { ) -> PyResult<()> { #[cfg(windows)] { - // Windows: Use schannel to load from both ROOT and CA stores - use schannel::cert_store::CertStore; - let store_names = ["ROOT", "CA"]; - let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; for store_name in store_names { - for open_fn in &open_fns { - if let Ok(cert_store) = open_fn(store_name) { - for cert_ctx in cert_store.certs() { - let der_bytes = cert_ctx.to_der(); - let cert = - rustls::pki_types::CertificateDer::from(der_bytes.to_vec()); - let is_ca = cert::is_ca_certificate(cert.as_ref()); - if store.add(cert).is_ok() { - *self.x509_cert_count.write() += 1; - if is_ca { - *self.ca_cert_count.write() += 1; - } - } + let certs = rustpython_host_env::cert_store::enum_certificates(store_name); + for cert_ctx in certs.entries { + let cert = rustls::pki_types::CertificateDer::from(cert_ctx.der.to_vec()); + let is_ca = cert::is_ca_certificate(cert.as_ref()); + if store.add(cert).is_ok() { + *self.x509_cert_count.write() += 1; + if is_ca { + *self.ca_cert_count.write() += 1; } } } @@ -4932,39 +4923,28 @@ mod _ssl { store_name: PyUtf8StrRef, vm: &VirtualMachine, ) -> PyResult> { - use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; - use windows_sys::Win32::Security::Cryptography; - let store_name_str = store_name.as_str(); - - // Try both Current User and Local Machine stores - let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; - let stores = open_fns - .iter() - .filter_map(|open| open(store_name_str).ok()) - .collect::>(); - - // If no stores could be opened, raise OSError - if stores.is_empty() { + let certs = rustpython_host_env::cert_store::enum_certificates(store_name_str); + if !certs.had_open_store { return Err(vm.new_os_error(format!( "failed to open certificate store {store_name_str:?}" ))); } - let certs = stores.iter().flat_map(|s| s.certs()).map(|c| { - let cert = vm.ctx.new_bytes(c.to_der().to_owned()); - let enc_type = unsafe { - let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; - (*ptr).dwCertEncodingType - }; - let enc_type = match enc_type { - Cryptography::X509_ASN_ENCODING => vm.new_pyobj("x509_asn"), - Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj("pkcs_7_asn"), - other => vm.new_pyobj(other), + let certs = certs.entries.into_iter().map(|c| { + let cert = vm.ctx.new_bytes(c.der); + let enc_type = match c.encoding { + rustpython_host_env::cert_store::EncodingType::X509Asn => vm.new_pyobj("x509_asn"), + rustpython_host_env::cert_store::EncodingType::Pkcs7Asn => { + vm.new_pyobj("pkcs_7_asn") + } + rustpython_host_env::cert_store::EncodingType::Other(other) => vm.new_pyobj(other), }; - let usage: PyObjectRef = match c.valid_uses() { - Ok(ValidUses::All) => vm.ctx.new_bool(true).into(), - Ok(ValidUses::Oids(oids)) => { + let usage: PyObjectRef = match c.valid_uses { + Ok(rustpython_host_env::cert_store::CertificateUses::All) => { + vm.ctx.new_bool(true).into() + } + Ok(rustpython_host_env::cert_store::CertificateUses::Oids(oids)) => { match crate::builtins::PyFrozenSet::from_iter( vm, oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), @@ -4983,54 +4963,30 @@ mod _ssl { #[cfg(windows)] #[pyfunction] fn enum_crls(store_name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult> { - use windows_sys::Win32::Security::Cryptography::{ - CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW, - X509_ASN_ENCODING, - }; - let store_name_str = store_name.as_str(); - let store_name_wide: Vec = store_name_str - .encode_utf16() - .chain(core::iter::once(0)) - .collect(); - - // Open system store - let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) }; - - if store.is_null() { - return Err(vm.new_os_error(format!( + let crls = rustpython_host_env::cert_store::enum_crls(store_name_str).map_err(|_| { + vm.new_os_error(format!( "failed to open certificate store {store_name_str:?}" - ))); - } - - let mut result = Vec::new(); - - let mut crl_context: *const CRL_CONTEXT = core::ptr::null(); - loop { - crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) }; - if crl_context.is_null() { - break; - } - - let crl = unsafe { &*crl_context }; - let crl_bytes = - unsafe { core::slice::from_raw_parts(crl.pbCrlEncoded, crl.cbCrlEncoded as usize) }; - - let enc_type = if crl.dwCertEncodingType == X509_ASN_ENCODING { - vm.new_pyobj("x509_asn") - } else { - vm.new_pyobj(crl.dwCertEncodingType) - }; - - result.push( - vm.new_tuple((vm.ctx.new_bytes(crl_bytes.to_vec()), enc_type)) - .into(), - ); - } - - unsafe { CertCloseStore(store, 0) }; + )) + })?; - Ok(result) + Ok(crls + .into_iter() + .map(|crl| { + let enc_type = match crl.encoding { + rustpython_host_env::cert_store::EncodingType::X509Asn => { + vm.new_pyobj("x509_asn") + } + rustpython_host_env::cert_store::EncodingType::Pkcs7Asn => { + vm.new_pyobj("pkcs_7_asn") + } + rustpython_host_env::cert_store::EncodingType::Other(other) => { + vm.new_pyobj(other) + } + }; + vm.new_tuple((vm.ctx.new_bytes(crl.der), enc_type)).into() + }) + .collect()) } // Certificate type for SSL module (pure Rust implementation) diff --git a/crates/stdlib/src/termios.rs b/crates/stdlib/src/termios.rs index 919b4ff702a..9731f33b39f 100644 --- a/crates/stdlib/src/termios.rs +++ b/crates/stdlib/src/termios.rs @@ -31,56 +31,6 @@ mod termios { // TCSBRKP, TIOCGICOUNT, TIOCGLCKTRMIOS, TIOCSERCONFIG, TIOCSERGETLSR, TIOCSERGETMULTI, // TIOCSERGSTRUCT, TIOCSERGWILD, TIOCSERSETMULTI, TIOCSERSWILD, TIOCSER_TEMT, // TIOCSLCKTRMIOS, TIOCSSERIAL, TIOCTTYGSTRUCT - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[pyattr] - use libc::{CSTART, CSTOP, CSWTCH}; - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[pyattr] - use libc::{FIOASYNC, TIOCGETD, TIOCSETD}; - #[pyattr] - use libc::{FIOCLEX, FIONBIO, TIOCGWINSZ, TIOCSWINSZ}; - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[pyattr] - use libc::{ - FIONCLEX, FIONREAD, TIOCEXCL, TIOCM_CAR, TIOCM_CD, TIOCM_CTS, TIOCM_DSR, TIOCM_DTR, - TIOCM_LE, TIOCM_RI, TIOCM_RNG, TIOCM_RTS, TIOCM_SR, TIOCM_ST, TIOCMBIC, TIOCMBIS, TIOCMGET, - TIOCMSET, TIOCNXCL, TIOCSCTTY, - }; - #[cfg(any(target_os = "android", target_os = "linux"))] - #[pyattr] - use libc::{ - IBSHIFT, TCFLSH, TCGETA, TCGETS, TCSBRK, TCSETA, TCSETAF, TCSETAW, TCSETS, TCSETSF, - TCSETSW, TCXONC, TIOCGSERIAL, TIOCGSOFTCAR, TIOCINQ, TIOCLINUX, TIOCSSOFTCAR, XTABS, - }; - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "macos" - ))] - #[pyattr] - use libc::{TIOCCONS, TIOCGPGRP, TIOCOUTQ, TIOCSPGRP, TIOCSTI}; - #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"))] - #[pyattr] - use libc::{ - TIOCNOTTY, TIOCPKT, TIOCPKT_DATA, TIOCPKT_DOSTOP, TIOCPKT_FLUSHREAD, TIOCPKT_FLUSHWRITE, - TIOCPKT_NOSTOP, TIOCPKT_START, TIOCPKT_STOP, - }; #[cfg(any( target_os = "android", target_os = "freebsd", @@ -91,7 +41,7 @@ mod termios { target_os = "solaris" ))] #[pyattr] - use termios::os::target::TAB3; + use host_termios::TAB3; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", @@ -100,7 +50,18 @@ mod termios { target_os = "openbsd" ))] #[pyattr] - use termios::os::target::TCSASOFT; + use host_termios::TCSASOFT; + #[pyattr] + use host_termios::{ + B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, + B19200, B38400, B57600, B115200, B230400, BRKINT, CLOCAL, CREAD, CRTSCTS, CS5, CS6, CS7, + CS8, CSIZE, CSTOPB, ECHO, ECHOCTL, ECHOE, ECHOK, ECHOKE, ECHONL, ECHOPRT, EXTA, EXTB, + FLUSHO, HUPCL, ICANON, ICRNL, IEXTEN, IGNBRK, IGNCR, IGNPAR, IMAXBEL, INLCR, INPCK, ISIG, + ISTRIP, IXANY, IXOFF, IXON, NCCS, NOFLSH, OCRNL, ONLCR, ONLRET, ONOCR, OPOST, PARENB, + PARMRK, PARODD, PENDIN, TCIFLUSH, TCIOFF, TCIOFLUSH, TCION, TCOFLUSH, TCOOFF, TCOON, + TCSADRAIN, TCSAFLUSH, TCSANOW, TOSTOP, VDISCARD, VEOF, VEOL, VEOL2, VERASE, VINTR, VKILL, + VLNEXT, VMIN, VQUIT, VREPRINT, VSTART, VSTOP, VSUSP, VTIME, VWERASE, + }; #[cfg(any( target_os = "android", target_os = "freebsd", @@ -110,10 +71,10 @@ mod termios { target_os = "solaris" ))] #[pyattr] - use termios::os::target::{B460800, B921600}; + use host_termios::{B460800, B921600}; #[cfg(any(target_os = "android", target_os = "linux"))] #[pyattr] - use termios::os::target::{ + use host_termios::{ B500000, B576000, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000, CBAUDEX, }; @@ -125,7 +86,7 @@ mod termios { target_os = "solaris" ))] #[pyattr] - use termios::os::target::{ + use host_termios::{ BS0, BS1, BSDLY, CR0, CR1, CR2, CR3, CRDLY, FF0, FF1, FFDLY, NL0, NL1, NLDLY, OFDEL, OFILL, TAB1, TAB2, VT0, VT1, VTDLY, }; @@ -136,7 +97,7 @@ mod termios { target_os = "solaris" ))] #[pyattr] - use termios::os::target::{CBAUD, CIBAUD, IUCLC, OLCUC, XCASE}; + use host_termios::{CBAUD, CIBAUD, IUCLC, OLCUC, XCASE}; #[cfg(any( target_os = "android", target_os = "freebsd", @@ -146,38 +107,74 @@ mod termios { target_os = "solaris" ))] #[pyattr] - use termios::os::target::{TAB0, TABDLY}; + use host_termios::{TAB0, TABDLY}; #[cfg(any(target_os = "android", target_os = "linux"))] #[pyattr] - use termios::os::target::{VSWTC, VSWTC as VSWTCH}; + use host_termios::{VSWTC, VSWTC as VSWTCH}; #[cfg(any(target_os = "illumos", target_os = "solaris"))] #[pyattr] - use termios::os::target::{VSWTCH, VSWTCH as VSWTC}; + use host_termios::{VSWTCH, VSWTCH as VSWTC}; + #[cfg(any(target_os = "illumos", target_os = "solaris"))] #[pyattr] - use termios::{ - B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, - B19200, B38400, BRKINT, CLOCAL, CREAD, CS5, CS6, CS7, CS8, CSIZE, CSTOPB, ECHO, ECHOE, - ECHOK, ECHONL, HUPCL, ICANON, ICRNL, IEXTEN, IGNBRK, IGNCR, IGNPAR, INLCR, INPCK, ISIG, - ISTRIP, IXANY, IXOFF, IXON, NOFLSH, OCRNL, ONLCR, ONLRET, ONOCR, OPOST, PARENB, PARMRK, - PARODD, TCIFLUSH, TCIOFF, TCIOFLUSH, TCION, TCOFLUSH, TCOOFF, TCOON, TCSADRAIN, TCSAFLUSH, - TCSANOW, TOSTOP, VEOF, VEOL, VERASE, VINTR, VKILL, VMIN, VQUIT, VSTART, VSTOP, VSUSP, - VTIME, - os::target::{ - B57600, B115200, B230400, CRTSCTS, ECHOCTL, ECHOKE, ECHOPRT, EXTA, EXTB, FLUSHO, - IMAXBEL, NCCS, PENDIN, VDISCARD, VEOL2, VLNEXT, VREPRINT, VWERASE, - }, + use libc::{CSTART, CSTOP, CSWTCH}; + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[pyattr] + use libc::{FIOASYNC, TIOCGETD, TIOCSETD}; + #[pyattr] + use libc::{FIOCLEX, FIONBIO, TIOCGWINSZ, TIOCSWINSZ}; + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[pyattr] + use libc::{ + FIONCLEX, FIONREAD, TIOCEXCL, TIOCM_CAR, TIOCM_CD, TIOCM_CTS, TIOCM_DSR, TIOCM_DTR, + TIOCM_LE, TIOCM_RI, TIOCM_RNG, TIOCM_RTS, TIOCM_SR, TIOCM_ST, TIOCMBIC, TIOCMBIS, TIOCMGET, + TIOCMSET, TIOCNXCL, TIOCSCTTY, + }; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[pyattr] + use libc::{ + IBSHIFT, TCFLSH, TCGETA, TCGETS, TCSBRK, TCSETA, TCSETAF, TCSETAW, TCSETS, TCSETSF, + TCSETSW, TCXONC, TIOCGSERIAL, TIOCGSOFTCAR, TIOCINQ, TIOCLINUX, TIOCSSOFTCAR, XTABS, + }; + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos" + ))] + #[pyattr] + use libc::{TIOCCONS, TIOCGPGRP, TIOCOUTQ, TIOCSPGRP, TIOCSTI}; + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"))] + #[pyattr] + use libc::{ + TIOCNOTTY, TIOCPKT, TIOCPKT_DATA, TIOCPKT_DOSTOP, TIOCPKT_FLUSHREAD, TIOCPKT_FLUSHWRITE, + TIOCPKT_NOSTOP, TIOCPKT_START, TIOCPKT_STOP, }; #[pyfunction] fn tcgetattr(fd: i32, vm: &VirtualMachine) -> PyResult> { let termios = host_termios::tcgetattr(fd).map_err(|e| termios_error(e, vm))?; - let noncanon = (termios.c_lflag & termios::ICANON) == 0; + let noncanon = (termios.c_lflag & host_termios::ICANON) == 0; let cc = termios .c_cc .iter() .enumerate() .map(|(i, &c)| match i { - termios::VMIN | termios::VTIME if noncanon => vm.ctx.new_int(c).into(), + host_termios::VMIN | host_termios::VTIME if noncanon => vm.ctx.new_int(c).into(), _ => vm.ctx.new_bytes(vec![c as _]).into(), }) .collect::>(); @@ -186,8 +183,8 @@ mod termios { termios.c_oflag.to_pyobject(vm), termios.c_cflag.to_pyobject(vm), termios.c_lflag.to_pyobject(vm), - termios::cfgetispeed(&termios).to_pyobject(vm), - termios::cfgetospeed(&termios).to_pyobject(vm), + host_termios::cfgetispeed(&termios).to_pyobject(vm), + host_termios::cfgetospeed(&termios).to_pyobject(vm), vm.ctx.new_list(cc).into(), ]; Ok(out) @@ -204,9 +201,9 @@ mod termios { termios.c_oflag = oflag.try_into_value(vm)?; termios.c_cflag = cflag.try_into_value(vm)?; termios.c_lflag = lflag.try_into_value(vm)?; - termios::cfsetispeed(&mut termios, ispeed.try_into_value(vm)?) + host_termios::cfsetispeed(&mut termios, ispeed.try_into_value(vm)?) .map_err(|e| termios_error(e, vm))?; - termios::cfsetospeed(&mut termios, ospeed.try_into_value(vm)?) + host_termios::cfsetospeed(&mut termios, ospeed.try_into_value(vm)?) .map_err(|e| termios_error(e, vm))?; let cc = PyListRef::try_from_object(vm, cc)?; let cc = cc.borrow_vec(); diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 869f3418f11..3ffa68c0b19 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -89,56 +89,13 @@ writeable = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true } -nix = { workspace = true } exitcode = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustyline = { workspace = true } which = { workspace = true } -errno = { workspace = true } widestring = { workspace = true } -[target.'cfg(all(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android"), not(any(target_env = "musl", target_env = "sgx"))))'.dependencies] -libffi = { workspace = true, features = ["system"] } -libloading = { workspace = true } - -[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] -num_cpus = { workspace = true } - -[target.'cfg(windows)'.dependencies] -junction = { workspace = true } - -[target.'cfg(windows)'.dependencies.windows-sys] -workspace = true -features = [ - "Win32_Foundation", - "Win32_Globalization", - "Win32_Media_Audio", - "Win32_Networking_WinSock", - "Win32_Security", - "Win32_Security_Authorization", - "Win32_Storage_FileSystem", - "Win32_System_Console", - "Win32_System_Diagnostics_Debug", - "Win32_System_Environment", - "Win32_System_IO", - "Win32_System_Ioctl", - "Win32_System_JobObjects", - "Win32_System_Kernel", - "Win32_System_LibraryLoader", - "Win32_System_Memory", - "Win32_System_Performance", - "Win32_System_Pipes", - "Win32_System_Registry", - "Win32_System_SystemInformation", - "Win32_System_SystemServices", - "Win32_System_Threading", - "Win32_System_Time", - "Win32_System_WindowsProgramming", - "Win32_UI_Shell", - "Win32_UI_WindowsAndMessaging", -] - [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] wasm-bindgen = { workspace = true, optional = true } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 51ce99de82b..1adc5f0a1b1 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1433,13 +1433,6 @@ impl IntoPyException for std::io::Error { } } -#[cfg(unix)] -impl IntoPyException for nix::Error { - fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { - std::io::Error::from(self).into_pyexception(vm) - } -} - #[cfg(unix)] impl IntoPyException for rustix::io::Errno { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 51f85046ea7..ecd33a6f4cc 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -369,9 +369,11 @@ fn get_executable_path() -> Option { } /// Parse pyvenv.cfg and extract the 'home' key value -#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { + #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] let content = crate::host_env::fs::read_to_string(pyvenv_cfg).ok()?; + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + let content = std::fs::read_to_string(pyvenv_cfg).ok()?; for line in content.lines() { if let Some((key, value)) = line.split_once('=') @@ -384,11 +386,6 @@ fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { None } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -fn parse_pyvenv_home(_pyvenv_cfg: &Path) -> Option { - None -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/vm/src/readline.rs b/crates/vm/src/readline.rs index b154bd1365c..bd0ecd73912 100644 --- a/crates/vm/src/readline.rs +++ b/crates/vm/src/readline.rs @@ -14,7 +14,7 @@ pub enum ReadlineResult { Interrupt, Io(std::io::Error), #[cfg(unix)] - OsError(nix::Error), + OsError(String), Other(OtherError), } @@ -163,7 +163,7 @@ pub mod rustyline_readline { Err(ReadlineError::Io(e)) => ReadlineResult::Io(e), Err(ReadlineError::Signal(_)) => continue, #[cfg(unix)] - Err(ReadlineError::Errno(num)) => ReadlineResult::OsError(num), + Err(ReadlineError::Errno(num)) => ReadlineResult::OsError(num.to_string()), Err(e) => ReadlineResult::Other(e.into()), }; } diff --git a/crates/vm/src/signal.rs b/crates/vm/src/signal.rs index dbeeaeb4bf8..6c8e9cb6d35 100644 --- a/crates/vm/src/signal.rs +++ b/crates/vm/src/signal.rs @@ -92,6 +92,7 @@ pub(crate) fn set_triggered() { } #[inline(always)] +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn is_triggered() -> bool { ANY_TRIGGERED.load(Ordering::Relaxed) } diff --git a/crates/vm/src/stdlib/_codecs.rs b/crates/vm/src/stdlib/_codecs.rs index 0fc9c792b61..a9402edc3a2 100644 --- a/crates/vm/src/stdlib/_codecs.rs +++ b/crates/vm/src/stdlib/_codecs.rs @@ -375,6 +375,7 @@ fn delegate_pycodecs( mod _codecs_windows { use crate::{PyResult, VirtualMachine}; use crate::{builtins::PyStrRef, builtins::PyUtf8StrRef, function::ArgBytesLike}; + use rustpython_host_env::windows as host_windows; #[derive(FromArgs)] struct MbcsEncodeArgs { @@ -387,9 +388,6 @@ mod _codecs_windows { #[pyfunction] fn mbcs_encode(args: MbcsEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec, usize)> { use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Globalization::{ - CP_ACP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, - }; let errors = args.errors.as_ref().map_or("strict", |s| s.as_str()); let s = match args.s.to_str() { @@ -411,56 +409,31 @@ mod _codecs_windows { let wide: Vec = std::ffi::OsStr::new(s).to_wide(); // Get the required buffer size - let size = unsafe { - WideCharToMultiByte( - CP_ACP, - WC_NO_BEST_FIT_CHARS, - wide.as_ptr(), - wide.len() as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - - if size == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_encode failed: {err}"))); - } - - let mut buffer = vec![0u8; size as usize]; - let mut used_default_char: i32 = 0; - - let result = unsafe { - WideCharToMultiByte( - CP_ACP, - WC_NO_BEST_FIT_CHARS, - wide.as_ptr(), - wide.len() as i32, - buffer.as_mut_ptr().cast(), - size, - core::ptr::null(), - if errors == "strict" { - &mut used_default_char - } else { - core::ptr::null_mut() - }, - ) - }; - - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_encode failed: {err}"))); - } - - if errors == "strict" && used_default_char != 0 { + let (size, _) = host_windows::wide_char_to_multi_byte_len( + host_windows::CP_ACP, + host_windows::WC_NO_BEST_FIT_CHARS, + &wide, + false, + ) + .map_err(|err| vm.new_os_error(format!("mbcs_encode failed: {err}")))?; + + let mut buffer = vec![0u8; size]; + let (result, used_default_char) = host_windows::wide_char_to_multi_byte( + host_windows::CP_ACP, + host_windows::WC_NO_BEST_FIT_CHARS, + &wide, + &mut buffer, + errors == "strict", + ) + .map_err(|err| vm.new_os_error(format!("mbcs_encode failed: {err}")))?; + + if errors == "strict" && used_default_char { return Err(vm.new_unicode_encode_error( "'mbcs' codec can't encode characters: invalid character", )); } - buffer.truncate(result as usize); + buffer.truncate(result); Ok((buffer, char_len)) } @@ -477,10 +450,6 @@ mod _codecs_windows { #[pyfunction] fn mbcs_decode(args: MbcsDecodeArgs, vm: &VirtualMachine) -> PyResult<(String, usize)> { - use windows_sys::Win32::Globalization::{ - CP_ACP, MB_ERR_INVALID_CHARS, MultiByteToWideChar, - }; - let _errors = args.errors.as_ref().map_or("strict", |s| s.as_str()); let data = args.data.borrow_buf(); let len = data.len(); @@ -490,72 +459,42 @@ mod _codecs_windows { } // Get the required buffer size for UTF-16 - let size = unsafe { - MultiByteToWideChar( - CP_ACP, - MB_ERR_INVALID_CHARS, - data.as_ptr().cast(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; + let size = host_windows::multi_byte_to_wide_len( + host_windows::CP_ACP, + host_windows::MB_ERR_INVALID_CHARS, + data.as_ref(), + ); - if size == 0 { + if size.is_err() { // Try without MB_ERR_INVALID_CHARS for non-strict mode (replacement behavior) - let size = unsafe { - MultiByteToWideChar( - CP_ACP, - 0, - data.as_ptr().cast(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; - if size == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {err}"))); - } + let size = host_windows::multi_byte_to_wide_len(host_windows::CP_ACP, 0, data.as_ref()) + .map_err(|err| vm.new_os_error(format!("mbcs_decode failed: {err}")))?; - let mut buffer = vec![0u16; size as usize]; - let result = unsafe { - MultiByteToWideChar( - CP_ACP, - 0, - data.as_ptr().cast(), - len as i32, - buffer.as_mut_ptr(), - size, - ) - }; - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {err}"))); - } - buffer.truncate(result as usize); + let mut buffer = vec![0u16; size]; + let result = host_windows::multi_byte_to_wide( + host_windows::CP_ACP, + 0, + data.as_ref(), + &mut buffer, + ) + .map_err(|err| vm.new_os_error(format!("mbcs_decode failed: {err}")))?; + buffer.truncate(result); let s = String::from_utf16(&buffer) .map_err(|e| vm.new_unicode_decode_error(format!("mbcs_decode failed: {e}")))?; return Ok((s, len)); } // Strict mode succeeded - no invalid characters - let mut buffer = vec![0u16; size as usize]; - let result = unsafe { - MultiByteToWideChar( - CP_ACP, - MB_ERR_INVALID_CHARS, - data.as_ptr().cast(), - len as i32, - buffer.as_mut_ptr(), - size, - ) - }; - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {err}"))); - } - buffer.truncate(result as usize); + let size = size.unwrap(); + let mut buffer = vec![0u16; size]; + let result = host_windows::multi_byte_to_wide( + host_windows::CP_ACP, + host_windows::MB_ERR_INVALID_CHARS, + data.as_ref(), + &mut buffer, + ) + .map_err(|err| vm.new_os_error(format!("mbcs_decode failed: {err}")))?; + buffer.truncate(result); let s = String::from_utf16(&buffer) .map_err(|e| vm.new_unicode_decode_error(format!("mbcs_decode failed: {e}")))?; @@ -573,9 +512,6 @@ mod _codecs_windows { #[pyfunction] fn oem_encode(args: OemEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec, usize)> { use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Globalization::{ - CP_OEMCP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, - }; let errors = args.errors.as_ref().map_or("strict", |s| s.as_str()); let s = match args.s.to_str() { @@ -597,56 +533,31 @@ mod _codecs_windows { let wide: Vec = std::ffi::OsStr::new(s).to_wide(); // Get the required buffer size - let size = unsafe { - WideCharToMultiByte( - CP_OEMCP, - WC_NO_BEST_FIT_CHARS, - wide.as_ptr(), - wide.len() as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - - if size == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_encode failed: {err}"))); - } - - let mut buffer = vec![0u8; size as usize]; - let mut used_default_char: i32 = 0; - - let result = unsafe { - WideCharToMultiByte( - CP_OEMCP, - WC_NO_BEST_FIT_CHARS, - wide.as_ptr(), - wide.len() as i32, - buffer.as_mut_ptr().cast(), - size, - core::ptr::null(), - if errors == "strict" { - &mut used_default_char - } else { - core::ptr::null_mut() - }, - ) - }; - - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_encode failed: {err}"))); - } - - if errors == "strict" && used_default_char != 0 { + let (size, _) = host_windows::wide_char_to_multi_byte_len( + host_windows::CP_OEMCP, + host_windows::WC_NO_BEST_FIT_CHARS, + &wide, + false, + ) + .map_err(|err| vm.new_os_error(format!("oem_encode failed: {err}")))?; + + let mut buffer = vec![0u8; size]; + let (result, used_default_char) = host_windows::wide_char_to_multi_byte( + host_windows::CP_OEMCP, + host_windows::WC_NO_BEST_FIT_CHARS, + &wide, + &mut buffer, + errors == "strict", + ) + .map_err(|err| vm.new_os_error(format!("oem_encode failed: {err}")))?; + + if errors == "strict" && used_default_char { return Err(vm.new_unicode_encode_error( "'oem' codec can't encode characters: invalid character", )); } - buffer.truncate(result as usize); + buffer.truncate(result); Ok((buffer, char_len)) } @@ -663,10 +574,6 @@ mod _codecs_windows { #[pyfunction] fn oem_decode(args: OemDecodeArgs, vm: &VirtualMachine) -> PyResult<(String, usize)> { - use windows_sys::Win32::Globalization::{ - CP_OEMCP, MB_ERR_INVALID_CHARS, MultiByteToWideChar, - }; - let _errors = args.errors.as_ref().map_or("strict", |s| s.as_str()); let data = args.data.borrow_buf(); let len = data.len(); @@ -676,72 +583,43 @@ mod _codecs_windows { } // Get the required buffer size for UTF-16 - let size = unsafe { - MultiByteToWideChar( - CP_OEMCP, - MB_ERR_INVALID_CHARS, - data.as_ptr().cast(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; + let size = host_windows::multi_byte_to_wide_len( + host_windows::CP_OEMCP, + host_windows::MB_ERR_INVALID_CHARS, + data.as_ref(), + ); - if size == 0 { + if size.is_err() { // Try without MB_ERR_INVALID_CHARS for non-strict mode (replacement behavior) - let size = unsafe { - MultiByteToWideChar( - CP_OEMCP, - 0, - data.as_ptr().cast(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; - if size == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {err}"))); - } + let size = + host_windows::multi_byte_to_wide_len(host_windows::CP_OEMCP, 0, data.as_ref()) + .map_err(|err| vm.new_os_error(format!("oem_decode failed: {err}")))?; - let mut buffer = vec![0u16; size as usize]; - let result = unsafe { - MultiByteToWideChar( - CP_OEMCP, - 0, - data.as_ptr().cast(), - len as i32, - buffer.as_mut_ptr(), - size, - ) - }; - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {err}"))); - } - buffer.truncate(result as usize); + let mut buffer = vec![0u16; size]; + let result = host_windows::multi_byte_to_wide( + host_windows::CP_OEMCP, + 0, + data.as_ref(), + &mut buffer, + ) + .map_err(|err| vm.new_os_error(format!("oem_decode failed: {err}")))?; + buffer.truncate(result); let s = String::from_utf16(&buffer) .map_err(|e| vm.new_unicode_decode_error(format!("oem_decode failed: {e}")))?; return Ok((s, len)); } // Strict mode succeeded - no invalid characters - let mut buffer = vec![0u16; size as usize]; - let result = unsafe { - MultiByteToWideChar( - CP_OEMCP, - MB_ERR_INVALID_CHARS, - data.as_ptr().cast(), - len as i32, - buffer.as_mut_ptr(), - size, - ) - }; - if result == 0 { - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {err}"))); - } - buffer.truncate(result as usize); + let size = size.unwrap(); + let mut buffer = vec![0u16; size]; + let result = host_windows::multi_byte_to_wide( + host_windows::CP_OEMCP, + host_windows::MB_ERR_INVALID_CHARS, + data.as_ref(), + &mut buffer, + ) + .map_err(|err| vm.new_os_error(format!("oem_decode failed: {err}")))?; + buffer.truncate(result); let s = String::from_utf16(&buffer) .map_err(|e| vm.new_unicode_decode_error(format!("oem_decode failed: {e}")))?; @@ -768,17 +646,12 @@ mod _codecs_windows { /// Get WideCharToMultiByte flags for encoding. /// Matches encode_code_page_flags() in CPython. fn encode_code_page_flags(code_page: u32, errors: &str) -> u32 { - use windows_sys::Win32::Globalization::{WC_ERR_INVALID_CHARS, WC_NO_BEST_FIT_CHARS}; - if code_page == 65001 { - // CP_UTF8 - WC_ERR_INVALID_CHARS - } else if code_page == 65000 { - // CP_UTF7 only supports flags=0 - 0 - } else if errors == "replace" { + if code_page == host_windows::CP_UTF8 { + host_windows::WC_ERR_INVALID_CHARS + } else if code_page == host_windows::CP_UTF7 || errors == "replace" { 0 } else { - WC_NO_BEST_FIT_CHARS + host_windows::WC_NO_BEST_FIT_CHARS } } @@ -790,80 +663,56 @@ mod _codecs_windows { wide: &[u16], vm: &VirtualMachine, ) -> PyResult>> { - use windows_sys::Win32::Globalization::WideCharToMultiByte; - let flags = encode_code_page_flags(code_page, "strict"); - let use_default_char = code_page != 65001 && code_page != 65000; - let mut used_default_char: i32 = 0; - let pused = if use_default_char { - &mut used_default_char as *mut i32 - } else { - core::ptr::null_mut() - }; - - let size = unsafe { - WideCharToMultiByte( - code_page, - flags, - wide.as_ptr(), - wide.len() as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - pused, - ) - }; - - if size <= 0 { - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); - if err_code == 1113 { - // ERROR_NO_UNICODE_TRANSLATION - return Ok(None); + let use_default_char = + code_page != host_windows::CP_UTF8 && code_page != host_windows::CP_UTF7; + + let size = match host_windows::wide_char_to_multi_byte_len( + code_page, + flags, + wide, + use_default_char, + ) { + Ok((size, used_default_char)) => { + if use_default_char && used_default_char { + return Ok(None); + } + size + } + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if err_code == host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 { + return Ok(None); + } + return Err(vm.new_os_error(format!("code_page_encode: {err}"))); } - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("code_page_encode: {err}"))); - } - - if use_default_char && used_default_char != 0 { - return Ok(None); - } - - let mut buffer = vec![0u8; size as usize]; - used_default_char = 0; - let pused = if use_default_char { - &mut used_default_char as *mut i32 - } else { - core::ptr::null_mut() - }; - - let result = unsafe { - WideCharToMultiByte( - code_page, - flags, - wide.as_ptr(), - wide.len() as i32, - buffer.as_mut_ptr().cast(), - size, - core::ptr::null(), - pused, - ) }; - if result <= 0 { - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); - if err_code == 1113 { - return Ok(None); + let mut buffer = vec![0u8; size]; + let result = match host_windows::wide_char_to_multi_byte( + code_page, + flags, + wide, + &mut buffer, + use_default_char, + ) { + Ok((result, used_default_char)) => { + if use_default_char && used_default_char { + return Ok(None); + } + result } - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("code_page_encode: {err}"))); - } - - if use_default_char && used_default_char != 0 { - return Ok(None); - } + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if err_code == host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 { + return Ok(None); + } + return Err(vm.new_os_error(format!("code_page_encode: {err}"))); + } + }; - buffer.truncate(result as usize); + buffer.truncate(result); Ok(Some(buffer)) } @@ -876,11 +725,11 @@ mod _codecs_windows { vm: &VirtualMachine, ) -> PyResult<(Vec, usize)> { use crate::builtins::{PyBytes, PyStr, PyTuple}; - use windows_sys::Win32::Globalization::WideCharToMultiByte; let char_len = s.char_len(); let flags = encode_code_page_flags(code_page, errors); - let use_default_char = code_page != 65001 && code_page != 65000; + let use_default_char = + code_page != host_windows::CP_UTF8 && code_page != host_windows::CP_UTF7; let encoding_str = vm.ctx.new_str(encoding_name); let reason_str = vm.ctx.new_str("invalid character"); @@ -902,28 +751,19 @@ mod _codecs_windows { wchars[1] = ((ch - 0x10000) & 0x3FF) as u16 + 0xDC00; 2 }; - let mut used_default_char: i32 = 0; - let pused = if use_default_char { - &mut used_default_char as *mut i32 - } else { - core::ptr::null_mut() - }; - let outsize = unsafe { - WideCharToMultiByte( - code_page, - flags, - wchars.as_ptr(), - wchar_len, - core::ptr::null_mut(), - 0, - core::ptr::null(), - pused, - ) - }; - if outsize <= 0 || (use_default_char && used_default_char != 0) { - break; + match host_windows::wide_char_to_multi_byte_len( + code_page, + flags, + &wchars[..wchar_len], + use_default_char, + ) { + Ok((_outsize, used_default_char)) + if !use_default_char || !used_default_char => + { + fail_pos += 1; + } + _ => break, } - fail_pos += 1; } return Err(vm.new_unicode_encode_error_real( encoding_str, @@ -961,29 +801,16 @@ mod _codecs_windows { } if !is_surrogate { - let mut used_default_char: i32 = 0; - let pused = if use_default_char { - &mut used_default_char as *mut i32 - } else { - core::ptr::null_mut() - }; - let mut buf = [0u8; 8]; - let outsize = unsafe { - WideCharToMultiByte( - code_page, - flags, - wchars.as_ptr(), - wchar_len, - buf.as_mut_ptr().cast(), - buf.len() as i32, - core::ptr::null(), - pused, - ) - }; - - if outsize > 0 && (!use_default_char || used_default_char == 0) { - output.extend_from_slice(&buf[..outsize as usize]); + if let Ok((outsize, used_default_char)) = host_windows::wide_char_to_multi_byte( + code_page, + flags, + &wchars[..wchar_len], + &mut buf, + use_default_char, + ) && (!use_default_char || !used_default_char) + { + output.extend_from_slice(&buf[..outsize]); pos += 1; continue; } @@ -1096,51 +923,41 @@ mod _codecs_windows { data: &[u8], vm: &VirtualMachine, ) -> PyResult>> { - use windows_sys::Win32::Globalization::{MB_ERR_INVALID_CHARS, MultiByteToWideChar}; - - let mut flags = MB_ERR_INVALID_CHARS; + let mut flags = host_windows::MB_ERR_INVALID_CHARS; loop { - let size = unsafe { - MultiByteToWideChar( - code_page, - flags, - data.as_ptr().cast(), - data.len() as i32, - core::ptr::null_mut(), - 0, - ) + let size = match host_windows::multi_byte_to_wide_len(code_page, flags, data) { + Ok(size) => size, + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if flags != 0 && err_code == host_windows::ERROR_INVALID_FLAGS_I32 { + flags = 0; + continue; + } + if err_code == host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 { + return Ok(None); + } + return Err(vm.new_os_error(format!("code_page_decode: {err}"))); + } }; - if size > 0 { - let mut buffer = vec![0u16; size as usize]; - let result = unsafe { - MultiByteToWideChar( - code_page, - flags, - data.as_ptr().cast(), - data.len() as i32, - buffer.as_mut_ptr(), - size, - ) - }; - if result > 0 { - buffer.truncate(result as usize); + let mut buffer = vec![0u16; size]; + match host_windows::multi_byte_to_wide(code_page, flags, data, &mut buffer) { + Ok(result) => { + buffer.truncate(result); return Ok(Some(buffer)); } + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if flags != 0 && err_code == host_windows::ERROR_INVALID_FLAGS_I32 { + flags = 0; + continue; + } + if err_code == host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 { + return Ok(None); + } + return Err(vm.new_os_error(format!("code_page_decode: {err}"))); + } } - - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); - // ERROR_INVALID_FLAGS = 1004 - if flags != 0 && err_code == 1004 { - flags = 0; - continue; - } - // ERROR_NO_UNICODE_TRANSLATION = 1113 - if err_code == 1113 { - return Ok(None); - } - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("code_page_decode: {err}"))); } } @@ -1155,7 +972,6 @@ mod _codecs_windows { ) -> PyResult<(PyStrRef, usize)> { use crate::builtins::PyTuple; use crate::common::wtf8::Wtf8Buf; - use windows_sys::Win32::Globalization::{MB_ERR_INVALID_CHARS, MultiByteToWideChar}; let len = data.len(); let encoding_str = vm.ctx.new_str(encoding_name); @@ -1167,33 +983,37 @@ mod _codecs_windows { if errors == "strict" && is_final { // Find the exact failing byte position by trying byte by byte let mut fail_pos = 0; - let mut flags_s: u32 = MB_ERR_INVALID_CHARS; + let mut flags_s: u32 = host_windows::MB_ERR_INVALID_CHARS; let mut buf = [0u16; 2]; while fail_pos < len { let mut in_size = 1; let mut found = false; while in_size <= 4 && fail_pos + in_size <= len { - let outsize = unsafe { - MultiByteToWideChar( - code_page, - flags_s, - data[fail_pos..].as_ptr().cast(), - in_size as i32, - buf.as_mut_ptr(), - 2, - ) - }; - if outsize > 0 { - fail_pos += in_size; - found = true; - break; - } - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); - if err_code == 1004 && flags_s != 0 { - flags_s = 0; - continue; + match host_windows::multi_byte_to_wide( + code_page, + flags_s, + &data[fail_pos..fail_pos + in_size], + &mut buf, + ) { + Ok(_outsize) => { + fail_pos += in_size; + found = true; + break; + } + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if err_code == host_windows::ERROR_INVALID_FLAGS_I32 && flags_s != 0 { + flags_s = 0; + continue; + } + in_size += 1; + if err_code != host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 + && err_code != host_windows::ERROR_INSUFFICIENT_BUFFER_I32 + { + break; + } + } } - in_size += 1; } if !found { break; @@ -1222,46 +1042,46 @@ mod _codecs_windows { let mut wide_buf: Vec = Vec::new(); let mut pos = 0usize; - let mut flags: u32 = MB_ERR_INVALID_CHARS; + let mut flags: u32 = host_windows::MB_ERR_INVALID_CHARS; while pos < len { // Try to decode with increasing byte counts (1, 2, 3, 4) let mut in_size = 1; - let mut outsize; + let outsize; let mut buffer = [0u16; 2]; loop { - outsize = unsafe { - MultiByteToWideChar( - code_page, - flags, - data[pos..].as_ptr().cast(), - in_size as i32, - buffer.as_mut_ptr(), - 2, - ) - }; - if outsize > 0 { - break; - } - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); - if err_code == 1004 && flags != 0 { - // ERROR_INVALID_FLAGS - retry with flags=0 - flags = 0; - continue; - } - if err_code != 1113 && err_code != 122 { - // Not ERROR_NO_UNICODE_TRANSLATION and not ERROR_INSUFFICIENT_BUFFER - let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("code_page_decode: {err}"))); - } - in_size += 1; - if in_size > 4 || pos + in_size > len { - break; + match host_windows::multi_byte_to_wide( + code_page, + flags, + &data[pos..pos + in_size], + &mut buffer, + ) { + Ok(size) => { + outsize = size; + break; + } + Err(err) => { + let err_code = err.raw_os_error().unwrap_or(0); + if err_code == host_windows::ERROR_INVALID_FLAGS_I32 && flags != 0 { + flags = 0; + continue; + } + if err_code != host_windows::ERROR_NO_UNICODE_TRANSLATION_I32 + && err_code != host_windows::ERROR_INSUFFICIENT_BUFFER_I32 + { + return Err(vm.new_os_error(format!("code_page_decode: {err}"))); + } + in_size += 1; + if in_size > 4 || pos + in_size > len { + outsize = 0; + break; + } + } } } - if outsize <= 0 { + if outsize == 0 { // Can't decode this byte sequence if pos + in_size >= len && !is_final { // Incomplete sequence at end, not final - stop here @@ -1348,7 +1168,7 @@ mod _codecs_windows { } } else { // Successfully decoded - wide_buf.extend_from_slice(&buffer[..outsize as usize]); + wide_buf.extend_from_slice(&buffer[..outsize]); pos += in_size; } } @@ -1374,23 +1194,24 @@ mod _codecs_windows { let is_final = args.r#final; if data.is_empty() { - return Ok((vm.ctx.empty_str.to_owned(), 0)); + return Ok((vm.ctx.new_str(""), 0)); } let encoding_name = code_page_encoding_name(code_page); - // Fast path: try to decode the whole buffer with strict flags - match try_decode_code_page_strict(code_page, &data, vm)? { - Some(wide) => { - let s = Wtf8Buf::from_wide(&wide); - return Ok((vm.ctx.new_str(s), data.len())); - } - None => { - // Decode error - fall through to slow path - } + // Fast path: try decoding the whole buffer at once + if let Some(wide) = try_decode_code_page_strict(code_page, data.as_ref(), vm)? { + let s = Wtf8Buf::from_wide(&wide); + return Ok((vm.ctx.new_str(s), data.len())); } - // Slow path: byte by byte with error handling - decode_code_page_errors(code_page, &data, errors, is_final, &encoding_name, vm) + decode_code_page_errors( + code_page, + data.as_ref(), + errors, + is_final, + &encoding_name, + vm, + ) } } diff --git a/crates/vm/src/stdlib/_ctypes.rs b/crates/vm/src/stdlib/_ctypes.rs index 1a3f454524a..cc87ebde572 100644 --- a/crates/vm/src/stdlib/_ctypes.rs +++ b/crates/vm/src/stdlib/_ctypes.rs @@ -3,7 +3,6 @@ mod array; mod base; mod function; -mod library; mod pointer; mod simple; mod structure; @@ -15,12 +14,6 @@ use crate::{ class::PyClassImpl, types::TypeDataRef, }; -use core::ffi::{ - c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, - c_ulonglong, c_ushort, -}; -use core::mem; -use widestring::WideChar; pub(super) use array::PyCArray; pub(super) use base::{FfiArgValue, PyCData, PyCField, StgInfo, StgInfoFlags}; @@ -97,145 +90,8 @@ pub(crate) use _ctypes::module_def; // These check if an object's type's metaclass is a subclass of a specific metaclass -/// Size of long double - platform dependent -/// x86_64 macOS/Linux: 16 bytes (80-bit extended + padding) -/// ARM64: 16 bytes (128-bit) -/// Windows: 8 bytes (same as double) -#[cfg(all( - any(target_arch = "x86_64", target_arch = "aarch64"), - not(target_os = "windows") -))] -const LONG_DOUBLE_SIZE: usize = 16; - -#[cfg(target_os = "windows")] -const LONG_DOUBLE_SIZE: usize = mem::size_of::(); - -#[cfg(not(any( - all( - any(target_arch = "x86_64", target_arch = "aarch64"), - not(target_os = "windows") - ), - target_os = "windows" -)))] -const LONG_DOUBLE_SIZE: usize = mem::size_of::(); - -/// Type information for ctypes simple types -struct TypeInfo { - pub size: usize, - pub ffi_type_fn: fn() -> libffi::middle::Type, -} - -/// Get type information (size and ffi_type) for a ctypes type code -fn type_info(ty: &str) -> Option { - use libffi::middle::Type; - match ty { - "c" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u8, - }), - "u" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: if mem::size_of::() == 2 { - Type::u16 - } else { - Type::u32 - }, - }), - "b" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::i8, - }), - "B" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u8, - }), - "h" | "v" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::i16, - }), - "H" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u16, - }), - "i" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::i32, - }), - "I" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u32, - }), - "l" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: if mem::size_of::() == 8 { - Type::i64 - } else { - Type::i32 - }, - }), - "L" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: if mem::size_of::() == 8 { - Type::u64 - } else { - Type::u32 - }, - }), - "q" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::i64, - }), - "Q" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u64, - }), - "f" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::f32, - }), - "d" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::f64, - }), - "g" => Some(TypeInfo { - // long double - platform dependent size - // x86_64 macOS/Linux: 16 bytes (80-bit extended + padding) - // ARM64: 16 bytes (128-bit) - // Windows: 8 bytes (same as double) - // Note: Use f64 as FFI type since Rust doesn't support long double natively - size: LONG_DOUBLE_SIZE, - ffi_type_fn: Type::f64, - }), - "?" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::u8, - }), - "z" | "Z" | "P" | "X" | "O" => Some(TypeInfo { - size: mem::size_of::(), - ffi_type_fn: Type::pointer, - }), - "void" => Some(TypeInfo { - size: 0, - ffi_type_fn: Type::void, - }), - _ => None, - } -} - -/// Get size for a ctypes type code -fn get_size(ty: &str) -> usize { - type_info(ty).map(|t| t.size).expect("invalid type code") -} - -/// Get alignment for simple type codes from type_info(). -/// For primitive C types (c_int, c_long, etc.), alignment equals size. -fn get_align(ty: &str) -> usize { - get_size(ty) -} - #[pymodule] pub(crate) mod _ctypes { - use super::library; use super::{PyCArray, PyCData, PyCPointer, PyCSimple, PyCStructure, PyCUnion}; use crate::builtins::{PyType, PyTypeRef}; use crate::class::StaticType; @@ -280,10 +136,16 @@ pub(crate) mod _ctypes { b'b' | b'h' | b'i' | b'l' | b'q' => { // Signed integers let n = match zelf.value { - FfiArgValue::I8(v) => v as i64, - FfiArgValue::I16(v) => v as i64, - FfiArgValue::I32(v) => v as i64, - FfiArgValue::I64(v) => v, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::I8(v)) => { + v as i64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::I16(v)) => { + v as i64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::I32(v)) => { + v as i64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::I64(v)) => v, _ => 0, }; Ok(format!("")) @@ -291,25 +153,35 @@ pub(crate) mod _ctypes { b'B' | b'H' | b'I' | b'L' | b'Q' => { // Unsigned integers let n = match zelf.value { - FfiArgValue::U8(v) => v as u64, - FfiArgValue::U16(v) => v as u64, - FfiArgValue::U32(v) => v as u64, - FfiArgValue::U64(v) => v, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::U8(v)) => { + v as u64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::U16(v)) => { + v as u64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::U32(v)) => { + v as u64 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::U64(v)) => v, _ => 0, }; Ok(format!("")) } b'f' => { let v = match zelf.value { - FfiArgValue::F32(v) => v as f64, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::F32(v)) => { + v as f64 + } _ => 0.0, }; Ok(format!("")) } b'd' | b'g' => { let v = match zelf.value { - FfiArgValue::F64(v) => v, - FfiArgValue::F32(v) => v as f64, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::F64(v)) => v, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::F32(v)) => { + v as f64 + } _ => 0.0, }; Ok(format!("")) @@ -317,8 +189,10 @@ pub(crate) mod _ctypes { b'c' => { // c_char - single byte let byte = match zelf.value { - FfiArgValue::I8(v) => v as u8, - FfiArgValue::U8(v) => v, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::I8(v)) => { + v as u8 + } + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::U8(v)) => v, _ => 0, }; if is_literal_char(byte) { @@ -330,7 +204,8 @@ pub(crate) mod _ctypes { b'z' | b'Z' | b'P' | b'V' => { // Pointer types let ptr = match zelf.value { - FfiArgValue::Pointer(v) => v, + FfiArgValue::Scalar(rustpython_host_env::ctypes::FfiValue::Pointer(v)) => v, + FfiArgValue::OwnedPointer(v, _) => v, _ => 0, }; if ptr == 0 { @@ -365,14 +240,14 @@ pub(crate) mod _ctypes { // TODO: get properly #[pyattr] - const RTLD_LOCAL: i32 = 0; + const RTLD_LOCAL: i32 = rustpython_host_env::ctypes::RTLD_LOCAL; // TODO: get properly #[pyattr] - const RTLD_GLOBAL: i32 = 0; + const RTLD_GLOBAL: i32 = rustpython_host_env::ctypes::RTLD_GLOBAL; #[pyattr] - const SIZEOF_TIME_T: usize = core::mem::size_of::(); + const SIZEOF_TIME_T: usize = rustpython_host_env::ctypes::SIZEOF_TIME_T; #[pyattr] const CTYPES_MAX_ARGCOUNT: usize = 1024; @@ -518,13 +393,16 @@ pub(crate) mod _ctypes { if let Ok(type_attr) = type_obj.as_object().get_attr("_type_", vm) && let Ok(type_str) = type_attr.str(vm) { - return Ok(super::get_size(type_str.as_ref())); + return Ok( + rustpython_host_env::ctypes::simple_type_size(type_str.as_ref()) + .expect("invalid ctypes simple type"), + ); } - return Ok(core::mem::size_of::()); + return Ok(rustpython_host_env::ctypes::pointer_size()); } // Pointer types if type_obj.fast_issubclass(PyCPointer::static_type()) { - return Ok(core::mem::size_of::()); + return Ok(rustpython_host_env::ctypes::pointer_size()); } return Err(vm.new_type_error("this type has no size")); } @@ -535,7 +413,7 @@ pub(crate) mod _ctypes { return Ok(cdata.size()); } if obj.fast_isinstance(PyCPointer::static_type()) { - return Ok(core::mem::size_of::()); + return Ok(rustpython_host_env::ctypes::pointer_size()); } Err(vm.new_type_error("this type has no size")) @@ -546,14 +424,11 @@ pub(crate) mod _ctypes { fn load_library_windows( name: String, _load_flags: OptionalArg, - vm: &VirtualMachine, + _vm: &VirtualMachine, ) -> usize { // TODO: audit functions first // TODO: load_flags - let cache = library::libcache(); - let mut cache_write = cache.write(); - let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap(); - id + rustpython_host_env::ctypes::open_library(&name).unwrap() } #[cfg(not(windows))] @@ -563,58 +438,38 @@ pub(crate) mod _ctypes { load_flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // Default mode: RTLD_NOW | RTLD_LOCAL, always force RTLD_NOW - let mode = load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW; + let mode = rustpython_host_env::ctypes::dlopen_mode(load_flags.into_option()); match name { Some(name) => { - let cache = library::libcache(); - let mut cache_write = cache.write(); let os_str = name.as_os_str(vm)?; - let (id, _) = cache_write - .get_or_insert_lib_with_mode(&*os_str, mode, vm) - .map_err(|e| { - let name_str = os_str.to_string_lossy(); - vm.new_os_error(format!("{name_str}: {e}")) - })?; - Ok(id) + rustpython_host_env::ctypes::open_library_with_mode(&*os_str, mode).map_err(|e| { + let name_str = os_str.to_string_lossy(); + vm.new_os_error(format!("{name_str}: {e}")) + }) } None => { // dlopen(NULL, mode) to get the current process handle (for pythonapi) - let handle = unsafe { libc::dlopen(core::ptr::null(), mode) }; - if handle.is_null() { - let err = unsafe { libc::dlerror() }; - let msg = if err.is_null() { - "dlopen() error" - } else { - unsafe { &core::ffi::CStr::from_ptr(err).to_string_lossy() } - }; - return Err(vm.new_os_error(msg)); - } + let handle = rustpython_host_env::ctypes::dlopen_self(mode) + .map_err(|msg| vm.new_os_error(msg))?; // Add to library cache so symbol lookup works - let cache = library::libcache(); - let mut cache_write = cache.write(); - let id = cache_write.insert_raw_handle(handle); - Ok(id) + Ok(rustpython_host_env::ctypes::insert_raw_library_handle( + handle, + )) } } } #[pyfunction(name = "FreeLibrary")] fn free_library(handle: usize) { - let cache = library::libcache(); - let mut cache_write = cache.write(); - cache_write.drop_lib(handle); + rustpython_host_env::ctypes::drop_library(handle); } #[cfg(not(windows))] #[pyfunction] fn dlclose(handle: usize, _vm: &VirtualMachine) { - // Remove from cache, which triggers SharedLibrary drop. - // libloading::Library calls dlclose automatically on Drop. - let cache = library::libcache(); - let mut cache_write = cache.write(); - cache_write.drop_lib(handle); + // Remove from the host_env cache. The underlying library is closed on Drop. + rustpython_host_env::ctypes::drop_library(handle); } #[cfg(not(windows))] @@ -626,29 +481,8 @@ pub(crate) mod _ctypes { ) -> PyResult { let symbol_name = alloc::ffi::CString::new(name.as_str()) .map_err(|_| vm.new_value_error("symbol name contains null byte"))?; - - // Clear previous error - unsafe { libc::dlerror() }; - - let ptr = unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) }; - - // Check for error via dlerror first - let err = unsafe { libc::dlerror() }; - if !err.is_null() { - let msg = unsafe { - core::ffi::CStr::from_ptr(err) - .to_string_lossy() - .into_owned() - }; - return Err(vm.new_os_error(msg)); - } - - // Treat NULL symbol address as error - // This handles cases like GNU IFUNCs that resolve to NULL - if ptr.is_null() { - return Err(vm.new_os_error(format!("symbol '{}' not found", name.as_str()))); - } - + let ptr = rustpython_host_env::ctypes::dlsym_checked(handle, symbol_name.as_c_str()) + .map_err(|msg| vm.new_os_error(msg))?; Ok(ptr as usize) } @@ -784,10 +618,10 @@ pub(crate) mod _ctypes { // Get buffer address: (char *)((CDataObject *)obj)->b_ptr + offset let ptr_val = if let Some(simple) = obj.downcast_ref::() { let buffer = simple.0.buffer.read(); - (buffer.as_ptr() as isize + offset_val) as usize + rustpython_host_env::ctypes::offset_address(buffer.as_ptr() as usize, offset_val) } else if let Some(cdata) = obj.downcast_ref::() { let buffer = cdata.buffer.read(); - (buffer.as_ptr() as isize + offset_val) as usize + rustpython_host_env::ctypes::offset_address(buffer.as_ptr() as usize, offset_val) } else { 0 }; @@ -795,7 +629,7 @@ pub(crate) mod _ctypes { // Create CArgObject to hold the reference Ok(CArgObject { tag: b'P', - value: FfiArgValue::Pointer(ptr_val), + value: FfiArgValue::pointer(ptr_val), obj, size: 0, offset: offset_val, @@ -866,7 +700,8 @@ pub(crate) mod _ctypes { if let Ok(s) = type_attr.str(vm) { let ty = s.to_string(); if ty.len() == 1 && super::simple::SIMPLE_TYPE_CHARS.contains(ty.as_str()) { - return Ok(super::get_align(&ty)); + return Ok(rustpython_host_env::ctypes::simple_type_align(&ty) + .expect("invalid ctypes simple type")); } } } @@ -937,46 +772,43 @@ pub(crate) mod _ctypes { let new_size = size as usize; let mut buffer = cdata.buffer.write(); let old_data = buffer.to_vec(); - let mut new_data = vec![0u8; new_size]; - let copy_len = old_data.len().min(new_size); - new_data[..copy_len].copy_from_slice(&old_data[..copy_len]); - *buffer = Cow::Owned(new_data); + *buffer = Cow::Owned(rustpython_host_env::ctypes::resize_owned_bytes( + &old_data, new_size, + )); Ok(()) } #[pyfunction] fn get_errno() -> i32 { - super::function::get_errno_value() + rustpython_host_env::ctypes::get_errno() } #[pyfunction] fn set_errno(value: i32) -> i32 { - super::function::set_errno_value(value) + rustpython_host_env::ctypes::set_errno(value) } #[cfg(windows)] #[pyfunction] fn get_last_error() -> u32 { - super::function::get_last_error_value() + rustpython_host_env::ctypes::get_last_error() } #[cfg(windows)] #[pyfunction] fn set_last_error(value: u32) -> u32 { - super::function::set_last_error_value(value) + rustpython_host_env::ctypes::set_last_error(value) } #[pyattr] fn _memmove_addr(_vm: &VirtualMachine) -> usize { - let f = libc::memmove; - f as *const () as usize + rustpython_host_env::ctypes::memmove_addr() } #[pyattr] fn _memset_addr(_vm: &VirtualMachine) -> usize { - let f = libc::memset; - f as *const () as usize + rustpython_host_env::ctypes::memset_addr() } #[pyattr] @@ -1092,32 +924,24 @@ pub(crate) mod _ctypes { _flags: u32, vm: &VirtualMachine, ) -> PyResult { - use libffi::middle::{Arg, Cif, CodePtr, Type}; - if func_addr == 0 { return Err(vm.new_value_error("NULL function pointer")); } - let mut ffi_args: Vec> = Vec::with_capacity(args.len()); - let mut arg_values: Vec = Vec::with_capacity(args.len()); - let mut arg_types: Vec = Vec::with_capacity(args.len()); + let mut call_args = Vec::with_capacity(args.len()); for arg in args.iter() { if vm.is_none(arg) { - arg_values.push(0); - arg_types.push(Type::pointer()); + call_args.push(rustpython_host_env::ctypes::CdeclArgValue::Pointer(0)); } else if let Ok(int_val) = arg.try_int(vm) { let val = int_val.as_bigint().to_i64().unwrap_or(0) as isize; - arg_values.push(val); - arg_types.push(Type::isize()); + call_args.push(rustpython_host_env::ctypes::CdeclArgValue::Int(val)); } else if let Some(bytes) = arg.downcast_ref::() { let ptr = bytes.as_bytes().as_ptr() as isize; - arg_values.push(ptr); - arg_types.push(Type::pointer()); + call_args.push(rustpython_host_env::ctypes::CdeclArgValue::Pointer(ptr)); } else if let Some(s) = arg.downcast_ref::() { let ptr = s.as_bytes().as_ptr() as isize; - arg_values.push(ptr); - arg_types.push(Type::pointer()); + call_args.push(rustpython_host_env::ctypes::CdeclArgValue::Pointer(ptr)); } else { return Err(vm.new_type_error(format!( "Don't know how to convert parameter of type '{}'", @@ -1126,13 +950,7 @@ pub(crate) mod _ctypes { } } - for val in &arg_values { - ffi_args.push(Arg::new(val)); - } - - let cif = Cif::new(arg_types, Type::c_int()); - let code_ptr = CodePtr::from_ptr(func_addr as *const _); - let result: libc::c_int = unsafe { cif.call(code_ptr, &ffi_args) }; + let result = rustpython_host_env::ctypes::call_cdecl_i32_values(func_addr, &call_args); Ok(vm.ctx.new_int(result).into()) } @@ -1168,83 +986,41 @@ pub(crate) mod _ctypes { path: Option, vm: &VirtualMachine, ) -> PyResult { - use alloc::ffi::CString; - let path = match path { Some(p) if !vm.is_none(&p) => p, _ => return Ok(false), }; let path_str = path.str(vm)?.to_string(); - let c_path = - CString::new(path_str).map_err(|_| vm.new_value_error("path contains null byte"))?; - - unsafe extern "C" { - fn _dyld_shared_cache_contains_path(path: *const libc::c_char) -> bool; - } - - let result = unsafe { _dyld_shared_cache_contains_path(c_path.as_ptr()) }; - Ok(result) + rustpython_host_env::ctypes::dyld_shared_cache_contains_path(&path_str) + .map_err(|_| vm.new_value_error("path contains null byte")) } #[cfg(windows)] #[pyfunction(name = "FormatError")] fn format_error_func(code: OptionalArg, _vm: &VirtualMachine) -> String { - use windows_sys::Win32::Foundation::{GetLastError, LocalFree}; - use windows_sys::Win32::System::Diagnostics::Debug::{ - FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, - FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, - }; - - let error_code = code.unwrap_or_else(|| unsafe { GetLastError() }); - - let mut buffer: *mut u16 = core::ptr::null_mut(); - let len = unsafe { - FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS, - core::ptr::null(), - error_code, - 0, - &mut buffer as *mut *mut u16 as *mut u16, - 0, - core::ptr::null(), - ) - }; - - if len == 0 || buffer.is_null() { - return "".to_string(); - } - - unsafe { - let slice = core::slice::from_raw_parts(buffer, len as usize); - let msg = String::from_utf16_lossy(slice).trim_end().to_string(); - LocalFree(buffer as *mut _); - msg - } + rustpython_host_env::ctypes::format_error_message(code.into_option()) + .unwrap_or_else(|| "".to_string()) } #[cfg(windows)] #[pyfunction(name = "CopyComPointer")] fn copy_com_pointer(src: PyObjectRef, dst: PyObjectRef, vm: &VirtualMachine) -> i32 { - use windows_sys::Win32::Foundation::{E_POINTER, S_OK}; - // 1. Extract pointer-to-pointer address from dst (byref() result) let pdst: usize = if let Some(carg) = dst.downcast_ref::() { // byref() result: object buffer address + offset let base = if let Some(cdata) = carg.obj.downcast_ref::() { cdata.buffer.read().as_ptr() as usize } else { - return E_POINTER; + return rustpython_host_env::ctypes::HRESULT_E_POINTER; }; (base as isize + carg.offset) as usize } else { - return E_POINTER; + return rustpython_host_env::ctypes::HRESULT_E_POINTER; }; if pdst == 0 { - return E_POINTER; + return rustpython_host_env::ctypes::HRESULT_E_POINTER; } // 2. Extract COM pointer value from src @@ -1253,38 +1029,12 @@ pub(crate) mod _ctypes { } else if let Some(cdata) = src.downcast_ref::() { // c_void_p etc: read pointer value from buffer let buffer = cdata.buffer.read(); - if buffer.len() >= core::mem::size_of::() { - usize::from_ne_bytes( - buffer[..core::mem::size_of::()] - .try_into() - .unwrap_or([0; core::mem::size_of::()]), - ) - } else { - 0 - } + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) } else { - return E_POINTER; + return rustpython_host_env::ctypes::HRESULT_E_POINTER; }; - // 3. Call IUnknown::AddRef if src is non-NULL - if src_ptr != 0 { - unsafe { - // IUnknown vtable: [QueryInterface, AddRef, Release, ...] - let iunknown = src_ptr as *mut *const usize; - let vtable = *iunknown; - debug_assert!(!vtable.is_null(), "IUnknown vtable is null"); - let addref_fn: extern "system" fn(*mut core::ffi::c_void) -> u32 = - core::mem::transmute(*vtable.add(1)); // AddRef is index 1 - addref_fn(src_ptr as *mut core::ffi::c_void); - } - } - - // 4. Copy pointer: *pdst = src - unsafe { - *(pdst as *mut usize) = src_ptr; - } - - S_OK + rustpython_host_env::ctypes::copy_com_pointer(src_ptr, pdst) } #[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")] diff --git a/crates/vm/src/stdlib/_ctypes/array.rs b/crates/vm/src/stdlib/_ctypes/array.rs index 4c7f8708679..f7abc834564 100644 --- a/crates/vm/src/stdlib/_ctypes/array.rs +++ b/crates/vm/src/stdlib/_ctypes/array.rs @@ -1,6 +1,5 @@ use super::StgInfo; use super::base::{CDATA_BUFFER_METHODS, PyCData}; -use super::type_info; use crate::common::lock::LazyLock; use crate::sliceable::SaturatedSliceIter; use crate::{ @@ -17,6 +16,13 @@ use crate::{ use alloc::borrow::Cow; use num_traits::{Signed, ToPrimitive}; +use rustpython_host_env::ctypes::{ + ArrayElementWriteValue, DecodedValue, WCHAR_SIZE, WCharArrayWriteError, char_array_field_value, + int_to_sized_bytes, read_array_element, simple_type_size, uint_to_sized_bytes, + wchar_from_bytes, write_array_element, write_char_array_raw, write_char_array_value, + write_wchar_array_value, wstring_from_bytes, zeroed_bytes, +}; + /// Get itemsize from a PEP 3118 format string /// Extracts the type code (last char after endianness prefix) and returns its size fn get_size_from_format(fmt: &str) -> usize { @@ -26,7 +32,7 @@ fn get_size_from_format(fmt: &str) -> usize { .chars() .next() .map(|c| c.to_string()); - code.map_or(1, |c| type_info(&c).map_or(1, |t| t.size)) + code.map_or(1, |c| simple_type_size(&c).unwrap_or(1)) } /// Creates array type for (element_type, length) @@ -520,72 +526,19 @@ impl PyCArray { vec![i.to_i8().unwrap_or(0) as u8] } } - 2 => { - if let Some(v) = i.to_u16() { - v.to_ne_bytes().to_vec() - } else { - i.to_i16().unwrap_or(0).to_ne_bytes().to_vec() - } - } - 4 => { - if let Some(v) = i.to_u32() { - v.to_ne_bytes().to_vec() - } else { - i.to_i32().unwrap_or(0).to_ne_bytes().to_vec() - } - } - 8 => { - if let Some(v) = i.to_u64() { - v.to_ne_bytes().to_vec() - } else { - i.to_i64().unwrap_or(0).to_ne_bytes().to_vec() - } - } - _ => vec![0u8; size], - } - } - - fn bytes_to_int( - bytes: &[u8], - size: usize, - type_code: Option<&str>, - vm: &VirtualMachine, - ) -> PyObjectRef { - // Unsigned type codes: B (uchar), H (ushort), I (uint), L (ulong), Q (ulonglong) - let is_unsigned = matches!(type_code, Some("B" | "H" | "I" | "L" | "Q")); - - match (size, is_unsigned) { - (1, false) => vm.ctx.new_int(bytes[0] as i8).into(), - (1, true) => vm.ctx.new_int(bytes[0]).into(), - (2, false) => { - let val = i16::from_ne_bytes([bytes[0], bytes[1]]); - vm.ctx.new_int(val).into() - } - (2, true) => { - let val = u16::from_ne_bytes([bytes[0], bytes[1]]); - vm.ctx.new_int(val).into() - } - (4, false) => { - let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - vm.ctx.new_int(val).into() - } - (4, true) => { - let val = u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - vm.ctx.new_int(val).into() - } - (8, false) => { - let val = i64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - vm.ctx.new_int(val).into() - } - (8, true) => { - let val = u64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - vm.ctx.new_int(val).into() - } - _ => vm.ctx.new_int(0).into(), + 2 => i.to_u16().map_or_else( + || int_to_sized_bytes(i.to_i16().unwrap_or(0).into(), 2), + |v| uint_to_sized_bytes(v.into(), 2), + ), + 4 => i.to_u32().map_or_else( + || int_to_sized_bytes(i.to_i32().unwrap_or(0).into(), 4), + |v| uint_to_sized_bytes(v.into(), 4), + ), + 8 => i.to_u64().map_or_else( + || int_to_sized_bytes(i.to_i64().unwrap_or(0), 8), + |v| uint_to_sized_bytes(v, 8), + ), + _ => zeroed_bytes(size), } } @@ -630,120 +583,15 @@ impl PyCArray { type_code: Option<&str>, vm: &VirtualMachine, ) -> PyObjectRef { - match type_code { - Some("c") => { - // Return single byte as bytes - if offset < buffer.len() { - vm.ctx.new_bytes(vec![buffer[offset]]).into() - } else { - vm.ctx.new_bytes(vec![0]).into() - } - } - Some("u") => { - // Return single wchar as str - if let Some(code) = wchar_from_bytes(&buffer[offset..]) { - let s = char::from_u32(code) - .map(|c| c.to_string()) - .unwrap_or_default(); - vm.ctx.new_str(s).into() - } else { - vm.ctx.new_str("").into() - } - } - Some("z") => { - // c_char_p: pointer to bytes - dereference to get string - if offset + element_size > buffer.len() { - return vm.ctx.none(); - } - - let ptr_bytes = &buffer[offset..offset + element_size]; - let ptr_val = usize::from_ne_bytes( - ptr_bytes - .try_into() - .unwrap_or([0; core::mem::size_of::()]), - ); - - if ptr_val == 0 { - return vm.ctx.none(); - } - - // Read null-terminated string from pointer address - unsafe { - let ptr = ptr_val as *const u8; - let mut len = 0; - while *ptr.add(len) != 0 { - len += 1; - } - let bytes = core::slice::from_raw_parts(ptr, len); - vm.ctx.new_bytes(bytes.to_vec()).into() - } - } - Some("Z") => { - // c_wchar_p: pointer to wchar_t - dereference to get string - if offset + element_size > buffer.len() { - return vm.ctx.none(); - } - - let ptr_bytes = &buffer[offset..offset + element_size]; - let ptr_val = usize::from_ne_bytes( - ptr_bytes - .try_into() - .unwrap_or([0; core::mem::size_of::()]), - ); - - if ptr_val == 0 { - return vm.ctx.none(); - } - - // Read null-terminated wide string using WCHAR_SIZE - unsafe { - let ptr = ptr_val as *const u8; - let mut chars = Vec::new(); - let mut pos = 0usize; - loop { - let code = if WCHAR_SIZE == 2 { - let bytes = core::slice::from_raw_parts(ptr.add(pos), 2); - u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 - } else { - let bytes = core::slice::from_raw_parts(ptr.add(pos), 4); - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - }; - if code == 0 { - break; - } - if let Some(ch) = char::from_u32(code) { - chars.push(ch); - } - pos += WCHAR_SIZE; - } - - let s: String = chars.into_iter().collect(); - vm.ctx.new_str(s).into() - } - } - Some("f") => { - // c_float - let val = buffer[offset..] - .first_chunk::<4>() - .copied() - .map_or(0.0, f32::from_ne_bytes); - vm.ctx.new_float(val as f64).into() - } - Some("d" | "g") => { - // c_double / c_longdouble - read f64 from first 8 bytes - let val = buffer[offset..] - .first_chunk::<8>() - .copied() - .map_or(0.0, f64::from_ne_bytes); - vm.ctx.new_float(val).into() - } - _ => { - if let Some(bytes) = buffer[offset..].get(..element_size) { - Self::bytes_to_int(bytes, element_size, type_code, vm) - } else { - vm.ctx.new_int(0).into() - } - } + match read_array_element(buffer, offset, element_size, type_code) { + DecodedValue::Bytes(bytes) => vm.ctx.new_bytes(bytes).into(), + DecodedValue::String(value) => vm.ctx.new_str(value).into(), + DecodedValue::Float(value) => vm.ctx.new_float(value).into(), + DecodedValue::Signed(value) => vm.ctx.new_int(value).into(), + DecodedValue::Unsigned(value) => vm.ctx.new_int(value).into(), + DecodedValue::None => vm.ctx.none(), + DecodedValue::Pointer(value) => vm.ctx.new_int(value).into(), + DecodedValue::Bool(value) => vm.ctx.new_bool(value).into(), } } @@ -763,13 +611,17 @@ impl PyCArray { match type_code { Some("c") => { if let Some(b) = value.downcast_ref::() { - if offset < buffer.len() { - buffer[offset] = b.as_bytes().first().copied().unwrap_or(0); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Byte(b.as_bytes().first().copied().unwrap_or(0)), + ); } else if let Ok(int_val) = value.try_int(vm) { - if offset < buffer.len() { - buffer[offset] = int_val.as_bigint().to_u8().unwrap_or(0); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Byte(int_val.as_bigint().to_u8().unwrap_or(0)), + ); } else { return Err(vm.new_type_error("an integer or bytes of length 1 is required")); } @@ -777,9 +629,7 @@ impl PyCArray { Some("u") => { if let Some(s) = value.downcast_ref::() { let code = s.as_wtf8().code_points().next().map_or(0, |c| c.to_u32()); - if offset + WCHAR_SIZE <= buffer.len() { - wchar_to_bytes(code, &mut buffer[offset..]); - } + write_array_element(buffer, offset, ArrayElementWriteValue::Wchar(code)); } else { return Err(vm.new_type_error("unicode string expected")); } @@ -799,9 +649,14 @@ impl PyCArray { value.class().name() ))); }; - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&ptr_val.to_ne_bytes()); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Pointer { + value: ptr_val, + size: element_size, + }, + ); if let Some(c) = converted { return zelf.0.keep_ref(index, c, vm); } @@ -817,9 +672,14 @@ impl PyCArray { } else { return Err(vm.new_type_error("unicode string or integer address expected")); }; - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&ptr_val.to_ne_bytes()); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Pointer { + value: ptr_val, + size: element_size, + }, + ); if let Some(c) = converted { return zelf.0.keep_ref(index, c, vm); } @@ -833,9 +693,14 @@ impl PyCArray { } else { return Err(vm.new_type_error("a float is required")); }; - if offset + 4 <= buffer.len() { - buffer[offset..offset + 4].copy_from_slice(&f32_val.to_ne_bytes()); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Float { + value: f32_val.into(), + size: 4, + }, + ); } Some("d" | "g") => { // c_double / c_longdouble: convert int/float to f64 bytes @@ -846,25 +711,39 @@ impl PyCArray { } else { return Err(vm.new_type_error("a float is required")); }; - if offset + 8 <= buffer.len() { - buffer[offset..offset + 8].copy_from_slice(&f64_val.to_ne_bytes()); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Float { + value: f64_val, + size: 8, + }, + ); // For "g" type, remaining bytes stay zero } _ => { // Handle ctypes instances (copy their buffer) if let Some(cdata) = value.downcast_ref::() { let src_buffer = cdata.buffer.read(); - let copy_len = src_buffer.len().min(element_size); - if offset + copy_len <= buffer.len() { - buffer[offset..offset + copy_len].copy_from_slice(&src_buffer[..copy_len]); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Bytes { + bytes: &src_buffer, + size: element_size, + }, + ); // Other types: use int_to_bytes } else if let Ok(int_val) = value.try_int(vm) { let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&bytes); - } + write_array_element( + buffer, + offset, + ArrayElementWriteValue::Bytes { + bytes: &bytes, + size: element_size, + }, + ); } else { return Err(vm.new_type_error(format!( "expected {} instance, not {}", @@ -920,9 +799,8 @@ impl PyCArray { Cow::Borrowed(slice) => { // SAFETY: For from_buffer, the slice points to writable shared memory. // Python's from_buffer requires writable buffer, so this is safe. - let ptr = slice.as_ptr() as *mut u8; - let len = slice.len(); - let owned_slice = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; + let owned_slice = + unsafe { rustpython_host_env::ctypes::borrowed_slice_as_mut(slice) }; Self::write_element_to_buffer( owned_slice, final_offset, @@ -1179,8 +1057,9 @@ impl AsBuffer for PyCArray { fn char_array_get_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let zelf = obj.downcast_ref::().unwrap(); let buffer = zelf.0.buffer.read(); - let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len()); - vm.ctx.new_bytes(buffer[..len].to_vec()).into() + vm.ctx + .new_bytes(char_array_field_value(&buffer).to_vec()) + .into() } // CharArray_set_value @@ -1196,10 +1075,7 @@ fn char_array_set_value(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachin return Err(vm.new_value_error("byte string too long")); } - buffer.to_mut()[..src.len()].copy_from_slice(src); - if src.len() < buffer.len() { - buffer.to_mut()[src.len()] = 0; - } + write_char_array_value(buffer.to_mut(), src); Ok(()) } @@ -1224,7 +1100,7 @@ fn char_array_set_raw( if src.len() > buffer.len() { return Err(vm.new_value_error("byte string too long")); } - buffer.to_mut()[..src.len()].copy_from_slice(&src); + write_char_array_raw(buffer.to_mut(), &src); Ok(()) } @@ -1246,22 +1122,9 @@ fn wchar_array_set_value( .downcast_ref::() .ok_or_else(|| vm.new_type_error("unicode string expected"))?; let mut buffer = zelf.0.buffer.write(); - let wchar_count = buffer.len() / WCHAR_SIZE; - let char_count = s.as_wtf8().code_points().count(); - - if char_count > wchar_count { - return Err(vm.new_value_error("string too long")); - } - - for (i, ch) in s.as_wtf8().code_points().enumerate() { - let offset = i * WCHAR_SIZE; - wchar_to_bytes(ch.to_u32(), &mut buffer.to_mut()[offset..]); - } - - let terminator_offset = char_count * WCHAR_SIZE; - if terminator_offset + WCHAR_SIZE <= buffer.len() { - wchar_to_bytes(0, &mut buffer.to_mut()[terminator_offset..]); - } + write_wchar_array_value(buffer.to_mut(), s.as_wtf8()).map_err(|err| match err { + WCharArrayWriteError::TooLong => vm.new_value_error("string too long"), + })?; Ok(()) } @@ -1309,57 +1172,3 @@ fn add_wchar_array_getsets(array_type: &Py, vm: &VirtualMachine) { .write() .insert(vm.ctx.intern_str("value"), value_getset.into()); } - -// wchar_t helpers - Platform-independent wide character handling -// Windows: sizeof(wchar_t) == 2 (UTF-16) -// Linux/macOS: sizeof(wchar_t) == 4 (UTF-32) - -/// Size of wchar_t on this platform -pub(super) const WCHAR_SIZE: usize = core::mem::size_of::(); - -/// Read a single wchar_t from bytes (platform-endian) -#[inline] -pub(super) fn wchar_from_bytes(bytes: &[u8]) -> Option { - if bytes.len() < WCHAR_SIZE { - return None; - } - Some(if WCHAR_SIZE == 2 { - u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 - } else { - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - }) -} - -/// Write a single wchar_t to bytes (platform-endian) -#[inline] -pub(super) fn wchar_to_bytes(ch: u32, buffer: &mut [u8]) { - if WCHAR_SIZE == 2 { - if buffer.len() >= 2 { - buffer[..2].copy_from_slice(&(ch as u16).to_ne_bytes()); - } - } else if buffer.len() >= 4 { - buffer[..4].copy_from_slice(&ch.to_ne_bytes()); - } -} - -/// Read a null-terminated wchar_t string from bytes, returns String -fn wstring_from_bytes(buffer: &[u8]) -> String { - let mut chars = Vec::new(); - for chunk in buffer.chunks(WCHAR_SIZE) { - if chunk.len() < WCHAR_SIZE { - break; - } - let code = if WCHAR_SIZE == 2 { - u16::from_ne_bytes([chunk[0], chunk[1]]) as u32 - } else { - u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) - }; - if code == 0 { - break; // null terminator - } - if let Some(ch) = char::from_u32(code) { - chars.push(ch); - } - } - chars.into_iter().collect() -} diff --git a/crates/vm/src/stdlib/_ctypes/base.rs b/crates/vm/src/stdlib/_ctypes/base.rs index d70178be4e8..765bacd19a0 100644 --- a/crates/vm/src/stdlib/_ctypes/base.rs +++ b/crates/vm/src/stdlib/_ctypes/base.rs @@ -1,4 +1,5 @@ -use super::array::{WCHAR_SIZE, wchar_from_bytes, wchar_to_bytes}; +#![allow(unreachable_pub)] + use crate::builtins::{ PyBytes, PyDict, PyList, PyMemoryView, PyStr, PyTuple, PyType, PyTypeRef, PyUtf8Str, }; @@ -11,16 +12,15 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use alloc::borrow::Cow; -use core::ffi::{ - c_double, c_float, c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, -}; use core::fmt::Debug; -use core::mem; use crossbeam_utils::atomic::AtomicCell; use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; use rustpython_common::wtf8::Wtf8; -use widestring::WideChar; +use rustpython_host_env::ctypes::{ + CTypeParamKind, FfiArg, FfiType, FfiValue, char_array_assignment_bytes, char_array_field_value, + ffi_arg_from_value, ffi_type_for_layout, wchar_array_field_value, write_cow_bytes_at_offset, +}; // StgInfo - Storage information for ctypes types // Stored in TypeDataSlot of heap types (PyType::init_type_data/get_type_data) @@ -77,7 +77,7 @@ pub(super) enum ParamFunc { } #[derive(Clone)] -pub(crate) struct StgInfo { +pub struct StgInfo { pub initialized: bool, pub size: usize, // number of bytes pub align: usize, // alignment requirements @@ -100,7 +100,7 @@ pub(crate) struct StgInfo { pub big_endian: bool, // true if big endian, false if little endian // FFI field types for structure/union passing (inherited from base class) - pub ffi_field_types: Vec, + pub ffi_field_types: Vec, // Cached pointer type (non-inheritable via descriptor) pub pointer_type: Option, @@ -178,7 +178,7 @@ impl StgInfo { /// item_shape: the element's shape (will be prepended with length) /// item_flags: the element type's flags (for HASPOINTER inheritance) #[allow(clippy::too_many_arguments)] - pub(crate) fn new_array( + pub fn new_array( size: usize, align: usize, length: usize, @@ -227,78 +227,30 @@ impl StgInfo { /// Get libffi type for this StgInfo /// Note: For very large types, returns pointer type to avoid overflow - pub(crate) fn to_ffi_type(&self) -> libffi::middle::Type { - // Limit to avoid overflow in libffi (MAX_STRUCT_SIZE is platform-dependent) - const MAX_FFI_STRUCT_SIZE: usize = 1024 * 1024; // 1MB limit for safety - - match self.paramfunc { - ParamFunc::Structure | ParamFunc::Union => { - if !self.ffi_field_types.is_empty() { - libffi::middle::Type::structure(self.ffi_field_types.iter().cloned()) - } else if self.size <= MAX_FFI_STRUCT_SIZE { - // Small struct without field types: use bytes array - libffi::middle::Type::structure(core::iter::repeat_n( - libffi::middle::Type::u8(), - self.size, - )) - } else { - // Large struct: treat as pointer (passed by reference) - libffi::middle::Type::pointer() - } - } - ParamFunc::Array => { - if self.size > MAX_FFI_STRUCT_SIZE || self.length > MAX_FFI_STRUCT_SIZE { - // Large array: treat as pointer - libffi::middle::Type::pointer() - } else if let Some(ref fmt) = self.format { - let elem_type = Self::format_to_ffi_type(fmt); - libffi::middle::Type::structure(core::iter::repeat_n(elem_type, self.length)) - } else { - libffi::middle::Type::structure(core::iter::repeat_n( - libffi::middle::Type::u8(), - self.size, - )) - } - } - ParamFunc::Pointer => libffi::middle::Type::pointer(), - _ => { - // Simple type: derive from format - if let Some(ref fmt) = self.format { - Self::format_to_ffi_type(fmt) - } else { - libffi::middle::Type::u8() - } - } - } - } - - /// Convert format string to libffi type - fn format_to_ffi_type(fmt: &str) -> libffi::middle::Type { - // Strip endian prefix if present - let code = fmt.trim_start_matches(['<', '>', '!', '@', '=']); - match code { - "b" => libffi::middle::Type::i8(), - "B" => libffi::middle::Type::u8(), - "h" => libffi::middle::Type::i16(), - "H" => libffi::middle::Type::u16(), - "i" | "l" => libffi::middle::Type::i32(), - "I" | "L" => libffi::middle::Type::u32(), - "q" => libffi::middle::Type::i64(), - "Q" => libffi::middle::Type::u64(), - "f" => libffi::middle::Type::f32(), - "d" => libffi::middle::Type::f64(), - "P" | "z" | "Z" | "O" => libffi::middle::Type::pointer(), - _ => libffi::middle::Type::u8(), // default - } + pub fn to_ffi_type(&self) -> FfiType { + let kind = match self.paramfunc { + ParamFunc::Structure => CTypeParamKind::Structure, + ParamFunc::Union => CTypeParamKind::Union, + ParamFunc::Array => CTypeParamKind::Array, + ParamFunc::Pointer => CTypeParamKind::Pointer, + _ => CTypeParamKind::Simple, + }; + ffi_type_for_layout( + kind, + &self.ffi_field_types, + self.size, + self.length, + self.format.as_deref(), + ) } /// Check if this type is finalized (cannot set _fields_ again) - pub(crate) fn is_final(&self) -> bool { + pub fn is_final(&self) -> bool { self.flags.contains(StgInfoFlags::DICTFLAG_FINAL) } /// Get proto type reference (for Pointer/Array types) - pub(crate) fn proto(&self) -> &Py { + pub fn proto(&self) -> &Py { self.proto.as_deref().expect("type has proto") } } @@ -408,26 +360,13 @@ pub(super) static CDATA_BUFFER_METHODS: BufferMethods = BufferMethods { retain: |_| {}, }; -/// Convert Vec to Vec by reinterpreting the memory (same allocation). -fn vec_to_bytes(vec: Vec) -> Vec { - let len = vec.len() * core::mem::size_of::(); - let cap = vec.capacity() * core::mem::size_of::(); - let ptr = vec.as_ptr() as *mut u8; - core::mem::forget(vec); - unsafe { Vec::from_raw_parts(ptr, len, cap) } -} - /// Ensure PyBytes data is null-terminated. Returns (kept_alive_obj, pointer). /// The caller must keep the returned object alive to keep the pointer valid. pub(super) fn ensure_z_null_terminated( bytes: &PyBytes, vm: &VirtualMachine, ) -> (PyObjectRef, usize) { - let data = bytes.as_bytes(); - let mut buffer = data.to_vec(); - if !buffer.ends_with(&[0]) { - buffer.push(0); - } + let buffer = rustpython_host_env::ctypes::null_terminated_bytes(bytes.as_bytes()); let ptr = buffer.as_ptr() as usize; let kept_alive: PyObjectRef = vm.ctx.new_bytes(buffer).into(); (kept_alive, ptr) @@ -435,13 +374,8 @@ pub(super) fn ensure_z_null_terminated( /// Convert str to null-terminated wchar_t buffer. Returns (PyBytes holder, pointer). pub(super) fn str_to_wchar_bytes(s: &Wtf8, vm: &VirtualMachine) -> (PyObjectRef, usize) { - let wchars: Vec = s - .code_points() - .map(|cp| cp.to_u32() as libc::wchar_t) - .chain(core::iter::once(0)) - .collect(); - let ptr = wchars.as_ptr() as usize; - let bytes = vec_to_bytes(wchars); + let bytes = rustpython_host_env::ctypes::wchar_null_terminated_bytes(s); + let ptr = bytes.as_ptr() as usize; let holder: PyObjectRef = vm.ctx.new_bytes(bytes).into(); (holder, ptr) } @@ -449,7 +383,7 @@ pub(super) fn str_to_wchar_bytes(s: &Wtf8, vm: &VirtualMachine) -> (PyObjectRef, /// PyCData - base type for all ctypes data types #[pyclass(name = "_CData", module = "_ctypes")] #[derive(Debug, PyPayload)] -pub(crate) struct PyCData { +pub struct PyCData { /// Memory buffer - Owned (self-owned) or Borrowed (external reference) /// /// SAFETY: Borrowed variant's 'static lifetime is not actually static. @@ -501,7 +435,7 @@ impl PyCData { } /// Create from bytes with specified length (for arrays) - pub(crate) fn from_bytes_with_length( + pub fn from_bytes_with_length( data: Vec, objects: Option, length: usize, @@ -523,10 +457,10 @@ impl PyCData { /// The returned slice's 'static lifetime is a lie. /// Actually only valid for the lifetime of the memory pointed to by ptr. /// PyCData_AtAddress - pub(crate) unsafe fn at_address(ptr: *const u8, size: usize) -> Self { + pub unsafe fn at_address(ptr: *const u8, size: usize) -> Self { // = PyCData_AtAddress // SAFETY: Caller must ensure ptr is valid for the lifetime of returned PyCData - let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; + let slice = unsafe { rustpython_host_env::ctypes::borrow_memory(ptr, size) }; Self { buffer: PyRwLock::new(Cow::Borrowed(slice)), base: PyRwLock::new(None), @@ -543,7 +477,7 @@ impl PyCData { /// Similar to from_base_with_offset, but also stores a copy of the data. /// This is used for arrays where we need our own buffer for the buffer protocol, /// but still maintain the base reference for KeepRef and tracking. - pub(crate) fn from_base_with_data( + pub fn from_base_with_data( base_obj: PyObjectRef, offset: usize, idx: usize, @@ -568,7 +502,7 @@ impl PyCData { /// /// # Safety /// ptr must point into base_obj's buffer and remain valid as long as base_obj is alive. - pub(crate) unsafe fn from_base_obj( + pub unsafe fn from_base_obj( ptr: *mut u8, size: usize, base_obj: PyObjectRef, @@ -576,7 +510,7 @@ impl PyCData { ) -> Self { // = PyCData_FromBaseObj // SAFETY: ptr points into base_obj's buffer, kept alive via base reference - let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; + let slice = unsafe { rustpython_host_env::ctypes::borrow_memory(ptr, size) }; Self { buffer: PyRwLock::new(Cow::Borrowed(slice)), base: PyRwLock::new(Some(base_obj)), @@ -596,7 +530,7 @@ impl PyCData { /// /// # Safety /// ptr must point to valid memory that remains valid as long as source is alive. - pub(crate) unsafe fn from_buffer_shared( + pub unsafe fn from_buffer_shared( ptr: *const u8, size: usize, length: usize, @@ -604,7 +538,7 @@ impl PyCData { vm: &VirtualMachine, ) -> Self { // SAFETY: Caller must ensure ptr is valid for the lifetime of source - let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; + let slice = unsafe { rustpython_host_env::ctypes::borrow_memory(ptr, size) }; // Python stores the reference in a dict with key "-1" (unique_key pattern) let objects_dict = vm.ctx.new_dict(); @@ -627,7 +561,7 @@ impl PyCData { /// Validates buffer, creates memoryview, and returns PyCData sharing memory with source. /// /// CDataType_from_buffer_impl - pub(crate) fn from_buffer_impl( + pub fn from_buffer_impl( cls: &Py, source: PyObjectRef, offset: isize, @@ -687,7 +621,7 @@ impl PyCData { /// Copies data from buffer and creates new independent instance. /// /// CDataType_from_buffer_copy_impl - pub(crate) fn from_buffer_copy_impl( + pub fn from_buffer_copy_impl( cls: &Py, source: &[u8], offset: isize, @@ -721,13 +655,13 @@ impl PyCData { } #[inline] - pub(crate) fn size(&self) -> usize { + pub fn size(&self) -> usize { self.buffer.read().len() } /// Check if this buffer is borrowed (external memory reference) #[inline] - pub(crate) fn is_borrowed(&self) -> bool { + pub fn is_borrowed(&self) -> bool { matches!(&*self.buffer.read(), Cow::Borrowed(_)) } @@ -738,35 +672,15 @@ impl PyCData { /// /// # Safety /// For borrowed buffers, caller must ensure the memory is writable. - pub(crate) fn write_bytes_at_offset(&self, offset: usize, bytes: &[u8]) { - let buffer = self.buffer.read(); - if offset + bytes.len() > buffer.len() { - return; // Out of bounds - } - - match &*buffer { - Cow::Borrowed(slice) => { - // For borrowed memory, write directly - // SAFETY: We assume the caller knows this memory is writable - // (e.g., from from_address pointing to a ctypes buffer) - unsafe { - let ptr = slice.as_ptr() as *mut u8; - core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), bytes.len()); - } - } - Cow::Owned(_) => { - // For owned memory, use to_mut() through write lock - drop(buffer); - let mut buffer = self.buffer.write(); - buffer.to_mut()[offset..offset + bytes.len()].copy_from_slice(bytes); - } - } + pub fn write_bytes_at_offset(&self, offset: usize, bytes: &[u8]) { + let mut buffer = self.buffer.write(); + write_cow_bytes_at_offset(&mut buffer, offset, bytes); } /// Generate unique key for nested references (unique_key) /// Creates a hierarchical key by walking up the b_base chain. /// Format: "index:parent_index:grandparent_index:..." - pub(crate) fn unique_key(&self, index: usize) -> String { + pub fn unique_key(&self, index: usize) -> String { let mut key = format!("{index:x}"); // Walk up the base chain to build hierarchical key if self.base.read().is_some() { @@ -785,12 +699,7 @@ impl PyCData { /// /// If this object has a base (is embedded in another structure/union/array), /// the reference is stored in the root object's b_objects with a hierarchical key. - pub(crate) fn keep_ref( - &self, - index: usize, - keep: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { + pub fn keep_ref(&self, index: usize, keep: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { // Optimization: no need to store None if vm.is_none(&keep) { return Ok(()); @@ -850,7 +759,7 @@ impl PyCData { /// Walks up to root object (same as keep_ref) so the reference /// lives as long as the owning ctypes object. /// Uses unique_key (hierarchical) so nested fields don't collide. - pub(crate) fn keep_alive(&self, index: usize, obj: PyObjectRef) { + pub fn keep_alive(&self, index: usize, obj: PyObjectRef) { let key = self.unique_key(index); if let Some(base_obj) = self.base.read().clone() { let root = Self::find_root_object(&base_obj); @@ -923,7 +832,7 @@ impl PyCData { /// Get kept objects from a CData instance /// Returns the _objects of the CData, or an empty dict if None. - pub(crate) fn get_kept_objects(value: &PyObject, vm: &VirtualMachine) -> PyObjectRef { + pub fn get_kept_objects(value: &PyObject, vm: &VirtualMachine) -> PyObjectRef { value .downcast_ref::() .and_then(|cdata| cdata.objects.read().clone()) @@ -939,7 +848,7 @@ impl PyCData { /// PyCData_set /// Sets a field value at the given offset, handling type conversion and KeepRef #[allow(clippy::too_many_arguments)] - pub(crate) fn set_field( + pub fn set_field( &self, proto: &PyObject, value: PyObjectRef, @@ -957,7 +866,7 @@ impl PyCData { if is_char_array { if let Some(bytes_val) = value.downcast_ref::() { let src = bytes_val.as_bytes(); - let to_copy = PyCField::bytes_for_char_array(src); + let to_copy = char_array_assignment_bytes(src); let copy_len = core::cmp::min(to_copy.len(), size); self.write_bytes_at_offset(offset, &to_copy[..copy_len]); self.keep_ref(index, value, vm)?; @@ -969,17 +878,10 @@ impl PyCData { // For c_wchar arrays with str input, convert to wchar_t if is_wchar_array { if let Some(str_val) = value.downcast_ref::() { - // Convert str to wchar_t bytes (platform-dependent size) - let mut wchar_bytes = Vec::with_capacity(size); - for cp in str_val.as_wtf8().code_points().take(size / WCHAR_SIZE) { - let mut bytes = [0u8; 4]; - wchar_to_bytes(cp.to_u32(), &mut bytes); - wchar_bytes.extend_from_slice(&bytes[..WCHAR_SIZE]); - } - // Pad with nulls to fill the array - while wchar_bytes.len() < size { - wchar_bytes.push(0); - } + let wchar_bytes = rustpython_host_env::ctypes::encode_wtf8_to_wchar_padded( + str_val.as_wtf8(), + size, + ); self.write_bytes_at_offset(offset, &wchar_bytes); self.keep_ref(index, value, vm)?; return Ok(()); @@ -999,9 +901,8 @@ impl PyCData { let array_buffer = array.0.buffer.read(); array_buffer.as_ptr() as usize }; - let addr_bytes = buffer_addr.to_ne_bytes(); - let len = core::cmp::min(addr_bytes.len(), size); - self.write_bytes_at_offset(offset, &addr_bytes[..len]); + let addr_bytes = rustpython_host_env::ctypes::pointer_to_sized_bytes(buffer_addr, size); + self.write_bytes_at_offset(offset, &addr_bytes); self.keep_ref(index, value, vm)?; return Ok(()); } @@ -1055,13 +956,8 @@ impl PyCData { && let Some(bytes_val) = value.downcast_ref::() { let (kept_alive, ptr) = ensure_z_null_terminated(bytes_val, vm); - let mut result = vec![0u8; size]; - let addr_bytes = ptr.to_ne_bytes(); - let len = core::cmp::min(addr_bytes.len(), size); - result[..len].copy_from_slice(&addr_bytes[..len]); - if needs_swap { - result.reverse(); - } + let result = + rustpython_host_env::ctypes::pointer_to_sized_bytes_endian(ptr, size, needs_swap); self.write_bytes_at_offset(offset, &result); self.keep_ref(index, value, vm)?; self.keep_alive(index, kept_alive); @@ -1094,7 +990,7 @@ impl PyCData { /// PyCData_get /// Gets a field value at the given offset - pub(crate) fn get_field( + pub fn get_field( &self, proto: &PyObject, index: usize, @@ -1117,24 +1013,16 @@ impl PyCData { // c_char array → return bytes if PyCField::is_char_array(proto, vm) { let data = &buffer[offset..offset + size]; - // Find first null terminator (or use full length) - let end = data.iter().position(|&b| b == 0).unwrap_or(data.len()); - return Ok(vm.ctx.new_bytes(data[..end].to_vec()).into()); + return Ok(vm + .ctx + .new_bytes(char_array_field_value(data).to_vec()) + .into()); } // c_wchar array → return str if PyCField::is_wchar_array(proto, vm) { let data = &buffer[offset..offset + size]; - // wchar_t → char conversion, skip null - let chars: String = data - .chunks(WCHAR_SIZE) - .filter_map(|chunk| { - wchar_from_bytes(chunk) - .filter(|&wchar| wchar != 0) - .and_then(char::from_u32) - }) - .collect(); - return Ok(vm.ctx.new_str(chars).into()); + return Ok(vm.ctx.new_str(wchar_array_field_value(data)).into()); } // Other array types - create array with a copy of data from the base's buffer @@ -1186,7 +1074,7 @@ impl PyCData { buffer_data }; - return bytes_to_pyobject(&proto_type, &data, vm); + return Ok(bytes_to_pyobject(&proto_type, &data, vm)); } // Complex types: create ctypes instance via PyCData_FromBaseObj @@ -1299,24 +1187,21 @@ impl PyCData { .ok_or_else(|| vm.new_value_error("Invalid library handle"))? }; - // Look up the library in the cache and use lib.get() for symbol lookup - let library_cache = super::library::libcache().read(); - let library = library_cache - .get_lib(handle) - .ok_or_else(|| vm.new_value_error("Library not found"))?; - let inner_lib = library.lib.lock(); - let symbol_name_with_nul = format!("{}\0", name.as_wtf8()); - let ptr: *const u8 = if let Some(lib) = &*inner_lib { - unsafe { - lib.get::<*const u8>(symbol_name_with_nul.as_bytes()) - .map(|sym| *sym) - .map_err(|_| { - vm.new_value_error(format!("symbol '{}' not found", name.as_wtf8())) - })? + let ptr = match rustpython_host_env::ctypes::lookup_data_symbol_addr( + handle, + symbol_name_with_nul.as_bytes(), + ) { + Ok(ptr) => ptr as *const u8, + Err(rustpython_host_env::ctypes::LookupSymbolError::LibraryNotFound) => { + return Err(vm.new_value_error("Library not found")); + } + Err(rustpython_host_env::ctypes::LookupSymbolError::LibraryClosed) => { + return Err(vm.new_value_error("Library closed")); + } + Err(rustpython_host_env::ctypes::LookupSymbolError::Load(_)) => { + return Err(vm.new_value_error(format!("symbol '{}' not found", name.as_wtf8()))); } - } else { - return Err(vm.new_value_error("Library closed")); }; // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) @@ -1336,7 +1221,7 @@ impl PyCData { /// CField descriptor for Structure/Union field access #[pyclass(name = "CField", module = "_ctypes")] #[derive(Debug, PyPayload)] -pub(crate) struct PyCField { +pub struct PyCField { /// Field name pub(crate) name: String, /// Byte offset of the field within the structure/union @@ -1357,7 +1242,7 @@ pub(crate) struct PyCField { impl PyCField { /// Create a new CField descriptor (non-bitfield) - pub(crate) fn new( + pub fn new( name: String, proto: PyTypeRef, offset: isize, @@ -1377,7 +1262,7 @@ impl PyCField { } /// Create a new CField descriptor for a bitfield - pub(crate) fn new_bitfield( + pub fn new_bitfield( name: String, proto: PyTypeRef, offset: isize, @@ -1399,7 +1284,7 @@ impl PyCField { } /// Get the byte size of the field's underlying type - pub(crate) fn get_byte_size(&self) -> usize { + pub fn get_byte_size(&self) -> usize { self.byte_size_val as usize } @@ -1419,7 +1304,7 @@ impl PyCField { } /// Set anonymous flag - pub(crate) fn set_anonymous(&mut self, anonymous: bool) { + pub fn set_anonymous(&mut self, anonymous: bool) { self.anonymous = anonymous; } } @@ -1584,29 +1469,19 @@ impl PyCField { fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> Vec { // 1. Handle bytes objects if let Some(bytes) = value.downcast_ref::() { - let src = bytes.as_bytes(); - let mut result = vec![0u8; size]; - let len = core::cmp::min(src.len(), size); - result[..len].copy_from_slice(&src[..len]); - result + rustpython_host_env::ctypes::copy_to_sized_bytes(bytes.as_bytes(), size) } // 2. Handle ctypes array instances (copy their buffer) else if let Some(cdata) = value.downcast_ref::() { let buffer = cdata.buffer.read(); - let mut result = vec![0u8; size]; - let len = core::cmp::min(buffer.len(), size); - result[..len].copy_from_slice(&buffer[..len]); - result + rustpython_host_env::ctypes::copy_to_sized_bytes(&buffer, size) } // 4. Handle float values (check before int, since float.try_int would truncate) else if let Some(float_val) = value.downcast_ref::() { let f = float_val.to_f64(); match size { - 4 => { - let val = f as f32; - val.to_ne_bytes().to_vec() - } - 8 => f.to_ne_bytes().to_vec(), + 4 | 8 => rustpython_host_env::ctypes::float_to_sized_bytes(f, size) + .expect("float size checked"), _ => unreachable!("wrong payload size"), } } @@ -1614,26 +1489,23 @@ impl PyCField { else if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); match size { - 1 => { - let val = i.to_i8().unwrap_or(0); - val.to_ne_bytes().to_vec() - } - 2 => { - let val = i.to_i16().unwrap_or(0); - val.to_ne_bytes().to_vec() - } - 4 => { - let val = i.to_i32().unwrap_or(0); - val.to_ne_bytes().to_vec() - } - 8 => { - let val = i.to_i64().unwrap_or(0); - val.to_ne_bytes().to_vec() - } - _ => vec![0u8; size], + 1 => rustpython_host_env::ctypes::int_to_sized_bytes( + i.to_i8().unwrap_or(0).into(), + size, + ), + 2 => rustpython_host_env::ctypes::int_to_sized_bytes( + i.to_i16().unwrap_or(0).into(), + size, + ), + 4 => rustpython_host_env::ctypes::int_to_sized_bytes( + i.to_i32().unwrap_or(0).into(), + size, + ), + 8 => rustpython_host_env::ctypes::int_to_sized_bytes(i.to_i64().unwrap_or(0), size), + _ => rustpython_host_env::ctypes::zeroed_bytes(size), } } else { - vec![0u8; size] + rustpython_host_env::ctypes::zeroed_bytes(size) } } @@ -1658,8 +1530,11 @@ impl PyCField { value.class().name() ))); }; - let val = f as f32; - Ok((val.to_ne_bytes().to_vec(), None)) + Ok(( + rustpython_host_env::ctypes::float_to_sized_bytes(f, 4) + .expect("c_float size is fixed"), + None, + )) } // c_double: always convert to float first (d_set) "d" => { @@ -1673,7 +1548,11 @@ impl PyCField { value.class().name() ))); }; - Ok((f.to_ne_bytes().to_vec(), None)) + Ok(( + rustpython_host_env::ctypes::float_to_sized_bytes(f, 8) + .expect("c_double size is fixed"), + None, + )) } // c_longdouble: convert to float (treated as f64 in RustPython) "g" => { @@ -1687,7 +1566,11 @@ impl PyCField { value.class().name() ))); }; - Ok((f.to_ne_bytes().to_vec(), None)) + Ok(( + rustpython_host_env::ctypes::float_to_sized_bytes(f, 8) + .expect("c_longdouble bytes are stored as f64"), + None, + )) } "z" => { // c_char_p with bytes is handled in set_field before this call. @@ -1695,15 +1578,14 @@ impl PyCField { // Integer address if let Ok(int_val) = value.try_index(vm) { let v = int_val.as_bigint().to_usize().unwrap_or(0); - let mut result = vec![0u8; size]; - let bytes = v.to_ne_bytes(); - let len = core::cmp::min(bytes.len(), size); - result[..len].copy_from_slice(&bytes[..len]); - return Ok((result, None)); + return Ok(( + rustpython_host_env::ctypes::pointer_to_sized_bytes(v, size), + None, + )); } // None -> NULL pointer if vm.is_none(value) { - return Ok((vec![0u8; size], None)); + return Ok((rustpython_host_env::ctypes::zeroed_bytes(size), None)); } Ok((Self::value_to_bytes(value, size, vm), None)) } @@ -1711,24 +1593,22 @@ impl PyCField { // c_wchar_p: store pointer to null-terminated wchar_t buffer if let Some(s) = value.downcast_ref::() { let (holder, ptr) = str_to_wchar_bytes(s.as_wtf8(), vm); - let mut result = vec![0u8; size]; - let addr_bytes = ptr.to_ne_bytes(); - let len = core::cmp::min(addr_bytes.len(), size); - result[..len].copy_from_slice(&addr_bytes[..len]); - return Ok((result, Some(holder))); + return Ok(( + rustpython_host_env::ctypes::pointer_to_sized_bytes(ptr, size), + Some(holder), + )); } // Integer address if let Ok(int_val) = value.try_index(vm) { let v = int_val.as_bigint().to_usize().unwrap_or(0); - let mut result = vec![0u8; size]; - let bytes = v.to_ne_bytes(); - let len = core::cmp::min(bytes.len(), size); - result[..len].copy_from_slice(&bytes[..len]); - return Ok((result, None)); + return Ok(( + rustpython_host_env::ctypes::pointer_to_sized_bytes(v, size), + None, + )); } // None -> NULL pointer if vm.is_none(value) { - return Ok((vec![0u8; size], None)); + return Ok((rustpython_host_env::ctypes::zeroed_bytes(size), None)); } Ok((Self::value_to_bytes(value, size, vm), None)) } @@ -1736,15 +1616,14 @@ impl PyCField { // c_void_p: store integer as pointer if let Ok(int_val) = value.try_index(vm) { let v = int_val.as_bigint().to_usize().unwrap_or(0); - let mut result = vec![0u8; size]; - let bytes = v.to_ne_bytes(); - let len = core::cmp::min(bytes.len(), size); - result[..len].copy_from_slice(&bytes[..len]); - return Ok((result, None)); + return Ok(( + rustpython_host_env::ctypes::pointer_to_sized_bytes(v, size), + None, + )); } // None -> NULL pointer if vm.is_none(value) { - return Ok((vec![0u8; size], None)); + return Ok((rustpython_host_env::ctypes::zeroed_bytes(size), None)); } Ok((Self::value_to_bytes(value, size, vm), None)) } @@ -1785,17 +1664,6 @@ impl PyCField { } false } - - /// Convert bytes for c_char array assignment (stops at first null terminator) - /// Returns (bytes_to_copy, copy_len) - fn bytes_for_char_array(src: &[u8]) -> &[u8] { - // Find first null terminator and include it - if let Some(null_pos) = src.iter().position(|&b| b == 0) { - &src[..=null_pos] - } else { - src - } - } } #[pyclass(flags(IMMUTABLETYPE), with(Representable, GetDescriptor, Constructor))] @@ -1987,7 +1855,7 @@ fn array_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult Ok(CArgObject { tag: b'P', - value: FfiArgValue::Pointer(ptr_val), + value: FfiArgValue::pointer(ptr_val), obj: obj.to_owned(), size: 0, offset: 0, @@ -2007,7 +1875,7 @@ fn pointer_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult Self { + Self::Scalar(FfiValue::Pointer(value)) + } + /// Create an Arg reference to this owned value - pub(crate) fn as_arg(&self) -> libffi::middle::Arg<'_> { + pub fn as_arg(&self) -> FfiArg<'_> { match self { - Self::U8(v) => libffi::middle::Arg::new(v), - Self::I8(v) => libffi::middle::Arg::new(v), - Self::U16(v) => libffi::middle::Arg::new(v), - Self::I16(v) => libffi::middle::Arg::new(v), - Self::U32(v) => libffi::middle::Arg::new(v), - Self::I32(v) => libffi::middle::Arg::new(v), - Self::U64(v) => libffi::middle::Arg::new(v), - Self::I64(v) => libffi::middle::Arg::new(v), - Self::F32(v) => libffi::middle::Arg::new(v), - Self::F64(v) => libffi::middle::Arg::new(v), - Self::Pointer(v) => libffi::middle::Arg::new(v), - Self::OwnedPointer(v, _) => libffi::middle::Arg::new(v), + Self::Scalar(value) => ffi_arg_from_value(value), + Self::OwnedPointer(v, _) => rustpython_host_env::ctypes::ffi_arg( + rustpython_host_env::ctypes::FfiArgRef::Pointer(v), + ), } } } /// Convert buffer bytes to FfiArgValue based on type code pub(super) fn buffer_to_ffi_value(type_code: &str, buffer: &[u8]) -> FfiArgValue { - match type_code { - "c" | "b" => { - let v = buffer.first().map_or(0, |&b| b as i8); - FfiArgValue::I8(v) - } - "B" => { - let v = buffer.first().copied().unwrap_or(0); - FfiArgValue::U8(v) - } - "h" => { - let v = buffer.first_chunk().copied().map_or(0, i16::from_ne_bytes); - FfiArgValue::I16(v) - } - "H" => { - let v = buffer.first_chunk().copied().map_or(0, u16::from_ne_bytes); - FfiArgValue::U16(v) - } - "i" => { - let v = buffer.first_chunk().copied().map_or(0, i32::from_ne_bytes); - FfiArgValue::I32(v) - } - "I" => { - let v = buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes); - FfiArgValue::U32(v) - } - "l" | "q" => { - let v = if let Some(&bytes) = buffer.first_chunk::<8>() { - i64::from_ne_bytes(bytes) - } else if let Some(&bytes) = buffer.first_chunk::<4>() { - i32::from_ne_bytes(bytes).into() - } else { - 0 - }; - FfiArgValue::I64(v) - } - "L" | "Q" => { - let v = if let Some(&bytes) = buffer.first_chunk::<8>() { - u64::from_ne_bytes(bytes) - } else if let Some(&bytes) = buffer.first_chunk::<4>() { - u32::from_ne_bytes(bytes).into() - } else { - 0 - }; - FfiArgValue::U64(v) - } - "f" => { - let v = buffer - .first_chunk::<4>() - .copied() - .map_or(0.0, f32::from_ne_bytes); - FfiArgValue::F32(v) - } - "d" | "g" => { - let v = buffer - .first_chunk::<8>() - .copied() - .map_or(0.0, f64::from_ne_bytes); - FfiArgValue::F64(v) - } - "z" | "Z" | "P" | "O" => FfiArgValue::Pointer(read_ptr_from_buffer(buffer)), - "?" => { - let v = buffer.first().is_some_and(|&b| b != 0); - FfiArgValue::U8(if v { 1 } else { 0 }) - } - "u" => { - // wchar_t - 4 bytes on most platforms - let v = buffer.first_chunk().copied().map_or(0, u32::from_ne_bytes); - FfiArgValue::U32(v) - } - _ => FfiArgValue::Pointer(0), - } + FfiArgValue::Scalar(rustpython_host_env::ctypes::ffi_value_from_type_code( + type_code, buffer, + )) } /// Convert bytes to appropriate Python object based on ctypes type @@ -2163,204 +1949,41 @@ pub(super) fn bytes_to_pyobject( cls: &Py, bytes: &[u8], vm: &VirtualMachine, -) -> PyResult { +) -> PyObjectRef { // Try to get _type_ attribute if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) && let Ok(s) = type_attr.str(vm) { let ty = s.to_string(); - return match ty.as_str() { - "c" => Ok(vm.ctx.new_bytes(bytes.to_vec()).into()), - "b" => { - let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "B" => { - let val = if !bytes.is_empty() { bytes[0] } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "h" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "H" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "i" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "I" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) + return match rustpython_host_env::ctypes::decode_type_code(ty.as_str(), bytes) { + rustpython_host_env::ctypes::DecodedValue::Bytes(value) => { + vm.ctx.new_bytes(value).into() } - "l" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "L" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) + rustpython_host_env::ctypes::DecodedValue::Signed(value) => { + vm.ctx.new_int(value).into() } - "q" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) + rustpython_host_env::ctypes::DecodedValue::Unsigned(value) => { + vm.ctx.new_int(value).into() } - "Q" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) + rustpython_host_env::ctypes::DecodedValue::Float(value) => { + vm.ctx.new_float(value).into() } - "f" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + rustpython_host_env::ctypes::DecodedValue::Bool(value) => vm.ctx.new_bool(value).into(), + rustpython_host_env::ctypes::DecodedValue::Pointer(value) => { + if value == 0 { + vm.ctx.none() } else { - 0.0 - }; - Ok(vm.ctx.new_float(val as f64).into()) - } - "d" => { - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val).into()) - } - "g" => { - // long double - read as f64 for now since Rust doesn't have native long double - // This may lose precision on platforms where long double > 64 bits - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val).into()) - } - "?" => { - let val = !bytes.is_empty() && bytes[0] != 0; - Ok(vm.ctx.new_bool(val).into()) - } - "v" => { - // VARIANT_BOOL: non-zero = True, zero = False - const SIZE: usize = mem::size_of::(); - let val = if bytes.len() >= SIZE { - c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_bool(val != 0).into()) - } - "z" => { - // c_char_p: read NULL-terminated string from pointer - let ptr = read_ptr_from_buffer(bytes); - if ptr == 0 { - return Ok(vm.ctx.none()); + vm.ctx.new_int(value).into() } - let c_str = unsafe { core::ffi::CStr::from_ptr(ptr as _) }; - Ok(vm.ctx.new_bytes(c_str.to_bytes().to_vec()).into()) } - "Z" => { - // c_wchar_p: read NULL-terminated wide string from pointer - let ptr = read_ptr_from_buffer(bytes); - if ptr == 0 { - return Ok(vm.ctx.none()); - } - let len = unsafe { libc::wcslen(ptr as *const libc::wchar_t) }; - let wchars = - unsafe { core::slice::from_raw_parts(ptr as *const libc::wchar_t, len) }; - // wchar_t is i32 on some platforms and u32 on others - #[allow( - clippy::unnecessary_cast, - reason = "wchar_t is i32 on some platforms and u32 on others" - )] - let s: String = wchars - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - Ok(vm.ctx.new_str(s).into()) - } - "P" => { - // c_void_p: return pointer value as integer - let val = read_ptr_from_buffer(bytes); - if val == 0 { - return Ok(vm.ctx.none()); - } - Ok(vm.ctx.new_int(val).into()) - } - "O" => { - // py_object: return Python object from pointer - let ptr = read_ptr_from_buffer(bytes); - if ptr == 0 { - return Err(vm.new_value_error("PyObject is NULL")); - } - unsafe { - let obj = - PyObjectRef::from_raw(core::ptr::NonNull::new_unchecked(ptr as *mut _)); - Ok(obj) - } + rustpython_host_env::ctypes::DecodedValue::String(value) => { + vm.ctx.new_str(value).into() } - "u" => { - let val = if bytes.len() >= mem::size_of::() { - let wc = if mem::size_of::() == 2 { - u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 - } else { - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - }; - char::from_u32(wc).unwrap_or('\0') - } else { - '\0' - }; - Ok(vm.ctx.new_str(val).into()) - } - _ => Ok(vm.ctx.none()), + rustpython_host_env::ctypes::DecodedValue::None => vm.ctx.none(), }; } // Default: return bytes as-is - Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + vm.ctx.new_bytes(bytes.to_vec()).into() } // Shared functions for Structure and Union types @@ -2385,16 +2008,6 @@ pub(super) fn get_usize_attr( Ok(val.to_usize().unwrap_or(default)) } -/// Read a pointer value from buffer -#[inline] -pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { - const PTR_SIZE: usize = core::mem::size_of::(); - buffer - .first_chunk::() - .copied() - .map_or(0, usize::from_ne_bytes) -} - /// Check if a type is a "simple instance" (direct subclass of a simple type) /// Returns TRUE for c_int, c_void_p, etc. (simple types with _type_ attribute) /// Returns FALSE for Structure, Array, POINTER(T), etc. @@ -2469,8 +2082,9 @@ pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> usiz .and_then(|type_attr| type_attr.str(vm).ok()) .and_then(|type_str| { let s = type_str.to_string(); - (s.len() == 1).then(|| super::get_size(&s)) + (s.len() == 1).then(|| rustpython_host_env::ctypes::simple_type_size(&s)) }) + .flatten() { return size; } @@ -2485,7 +2099,7 @@ pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> usiz return s; } - core::mem::size_of::() + rustpython_host_env::ctypes::pointer_size() } /// Get the alignment of a ctypes field type @@ -2503,8 +2117,9 @@ pub(super) fn get_field_align(field_type: &PyObject, vm: &VirtualMachine) -> usi .and_then(|type_attr| type_attr.str(vm).ok()) .and_then(|type_str| { let s = type_str.to_string(); - (s.len() == 1).then(|| super::get_size(&s)) + (s.len() == 1).then(|| rustpython_host_env::ctypes::simple_type_align(&s)) }) + .flatten() { return align; } diff --git a/crates/vm/src/stdlib/_ctypes/function.rs b/crates/vm/src/stdlib/_ctypes/function.rs index e188a67d8c3..25cbcdcd9a1 100644 --- a/crates/vm/src/stdlib/_ctypes/function.rs +++ b/crates/vm/src/stdlib/_ctypes/function.rs @@ -1,11 +1,11 @@ // spell-checker:disable +#![allow(unreachable_pub)] use super::{ _ctypes::CArgObject, PyCArray, PyCData, PyCPointer, PyCStructure, StgInfo, base::{CDATA_BUFFER_METHODS, FfiArgValue, ParamFunc, StgInfoFlags}, simple::PyCSimple, - type_info, }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, @@ -19,13 +19,17 @@ use crate::{ use alloc::borrow::Cow; use core::ffi::c_void; use core::fmt::Debug; -use libffi::{ - low, - middle::{Arg, Cif, Closure, CodePtr, Type}, -}; -use libloading::Symbol; use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; +#[cfg(windows)] +use rustpython_host_env::ctypes::ComMethodError; +use rustpython_host_env::ctypes::{ + CallResult as RawResult, FfiCif, FfiCodePtr, FfiType, FfiValue, RawMemoryView, + RawMemoryViewError, StringAtError, ffi_f64_type, ffi_i32_type, ffi_pointer_type, + ffi_type_for_return_size, ffi_type_from_code, ffi_type_from_tag, ffi_void_type, + has_pointer_width, null_code_ptr, offset_address, pointer_bytes, pointer_format, pointer_size, + write_pointer_to_buffer_at, write_prefix_limited, +}; // Internal function addresses for special ctypes functions pub(super) const INTERNAL_CAST_ADDR: usize = 1; @@ -33,138 +37,8 @@ pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2; pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3; pub(super) const INTERNAL_MEMORYVIEW_AT_ADDR: usize = 4; -// Thread-local errno storage for ctypes -std::thread_local! { - /// Thread-local storage for ctypes errno - /// This is separate from the system errno - ctypes swaps them during FFI calls - /// when use_errno=True is specified. - static CTYPES_LOCAL_ERRNO: core::cell::Cell = const { core::cell::Cell::new(0) }; -} - -/// Get ctypes thread-local errno value -pub(super) fn get_errno_value() -> i32 { - CTYPES_LOCAL_ERRNO.with(|e| e.get()) -} - -/// Set ctypes thread-local errno value, returns old value -pub(super) fn set_errno_value(value: i32) -> i32 { - CTYPES_LOCAL_ERRNO.with(|e| { - let old = e.get(); - e.set(value); - old - }) -} - -/// Save and restore errno around FFI call (called when use_errno=True) -/// Before: restore thread-local errno to system -/// After: save system errno to thread-local -#[cfg(not(windows))] -fn swap_errno(f: F) -> R -where - F: FnOnce() -> R, -{ - // Before call: restore thread-local errno to system - let saved = CTYPES_LOCAL_ERRNO.with(|e| e.get()); - errno::set_errno(errno::Errno(saved)); - - // Call the function - let result = f(); - - // After call: save system errno to thread-local - let new_error = errno::errno().0; - CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error)); - - result -} - -#[cfg(windows)] -std::thread_local! { - /// Thread-local storage for ctypes last_error (Windows only) - static CTYPES_LOCAL_LAST_ERROR: core::cell::Cell = const { core::cell::Cell::new(0) }; -} - -#[cfg(windows)] -pub(super) fn get_last_error_value() -> u32 { - CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()) -} - -#[cfg(windows)] -pub(super) fn set_last_error_value(value: u32) -> u32 { - CTYPES_LOCAL_LAST_ERROR.with(|e| { - let old = e.get(); - e.set(value); - old - }) -} - -/// Save and restore last_error around FFI call (called when use_last_error=True) -#[cfg(windows)] -fn save_and_restore_last_error(f: F) -> R -where - F: FnOnce() -> R, -{ - // Before call: restore thread-local last_error to Windows - let saved = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()); - unsafe { windows_sys::Win32::Foundation::SetLastError(saved) }; - - // Call the function - let result = f(); - - // After call: save Windows last_error to thread-local - let new_error = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error)); - - result -} - -type FP = unsafe extern "C" fn(); - -/// Get FFI type for a ctypes type code -fn get_ffi_type(ty: &str) -> Option { - type_info(ty).map(|t| (t.ffi_type_fn)()) -} - // PyCFuncPtr - Function pointer implementation -/// Get FFI type from CArgObject tag character -fn ffi_type_from_tag(tag: u8) -> Type { - match tag { - b'c' | b'b' => Type::i8(), - b'B' => Type::u8(), - b'h' => Type::i16(), - b'H' => Type::u16(), - b'i' => Type::i32(), - b'I' => Type::u32(), - b'l' => { - if core::mem::size_of::() == 8 { - Type::i64() - } else { - Type::i32() - } - } - b'L' => { - if core::mem::size_of::() == 8 { - Type::u64() - } else { - Type::u32() - } - } - b'q' => Type::i64(), - b'Q' => Type::u64(), - b'f' => Type::f32(), - b'd' | b'g' => Type::f64(), - b'?' => Type::u8(), - b'u' => { - if core::mem::size_of::() == 2 { - Type::u16() - } else { - Type::u32() - } - } - _ => Type::pointer(), // 'P', 'V', 'z', 'Z', 'O', etc. - } -} - /// Convert any object to a pointer value for c_void_p arguments /// Follows ConvParam logic for pointer types fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult { @@ -180,44 +54,44 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult NULL if value.is(&vm.ctx.none) { - return Ok(FfiArgValue::Pointer(0)); + return Ok(FfiArgValue::pointer(0)); } // 2. PyCArray -> buffer address (PyCArrayType_paramfunc) if let Some(array) = value.downcast_ref::() { let addr = array.0.buffer.read().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + return Ok(FfiArgValue::pointer(addr)); } // 3. PyCPointer -> stored pointer value if let Some(ptr) = value.downcast_ref::() { - return Ok(FfiArgValue::Pointer(ptr.get_ptr_value())); + return Ok(FfiArgValue::pointer(ptr.get_ptr_value())); } // 4. PyCStructure -> buffer address if let Some(struct_obj) = value.downcast_ref::() { let addr = struct_obj.0.buffer.read().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + return Ok(FfiArgValue::pointer(addr)); } // 5. PyCSimple (c_void_p, c_char_p, etc.) -> value from buffer if let Some(simple) = value.downcast_ref::() { let buffer = simple.0.buffer.read(); - if buffer.len() >= core::mem::size_of::() { - let addr = super::base::read_ptr_from_buffer(&buffer); - return Ok(FfiArgValue::Pointer(addr)); + if has_pointer_width(&buffer) { + let addr = rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer); + return Ok(FfiArgValue::pointer(addr)); } } // 6. bytes -> buffer address (PyBytes_AsString) if let Some(bytes) = value.downcast_ref::() { let addr = bytes.as_bytes().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + return Ok(FfiArgValue::pointer(addr)); } // 7. Integer -> direct value (PyLong_AsVoidPtr behavior) @@ -226,10 +100,10 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult PyResult { // 2. None -> NULL pointer if value.is(&vm.ctx.none) { return Ok(Argument { - ffi_type: Type::pointer(), + ffi_type: ffi_pointer_type(), keep: None, - value: FfiArgValue::Pointer(0), + value: FfiArgValue::pointer(0), }); } @@ -280,33 +154,26 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult { // 4. Python str -> wide string pointer (like PyUnicode_AsWideCharString) if let Some(s) = value.downcast_ref::() { - // Convert to null-terminated UTF-16, preserving lone surrogates - let wide: Vec = s - .as_wtf8() - .encode_wide() - .chain(core::iter::once(0)) - .collect(); - let wide_bytes: Vec = wide.iter().flat_map(|&x| x.to_ne_bytes()).collect(); + let wide_bytes = rustpython_host_env::ctypes::utf16z_bytes(s.as_wtf8()); let keep = vm.ctx.new_bytes(wide_bytes); let addr = keep.as_bytes().as_ptr() as usize; return Ok(Argument { - ffi_type: Type::pointer(), + ffi_type: ffi_pointer_type(), keep: Some(keep.into()), - value: FfiArgValue::Pointer(addr), + value: FfiArgValue::pointer(addr), }); } // 9. Python bytes -> null-terminated buffer pointer // Need to ensure null termination like c_char_p if let Some(bytes) = value.downcast_ref::() { - let mut buffer = bytes.as_bytes().to_vec(); - buffer.push(0); // Add null terminator + let buffer = rustpython_host_env::ctypes::null_terminated_bytes(bytes.as_bytes()); let keep = vm.ctx.new_bytes(buffer); let addr = keep.as_bytes().as_ptr() as usize; return Ok(Argument { - ffi_type: Type::pointer(), + ffi_type: ffi_pointer_type(), keep: Some(keep.into()), - value: FfiArgValue::Pointer(addr), + value: FfiArgValue::pointer(addr), }); } @@ -314,18 +181,18 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult { if let Ok(int_val) = value.try_int(vm) { let val = int_val.as_bigint().to_i32().unwrap_or(0); return Ok(Argument { - ffi_type: Type::i32(), + ffi_type: ffi_i32_type(), keep: None, - value: FfiArgValue::I32(val), + value: FfiArgValue::Scalar(FfiValue::I32(val)), }); } // 11. Python float -> f64 if let Ok(float_val) = value.try_float(vm) { return Ok(Argument { - ffi_type: Type::f64(), + ffi_type: ffi_f64_type(), keep: None, - value: FfiArgValue::F64(float_val.to_f64()), + value: FfiArgValue::Scalar(FfiValue::F64(float_val.to_f64())), }); } @@ -341,29 +208,29 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult { } trait ArgumentType { - fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult; + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult; fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; } impl ArgumentType for PyTypeRef { - fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { use super::pointer::PyCPointer; use super::structure::PyCStructure; // CArgObject (from byref()) should be treated as pointer if self.fast_issubclass(CArgObject::static_type()) { - return Ok(Type::pointer()); + return Ok(ffi_pointer_type()); } // Pointer types (POINTER(T)) are always pointer FFI type // Check if type is a subclass of _Pointer (PyCPointer) if self.fast_issubclass(PyCPointer::static_type()) { - return Ok(Type::pointer()); + return Ok(ffi_pointer_type()); } // Structure types are passed as pointers if self.fast_issubclass(PyCStructure::static_type()) { - return Ok(Type::pointer()); + return Ok(ffi_pointer_type()); } // Use get_attr to traverse MRO (for subclasses like MyInt(c_int)) @@ -377,7 +244,7 @@ impl ArgumentType for PyTypeRef { .ok_or_else(|| vm.new_type_error("Unsupported argument type"))?; let typ = typ.to_string(); let typ = typ.as_str(); - get_ffi_type(typ) + ffi_type_from_code(typ) .ok_or_else(|| vm.new_type_error(format!("Unsupported argument type: {typ}"))) } @@ -398,13 +265,13 @@ impl ArgumentType for PyTypeRef { // None -> NULL pointer if vm.is_none(&converted) { - return Ok(FfiArgValue::Pointer(0)); + return Ok(FfiArgValue::pointer(0)); } // For pointer types (POINTER(T)), we need to pass the pointer VALUE stored in buffer if self.fast_issubclass(PyCPointer::static_type()) { if let Some(pointer) = converted.downcast_ref::() { - return Ok(FfiArgValue::Pointer(pointer.get_ptr_value())); + return Ok(FfiArgValue::pointer(pointer.get_ptr_value())); } return convert_to_pointer(&converted, vm); } @@ -440,15 +307,15 @@ impl ArgumentType for PyTypeRef { } trait ReturnType { - fn to_ffi_type(&self, vm: &VirtualMachine) -> Option; + fn to_ffi_type(&self, vm: &VirtualMachine) -> Option; } impl ReturnType for PyTypeRef { - fn to_ffi_type(&self, vm: &VirtualMachine) -> Option { + fn to_ffi_type(&self, vm: &VirtualMachine) -> Option { // Try to get _type_ attribute first (for ctypes types like c_void_p) if let Ok(type_attr) = self.as_object().get_attr(vm.ctx.intern_str("_type_"), vm) && let Some(s) = type_attr.downcast_ref::() - && let Some(ffi_type) = s.to_str().and_then(get_ffi_type) + && let Some(ffi_type) = s.to_str().and_then(ffi_type_from_code) { return Some(ffi_type); } @@ -459,25 +326,17 @@ impl ReturnType for PyTypeRef { let size = stg_info.size; // Small structs can be returned in registers // Match can_return_struct_as_int/can_return_struct_as_sint64 - return Some(if size <= 4 { - Type::i32() - } else if size <= 8 { - Type::i64() - } else { - // Large structs: use pointer-sized return - // (ABI typically returns via hidden pointer parameter) - Type::pointer() - }); + return Some(ffi_type_for_return_size(size)); } // Fallback to class name - get_ffi_type(self.name().to_string().as_str()) + ffi_type_from_code(self.name().to_string().as_str()) } } impl ReturnType for PyNone { - fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option { - get_ffi_type("void") + fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option { + ffi_type_from_code("void") } } @@ -500,7 +359,7 @@ impl Initializer for PyCFuncPtrType { new_type.check_not_initialized(vm)?; - let ptr_size = core::mem::size_of::(); + let ptr_size = pointer_size(); let mut stg_info = StgInfo::new(ptr_size, ptr_size); stg_info.format = Some("X{}".to_string()); stg_info.length = 1; @@ -577,8 +436,8 @@ fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult if carg.offset != 0 && let Some(cdata) = carg.obj.downcast_ref::() { - let base = cdata.buffer.read().as_ptr() as isize; - return Ok((base + carg.offset) as usize); + let base = cdata.buffer.read().as_ptr() as usize; + return Ok(offset_address(base, carg.offset)); } return extract_ptr_from_arg(&carg.obj, vm); } @@ -588,8 +447,10 @@ fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult } if let Some(simple) = arg.downcast_ref::() { let buffer = simple.0.buffer.read(); - if let Some(&bytes) = buffer.first_chunk::<{ size_of::() }>() { - return Ok(usize::from_ne_bytes(bytes)); + if buffer.first_chunk::<{ size_of::() }>().is_some() { + return Ok(rustpython_host_env::ctypes::read_pointer_from_buffer( + &buffer, + )); } } if let Some(cdata) = arg.downcast_ref::() { @@ -616,63 +477,19 @@ fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult /// string_at implementation - read bytes from memory at ptr fn string_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { - if ptr == 0 { - return Err(vm.new_value_error("NULL pointer access")); + match rustpython_host_env::ctypes::string_at(ptr, size) { + Ok(bytes) => Ok(vm.ctx.new_bytes(bytes).into()), + Err(StringAtError::NullPointer) => Err(vm.new_value_error("NULL pointer access")), + Err(StringAtError::TooLong) => Err(vm.new_overflow_error("string too long")), } - let ptr = ptr as *const u8; - let len = if size < 0 { - // size == -1 means use strlen - unsafe { libc::strlen(ptr as _) } - } else { - // Overflow check for huge size values - let size_usize = size as usize; - if size_usize > isize::MAX as usize / 2 { - return Err(vm.new_overflow_error("string too long")); - } - size_usize - }; - let bytes = unsafe { core::slice::from_raw_parts(ptr, len) }; - Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) } /// wstring_at implementation - read wide string from memory at ptr fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { - if ptr == 0 { - return Err(vm.new_value_error("NULL pointer access")); - } - let w_ptr = ptr as *const libc::wchar_t; - let len = if size < 0 { - unsafe { libc::wcslen(w_ptr) } - } else { - // Overflow check for huge size values - let size_usize = size as usize; - if size_usize > isize::MAX as usize / core::mem::size_of::() { - return Err(vm.new_overflow_error("string too long")); - } - size_usize - }; - let wchars = unsafe { core::slice::from_raw_parts(w_ptr, len) }; - - // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide - // macOS/Linux: wchar_t = i32 (UTF-32) -> convert via char::from_u32 - cfg_select! { - windows => { - use rustpython_common::wtf8::Wtf8Buf; - let wide: Vec = wchars.to_vec(); - let wtf8 = Wtf8Buf::from_wide(&wide); - Ok(vm.ctx.new_str(wtf8).into()) - } - _ => { - #[allow( - clippy::useless_conversion, - reason = "wchar_t is i32 on some platforms and u32 on others" - )] - let s: String = wchars - .iter() - .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) - .collect(); - Ok(vm.ctx.new_str(s).into()) - } + match rustpython_host_env::ctypes::wstring_at(ptr, size) { + Ok(text) => Ok(vm.ctx.new_str(text).into()), + Err(StringAtError::NullPointer) => Err(vm.new_value_error("NULL pointer access")), + Err(StringAtError::TooLong) => Err(vm.new_overflow_error("string too long")), } } @@ -680,24 +497,18 @@ fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { #[pyclass(name = "_RawMemoryBuffer", module = "_ctypes")] #[derive(Debug, PyPayload)] pub(super) struct RawMemoryBuffer { - ptr: *const u8, - size: usize, - readonly: bool, + memory: RawMemoryView, } -// SAFETY: The caller ensures the pointer remains valid -unsafe impl Send for RawMemoryBuffer {} -unsafe impl Sync for RawMemoryBuffer {} - static RAW_MEMORY_BUFFER_METHODS: crate::protocol::BufferMethods = crate::protocol::BufferMethods { obj_bytes: |buffer| { let raw = buffer.obj_as::(); - let slice = unsafe { core::slice::from_raw_parts(raw.ptr, raw.size) }; + let slice = unsafe { raw.memory.bytes() }; rustpython_common::borrow::BorrowedValue::Ref(slice) }, obj_bytes_mut: |buffer| { let raw = buffer.obj_as::(); - let slice = unsafe { core::slice::from_raw_parts_mut(raw.ptr as *mut u8, raw.size) }; + let slice = unsafe { raw.memory.bytes_mut() }; rustpython_common::borrow::BorrowedValueMut::RefMut(slice) }, release: |_| {}, @@ -711,7 +522,7 @@ impl AsBuffer for RawMemoryBuffer { fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { Ok(PyBuffer::new( zelf.to_owned().into(), - BufferDescriptor::simple(zelf.size, zelf.readonly), + BufferDescriptor::simple(zelf.memory.size(), zelf.memory.readonly()), &RAW_MEMORY_BUFFER_METHODS, )) } @@ -721,19 +532,11 @@ impl AsBuffer for RawMemoryBuffer { fn memoryview_at_impl(ptr: usize, size: isize, readonly: bool, vm: &VirtualMachine) -> PyResult { use crate::builtins::PyMemoryView; - if ptr == 0 { - return Err(vm.new_value_error("NULL pointer access")); - } - if size < 0 { - return Err(vm.new_value_error("negative size")); - } - let len = size as usize; - let raw_buf = RawMemoryBuffer { - ptr: ptr as *const u8, - size: len, - readonly, - } - .into_pyobject(vm); + let memory = RawMemoryView::new(ptr, size, readonly).map_err(|err| match err { + RawMemoryViewError::NullPointer => vm.new_value_error("NULL pointer access"), + RawMemoryViewError::NegativeSize => vm.new_value_error("negative size"), + })?; + let raw_buf = RawMemoryBuffer { memory }.into_pyobject(vm); let mv = PyMemoryView::from_object(&raw_buf, vm)?; Ok(mv.into_pyobject(vm)) } @@ -799,7 +602,7 @@ pub(super) fn cast_impl( } else if let Some(simple) = obj.downcast_ref::() { // Simple type (c_void_p, c_char_p, etc.) → value from buffer let buffer = simple.0.buffer.read(); - super::base::read_ptr_from_buffer(&buffer) + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) } else if let Some(cdata) = obj.downcast_ref::() { // Array, Structure, Union → buffer address (b_ptr) cdata.buffer.read().as_ptr() as usize @@ -858,12 +661,8 @@ pub(super) fn cast_impl( if let Some(ptr) = result.downcast_ref::() { ptr.set_ptr_value(ptr_value); } else if let Some(cdata) = result.downcast_ref::() { - let bytes = ptr_value.to_ne_bytes(); let mut buffer = cdata.buffer.write(); - let buf = buffer.to_mut(); - if buf.len() >= bytes.len() { - buf[..bytes.len()].copy_from_slice(&bytes); - } + write_pointer_to_buffer_at(buffer.to_mut(), 0, pointer_size(), ptr_value); } Ok(result) @@ -873,22 +672,18 @@ impl PyCFuncPtr { /// Get function pointer address from buffer fn get_func_ptr(&self) -> usize { let buffer = self._base.buffer.read(); - super::base::read_ptr_from_buffer(&buffer) + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) } /// Get CodePtr from buffer for FFI calls - fn get_code_ptr(&self) -> Option { + fn get_code_ptr(&self) -> Option { let addr = self.get_func_ptr(); - if addr != 0 { - Some(CodePtr(addr as *mut _)) - } else { - None - } + rustpython_host_env::ctypes::code_ptr_from_addr(addr) } /// Create buffer with function pointer address fn make_ptr_buffer(addr: usize) -> Vec { - addr.to_ne_bytes().to_vec() + pointer_bytes(addr) } } @@ -902,7 +697,7 @@ impl Constructor for PyCFuncPtr { // 3. Tuple argument: (name, dll) form // 4. Callable: callback creation - let ptr_size = core::mem::size_of::(); + let ptr_size = pointer_size(); if args.args.is_empty() { return Self { @@ -1017,32 +812,26 @@ impl Constructor for PyCFuncPtr { .as_bigint() .clone(), }; - let library_cache = super::library::libcache().read(); - let library = library_cache - .get_lib( - handle - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid handle"))?, - ) - .ok_or_else(|| vm.new_value_error("Library not found"))?; - let inner_lib = library.lib.lock(); - let terminated = format!("{}\0", &name); - let ptr_val = if let Some(lib) = &*inner_lib { - let pointer: Symbol<'_, FP> = unsafe { - lib.get(terminated.as_bytes()) - .map_err(|err| err.to_string()) - .map_err(|err| vm.new_attribute_error(err))? - }; - let addr = *pointer as usize; - // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) - // Treat NULL addresses as errors - if addr == 0 { - return Err(vm.new_attribute_error(format!("function '{name}' not found"))); + let ptr_val = match rustpython_host_env::ctypes::lookup_function_symbol_addr( + handle + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid handle"))?, + terminated.as_bytes(), + ) { + Ok(addr) => { + if addr == 0 { + return Err(vm.new_attribute_error(format!("function '{name}' not found"))); + } + addr + } + Err(rustpython_host_env::ctypes::LookupSymbolError::LibraryNotFound) => { + return Err(vm.new_value_error("Library not found")); + } + Err(rustpython_host_env::ctypes::LookupSymbolError::LibraryClosed) => 0, + Err(rustpython_host_env::ctypes::LookupSymbolError::Load(err)) => { + return Err(vm.new_attribute_error(err)); } - addr - } else { - 0 }; return Self { @@ -1176,7 +965,7 @@ struct CallInfo { explicit_arg_types: Option>, restype_obj: Option, restype_is_none: bool, - ffi_return_type: Type, + ffi_return_type: FfiType, is_pointer_return: bool, } @@ -1223,13 +1012,13 @@ fn extract_call_info(zelf: &Py, vm: &VirtualMachine) -> PyResult().ok()) .and_then(|t| ReturnType::to_ffi_type(&t, vm)) - .unwrap_or_else(Type::i32) + .unwrap_or_else(ffi_i32_type) }; // Check if return type is a pointer type via TYPEFLAG_ISPOINTER @@ -1300,7 +1089,7 @@ fn resolve_com_method( zelf: &Py, args: &FuncArgs, vm: &VirtualMachine, -) -> PyResult<(Option, bool)> { +) -> PyResult<(Option, bool)> { let com_index = zelf.index.read(); let Some(idx) = *com_index else { return Ok((None, false)); @@ -1315,8 +1104,8 @@ fn resolve_com_method( let self_arg = &args.args[0]; let com_ptr = if let Some(simple) = self_arg.downcast_ref::() { let buffer = simple.0.buffer.read(); - if buffer.len() >= core::mem::size_of::() { - super::base::read_ptr_from_buffer(&buffer) + if has_pointer_width(&buffer) { + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) } else { 0 } @@ -1326,33 +1115,26 @@ fn resolve_com_method( return Err(vm.new_type_error("COM method first argument must be a COM pointer")); }; - if com_ptr == 0 { - return Err(vm.new_value_error("NULL COM pointer access")); - } - - // Read vtable pointer from COM object: vtable = *(void**)com_ptr - let vtable_ptr = unsafe { *(com_ptr as *const usize) }; - if vtable_ptr == 0 { - return Err(vm.new_value_error("NULL vtable pointer")); - } - - // Read function pointer from vtable: func = vtable[index] - let fptr = unsafe { - let vtable = vtable_ptr as *const usize; - *vtable.add(idx) + let code_ptr = match rustpython_host_env::ctypes::resolve_com_vtable_entry(com_ptr, idx) { + Ok(code_ptr) => code_ptr, + Err(ComMethodError::NullComPointer) => { + return Err(vm.new_value_error("NULL COM pointer access")); + } + Err(ComMethodError::NullVtablePointer) => { + return Err(vm.new_value_error("NULL vtable pointer")); + } + Err(ComMethodError::NullFunctionPointer) => { + return Err(vm.new_value_error("NULL function pointer in vtable")); + } }; - if fptr == 0 { - return Err(vm.new_value_error("NULL function pointer in vtable")); - } - - Ok((Some(CodePtr(fptr as *mut _)), true)) + Ok((Some(code_ptr), true)) } /// Single argument for FFI call // struct argument struct Argument { - ffi_type: Type, + ffi_type: FfiType, value: FfiArgValue, #[allow(dead_code)] keep: Option, // Object to keep alive during call @@ -1468,7 +1250,7 @@ fn build_callargs_with_paramflags( arguments.push(Argument { ffi_type, keep: None, - value: FfiArgValue::Pointer(addr), + value: FfiArgValue::pointer(addr), }); out_buffers.push((param_idx, buffer)); } else { @@ -1539,29 +1321,22 @@ fn build_callargs( } } -/// Raw result from FFI call -enum RawResult { - Void, - Pointer(usize), - Value(libffi::low::ffi_arg), -} - /// Execute FFI call -fn ctypes_callproc(code_ptr: CodePtr, arguments: &[Argument], call_info: &CallInfo) -> RawResult { - let ffi_arg_types: Vec = arguments.iter().map(|a| a.ffi_type.clone()).collect(); - let cif = Cif::new(ffi_arg_types, call_info.ffi_return_type.clone()); - let ffi_args: Vec> = arguments.iter().map(|a| a.value.as_arg()).collect(); - - if call_info.restype_is_none { - unsafe { cif.call::<()>(code_ptr, &ffi_args) }; - RawResult::Void - } else if call_info.is_pointer_return { - let result = unsafe { cif.call::(code_ptr, &ffi_args) }; - RawResult::Pointer(result) - } else { - let result = unsafe { cif.call::(code_ptr, &ffi_args) }; - RawResult::Value(result) - } +fn ctypes_callproc( + code_ptr: FfiCodePtr, + arguments: &[Argument], + call_info: &CallInfo, +) -> RawResult { + let ffi_arg_types: Vec = arguments.iter().map(|a| a.ffi_type.clone()).collect(); + let ffi_args: Vec<_> = arguments.iter().map(|a| a.value.as_arg()).collect(); + rustpython_host_env::ctypes::callproc( + code_ptr, + ffi_arg_types, + call_info.ffi_return_type.clone(), + &ffi_args, + call_info.restype_is_none, + call_info.is_pointer_return, + ) } /// Check and handle HRESULT errors (Windows) @@ -1607,17 +1382,7 @@ fn convert_raw_result( vm: &VirtualMachine, ) -> Option { // Get result as bytes for type conversion - let (result_bytes, result_size) = match raw_result { - RawResult::Void => return None, - RawResult::Pointer(ptr) => { - let bytes = ptr.to_ne_bytes(); - (bytes.to_vec(), core::mem::size_of::()) - } - RawResult::Value(val) => { - let bytes = val.to_ne_bytes(); - (bytes.to_vec(), core::mem::size_of::()) - } - }; + let (result_bytes, result_size) = rustpython_host_env::ctypes::call_result_bytes(raw_result)?; // 1. No restype → return as int let restype = match &call_info.restype_obj { @@ -1670,7 +1435,11 @@ fn convert_raw_result( // 5. Simple type with getfunc → use bytes_to_pyobject (info->getfunc) // is_simple_instance returns TRUE for c_int, c_void_p, etc. if super::base::is_simple_instance(&restype_type) { - return super::base::bytes_to_pyobject(&restype_type, &result_bytes, vm).ok(); + return Some(super::base::bytes_to_pyobject( + &restype_type, + &result_bytes, + vm, + )); } // 6. Complex type → create ctypes instance (PyCData_FromBaseObj) @@ -1705,10 +1474,7 @@ fn pycdata_from_ffi_result( // Copy result data into instance buffer if let Some(cdata) = instance.downcast_ref::() { let mut buffer = cdata.buffer.write(); - let copy_size = size.min(buffer.len()).min(result_bytes.len()); - if copy_size > 0 { - buffer.to_mut()[..copy_size].copy_from_slice(&result_bytes[..copy_size]); - } + write_prefix_limited(buffer.to_mut(), result_bytes, size); } Ok(instance) @@ -1782,7 +1548,7 @@ impl Callable for PyCFuncPtr { #[cfg(windows)] let (func_ptr, is_com_method) = resolve_com_method(zelf, &args, vm)?; #[cfg(not(windows))] - let (func_ptr, is_com_method) = (None::, false); + let (func_ptr, is_com_method) = (None::, false); // 3. Extract call info (argtypes, restype) let call_info = extract_call_info(zelf, vm)?; @@ -1800,7 +1566,7 @@ impl Callable for PyCFuncPtr { None => { debug_assert!(false, "NULL function pointer"); // In release mode, this will crash - CodePtr(core::ptr::null_mut()) + null_code_ptr() } }; @@ -1811,7 +1577,9 @@ impl Callable for PyCFuncPtr { #[cfg(not(windows))] let raw_result = { if flags & super::base::StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0 { - swap_errno(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + rustpython_host_env::ctypes::with_swapped_errno(|| { + ctypes_callproc(code_ptr, &arguments, &call_info) + }) } else { ctypes_callproc(code_ptr, &arguments, &call_info) } @@ -1820,7 +1588,9 @@ impl Callable for PyCFuncPtr { #[cfg(windows)] let raw_result = { if flags & super::base::StgInfoFlags::FUNCFLAG_USE_LASTERROR.bits() != 0 { - save_and_restore_last_error(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + rustpython_host_env::ctypes::with_swapped_last_error(|| { + ctypes_callproc(code_ptr, &arguments, &call_info) + }) } else { ctypes_callproc(code_ptr, &arguments, &call_info) } @@ -1855,7 +1625,7 @@ impl AsBuffer for PyCFuncPtr { stg_info.size, ) } else { - (Cow::Borrowed("X{}"), core::mem::size_of::()) + (Cow::Borrowed(pointer_format()), pointer_size()) }; let desc = BufferDescriptor { len: itemsize, @@ -1984,71 +1754,24 @@ fn is_simple_subclass(ty: &Py, vm: &VirtualMachine) -> bool { } /// Convert a C value to a Python object based on the type code. -fn ffi_to_python(ty: &Py, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef { +fn ffi_to_python( + ty: &Py, + args: *const *const c_void, + index: usize, + vm: &VirtualMachine, +) -> PyObjectRef { let type_code = ty.type_code(vm); - let raw_value: PyObjectRef = unsafe { - match type_code.as_deref() { - Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(), - Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(), - Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(), - Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(), - Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), - Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), - Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), - Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(), - Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(), - Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(), - Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(), - Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), - Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), - Some("z") => { - // c_char_p: C string pointer → Python bytes - let cstr_ptr = *(ptr as *const *const libc::c_char); - if cstr_ptr.is_null() { - vm.ctx.none() - } else { - let cstr = core::ffi::CStr::from_ptr(cstr_ptr); - vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into() - } - } - Some("Z") => { - // c_wchar_p: wchar_t* → Python str - let wstr_ptr = *(ptr as *const *const libc::wchar_t); - if wstr_ptr.is_null() { - vm.ctx.none() - } else { - let mut len = 0; - while *wstr_ptr.add(len) != 0 { - len += 1; - } - let slice = core::slice::from_raw_parts(wstr_ptr, len); - // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide - // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 - cfg_select! { - windows => { - use rustpython_common::wtf8::Wtf8Buf; - let wide: Vec = slice.to_vec(); - let wtf8 = Wtf8Buf::from_wide(&wide); - vm.ctx.new_str(wtf8).into() - } - _ => { - #[allow( - clippy::useless_conversion, - reason = "wchar_t is i32 on some platforms and u32 on others" - )] - let s: String = slice - .iter() - .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) - .collect(); - vm.ctx.new_str(s).into() - } - } - } - } - Some("P") => vm.ctx.new_int(*(ptr as *const usize)).into(), - Some("?") => vm.ctx.new_bool(*(ptr as *const u8) != 0).into(), - _ => return vm.ctx.none(), - } + let raw_value: PyObjectRef = match unsafe { + rustpython_host_env::ctypes::callback_arg_value_at(type_code.as_deref(), args, index) + } { + rustpython_host_env::ctypes::DecodedValue::Bytes(value) => vm.ctx.new_bytes(value).into(), + rustpython_host_env::ctypes::DecodedValue::Signed(value) => vm.ctx.new_int(value).into(), + rustpython_host_env::ctypes::DecodedValue::Unsigned(value) => vm.ctx.new_int(value).into(), + rustpython_host_env::ctypes::DecodedValue::Float(value) => vm.ctx.new_float(value).into(), + rustpython_host_env::ctypes::DecodedValue::Bool(value) => vm.ctx.new_bool(value).into(), + rustpython_host_env::ctypes::DecodedValue::Pointer(value) => vm.ctx.new_int(value).into(), + rustpython_host_env::ctypes::DecodedValue::String(value) => vm.ctx.new_str(value).into(), + rustpython_host_env::ctypes::DecodedValue::None => vm.ctx.none(), }; if !is_simple_subclass(ty, vm) { @@ -2064,117 +1787,92 @@ fn python_to_ffi(obj: PyResult, ty: &Py, result: *mut c_void, vm: &Virtu let Ok(obj) = obj else { return }; let type_code = ty.type_code(vm); - unsafe { - match type_code.as_deref() { - Some("b") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0); - } - } - Some("B") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); - } - } - Some("c") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); - } - } - Some("h") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0); - } - } - Some("H") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0); - } - } - Some("i") => { - if let Ok(i) = obj.try_int(vm) { - let val = i.as_bigint().to_i32().unwrap_or(0); - *(result as *mut libffi::low::ffi_arg) = val as libffi::low::ffi_arg; + match type_code.as_deref() { + Some("b" | "h" | "i" | "l" | "q") => { + if let Ok(i) = obj.try_int(vm) { + unsafe { + rustpython_host_env::ctypes::write_callback_result( + type_code.as_deref(), + result, + rustpython_host_env::ctypes::CallbackResultValue::Signed( + i.as_bigint().to_i64().unwrap_or(0), + ), + ); } } - Some("I") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0); - } - } - Some("l" | "q") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0); - } - } - Some("L" | "Q") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0); - } - } - Some("f") => { - if let Ok(f) = obj.try_float(vm) { - *(result as *mut f32) = f.to_f64() as f32; + } + Some("B" | "c" | "H" | "I" | "L" | "Q") => { + if let Ok(i) = obj.try_int(vm) { + unsafe { + rustpython_host_env::ctypes::write_callback_result( + type_code.as_deref(), + result, + rustpython_host_env::ctypes::CallbackResultValue::Unsigned( + i.as_bigint().to_u64().unwrap_or(0), + ), + ); } } - Some("d") => { - if let Ok(f) = obj.try_float(vm) { - *(result as *mut f64) = f.to_f64(); + } + Some("f" | "d") => { + if let Ok(f) = obj.try_float(vm) { + unsafe { + rustpython_host_env::ctypes::write_callback_result( + type_code.as_deref(), + result, + rustpython_host_env::ctypes::CallbackResultValue::Float(f.to_f64()), + ); } } - Some("P" | "z" | "Z") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0); + } + Some("P" | "z" | "Z") => { + if let Ok(i) = obj.try_int(vm) { + unsafe { + rustpython_host_env::ctypes::write_callback_result( + type_code.as_deref(), + result, + rustpython_host_env::ctypes::CallbackResultValue::Pointer( + i.as_bigint().to_usize().unwrap_or(0), + ), + ); } } - Some("?") => { - if let Ok(b) = obj.is_true(vm) { - *(result as *mut u8) = u8::from(b); + } + Some("?") => { + if let Ok(b) = obj.is_true(vm) { + unsafe { + rustpython_host_env::ctypes::write_callback_result( + type_code.as_deref(), + result, + rustpython_host_env::ctypes::CallbackResultValue::Bool(b), + ); } } - _ => {} } + _ => {} } } /// The callback function that libffi calls when the closure is invoked. unsafe extern "C" fn thunk_callback( - _cif: &low::ffi_cif, + _cif: &FfiCif, result: &mut c_void, args: *const *const c_void, userdata: &ThunkUserData, ) { with_current_vm(|vm| { - // Swap errno before call if FUNCFLAG_USE_ERRNO is set let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0; - let saved_errno = if use_errno { - let current = rustpython_host_env::os::get_errno(); - // TODO: swap with ctypes stored errno (thread-local) - Some(current) - } else { - None - }; - - let py_args: Vec = userdata - .arg_types - .iter() - .enumerate() - .map(|(i, ty)| { - let arg_ptr = unsafe { *args.add(i) }; - ffi_to_python(ty, arg_ptr, vm) - }) - .collect(); - - let py_result = userdata.callable.call(py_args, vm); - - // Swap errno back after call - if use_errno { - let _current = rustpython_host_env::os::get_errno(); - // TODO: store current errno to ctypes storage - if let Some(saved) = saved_errno { - rustpython_host_env::os::set_errno(saved); - } - } + let py_result = + rustpython_host_env::ctypes::with_callback_errno_preserved(use_errno, || { + let py_args: Vec = userdata + .arg_types + .iter() + .enumerate() + .map(|(i, ty)| ffi_to_python(ty, args, i, vm)) + .collect(); + + userdata.callable.call(py_args, vm) + }); // Call unraisable hook if exception occurred if let Err(exc) = &py_result { @@ -2192,29 +1890,14 @@ unsafe extern "C" fn thunk_callback( }); } -/// Holds the closure and userdata together to ensure proper lifetime. -struct ThunkData { - #[allow(dead_code)] - closure: Closure<'static>, - userdata_ptr: *mut ThunkUserData, -} - -impl Drop for ThunkData { - fn drop(&mut self) { - unsafe { - drop(Box::from_raw(self.userdata_ptr)); - } - } -} - /// CThunkObject wraps a Python callable to make it callable from C code. #[pyclass(name = "CThunkObject", module = "_ctypes")] #[derive(PyPayload)] pub(super) struct PyCThunk { callable: PyObjectRef, #[allow(dead_code)] - thunk_data: PyRwLock>, - code_ptr: CodePtr, + thunk_data: PyRwLock>>, + code_ptr: FfiCodePtr, } impl Debug for PyCThunk { @@ -2226,7 +1909,7 @@ impl Debug for PyCThunk { } impl PyCThunk { - pub(super) fn new( + pub fn new( callable: PyObjectRef, arg_types: Option, res_type: Option, @@ -2254,39 +1937,33 @@ impl PyCThunk { _ => None, }; - let ffi_arg_types: Vec = arg_type_vec + let ffi_arg_types: Vec = arg_type_vec .iter() .map(|ty| { ty.type_code(vm) - .and_then(|code| get_ffi_type(&code)) - .unwrap_or_else(Type::pointer) + .and_then(|code| ffi_type_from_code(&code)) + .unwrap_or_else(ffi_pointer_type) }) .collect(); let ffi_res_type = res_type_ref .as_ref() .and_then(|ty| ty.type_code(vm)) - .and_then(|code| get_ffi_type(&code)) - .unwrap_or_else(Type::void); - - let cif = Cif::new(ffi_arg_types, ffi_res_type); - - let userdata = Box::new(ThunkUserData { - callable: callable.clone(), - arg_types: arg_type_vec, - res_type: res_type_ref, - flags, - }); - let userdata_ptr = Box::into_raw(userdata); - let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; - - let closure = Closure::new(cif, thunk_callback, userdata_ref); - let code_ptr = CodePtr(*closure.code_ptr() as *mut _); - - let thunk_data = ThunkData { - closure, - userdata_ptr, - }; + .and_then(|code| ffi_type_from_code(&code)) + .unwrap_or_else(ffi_void_type); + + let thunk_data = rustpython_host_env::ctypes::CallbackThunk::new( + ffi_arg_types, + ffi_res_type, + Box::new(ThunkUserData { + callable: callable.clone(), + arg_types: arg_type_vec, + res_type: res_type_ref, + flags, + }), + thunk_callback, + ); + let code_ptr = thunk_data.code_ptr(); Ok(Self { callable, @@ -2295,7 +1972,7 @@ impl PyCThunk { }) } - pub(super) fn code_ptr(&self) -> CodePtr { + pub fn code_ptr(&self) -> FfiCodePtr { self.code_ptr } } diff --git a/crates/vm/src/stdlib/_ctypes/library.rs b/crates/vm/src/stdlib/_ctypes/library.rs deleted file mode 100644 index ac9059864d6..00000000000 --- a/crates/vm/src/stdlib/_ctypes/library.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::VirtualMachine; -use alloc::fmt; -use libloading::Library; -use rustpython_common::lock::{PyMutex, PyRwLock}; -use std::collections::HashMap; -use std::ffi::OsStr; - -#[cfg(unix)] -use libloading::os::unix::Library as UnixLibrary; - -pub(super) struct SharedLibrary { - pub(crate) lib: PyMutex>, -} - -impl fmt::Debug for SharedLibrary { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SharedLibrary") - } -} - -impl SharedLibrary { - #[cfg(windows)] - pub(super) fn new(name: impl AsRef) -> Result { - Ok(Self { - lib: PyMutex::new(unsafe { Some(Library::new(name.as_ref())?) }), - }) - } - - #[cfg(unix)] - pub(super) fn new_with_mode( - name: impl AsRef, - mode: i32, - ) -> Result { - Ok(Self { - lib: PyMutex::new(Some(unsafe { - UnixLibrary::open(Some(name.as_ref()), mode)?.into() - })), - }) - } - - /// Create a SharedLibrary from a raw dlopen handle (for pythonapi / dlopen(NULL)) - #[cfg(unix)] - pub(super) fn from_raw_handle(handle: *mut libc::c_void) -> Self { - Self { - lib: PyMutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })), - } - } - - /// Get the underlying OS handle (HMODULE on Windows, dlopen handle on Unix) - pub(super) fn get_pointer(&self) -> usize { - let lib_lock = self.lib.lock(); - if let Some(l) = &*lib_lock { - // libloading::Library internally stores the OS handle directly - // On Windows: HMODULE (*mut c_void) - // On Unix: *mut c_void from dlopen - // We use transmute_copy to read the handle without consuming the Library - unsafe { core::mem::transmute_copy::(l) } - } else { - 0 - } - } - - fn is_closed(&self) -> bool { - let lib_lock = self.lib.lock(); - lib_lock.is_none() - } -} - -pub(super) struct ExternalLibs { - libraries: HashMap, -} - -impl ExternalLibs { - fn new() -> Self { - Self { - libraries: HashMap::new(), - } - } - - pub(super) fn get_lib(&self, key: usize) -> Option<&SharedLibrary> { - self.libraries.get(&key) - } - - #[cfg(windows)] - pub(super) fn get_or_insert_lib( - &mut self, - library_path: impl AsRef, - _vm: &VirtualMachine, - ) -> Result<(usize, &SharedLibrary), libloading::Error> { - let new_lib = SharedLibrary::new(library_path)?; - let key = new_lib.get_pointer(); - - // Check if library already exists and is not closed - let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); - - if should_use_cached { - // new_lib will be dropped, calling FreeLibrary (decrements refcount) - // But library stays loaded because cached version maintains refcount - drop(new_lib); - return Ok((key, self.libraries.get(&key).expect("just checked"))); - } - - self.libraries.insert(key, new_lib); - Ok((key, self.libraries.get(&key).expect("just inserted"))) - } - - #[cfg(unix)] - pub(super) fn get_or_insert_lib_with_mode( - &mut self, - library_path: impl AsRef, - mode: i32, - _vm: &VirtualMachine, - ) -> Result<(usize, &SharedLibrary), libloading::Error> { - let new_lib = SharedLibrary::new_with_mode(library_path, mode)?; - let key = new_lib.get_pointer(); - - // Check if library already exists and is not closed - let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); - - if should_use_cached { - // new_lib will be dropped, calling dlclose (decrements refcount) - // But library stays loaded because cached version maintains refcount - drop(new_lib); - return Ok((key, self.libraries.get(&key).expect("just checked"))); - } - - self.libraries.insert(key, new_lib); - Ok((key, self.libraries.get(&key).expect("just inserted"))) - } - - /// Insert a raw dlopen handle into the cache (for pythonapi / dlopen(NULL)) - #[cfg(unix)] - pub(super) fn insert_raw_handle(&mut self, handle: *mut libc::c_void) -> usize { - let shared_lib = SharedLibrary::from_raw_handle(handle); - let key = handle as usize; - self.libraries.insert(key, shared_lib); - key - } - - pub(super) fn drop_lib(&mut self, key: usize) { - self.libraries.remove(&key); - } -} - -pub(super) fn libcache() -> &'static PyRwLock { - rustpython_common::static_cell! { - static LIBCACHE: PyRwLock; - } - LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new())) -} diff --git a/crates/vm/src/stdlib/_ctypes/pointer.rs b/crates/vm/src/stdlib/_ctypes/pointer.rs index 71b8455b83e..be165ee9f8b 100644 --- a/crates/vm/src/stdlib/_ctypes/pointer.rs +++ b/crates/vm/src/stdlib/_ctypes/pointer.rs @@ -11,6 +11,10 @@ use crate::{ }; use alloc::borrow::Cow; use num_traits::ToPrimitive; +use rustpython_host_env::ctypes::{ + AddressValue, AddressWriteValue, IntegerValue, pointer_item_address, read_pointer_char_slice, + read_pointer_wchar_slice, +}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] @@ -46,7 +50,7 @@ impl Initializer for PyCPointerType { } // Initialize StgInfo for pointer type - let pointer_size = core::mem::size_of::(); + let pointer_size = rustpython_host_env::ctypes::pointer_size(); let mut stg_info = StgInfo::new(pointer_size, pointer_size); stg_info.proto = proto; stg_info.paramfunc = super::base::ParamFunc::Pointer; @@ -263,7 +267,7 @@ impl Constructor for PyCPointer { // Create a new PyCPointer instance with NULL pointer (all zeros) // Initial contents is set via __init__ if provided - let cdata = PyCData::from_bytes(vec![0u8; core::mem::size_of::()], None); + let cdata = PyCData::from_bytes(rustpython_host_env::ctypes::null_pointer_bytes(), None); // pointer instance has b_length set to 2 (for index 0 and 1) cdata.length.store(2); Self(cdata).into_ref_with_type(vm, cls).map(Into::into) @@ -296,16 +300,18 @@ impl PyCPointer { /// Get the pointer value stored in buffer as usize pub(crate) fn get_ptr_value(&self) -> usize { let buffer = self.0.buffer.read(); - super::base::read_ptr_from_buffer(&buffer) + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) } /// Set the pointer value in buffer pub(crate) fn set_ptr_value(&self, value: usize) { let mut buffer = self.0.buffer.write(); - let bytes = value.to_ne_bytes(); - if buffer.len() >= bytes.len() { - buffer.to_mut()[..bytes.len()].copy_from_slice(&bytes); - } + rustpython_host_env::ctypes::write_pointer_to_buffer_at( + buffer.to_mut(), + 0, + rustpython_host_env::ctypes::pointer_size(), + value, + ); } /// contents getter - reads address from b_ptr and creates an instance of the pointed-to type @@ -322,7 +328,7 @@ impl PyCPointer { let proto_type = stg_info.proto(); let element_size = proto_type .stg_info_opt() - .map_or(core::mem::size_of::(), |info| info.size); + .map_or_else(rustpython_host_env::ctypes::pointer_size, |info| info.size); // Create instance that references the memory directly // PyCData.into_ref_with_type works for all ctypes (simple, structure, union, array, pointer) @@ -405,11 +411,10 @@ impl PyCPointer { let proto_type = stg_info.proto(); let element_size = proto_type .stg_info_opt() - .map_or(core::mem::size_of::(), |info| info.size); + .map_or_else(rustpython_host_env::ctypes::pointer_size, |info| info.size); // offset = index * iteminfo->size - let offset = index * element_size as isize; - let addr = (ptr_value as isize + offset) as usize; + let addr = pointer_item_address(ptr_value, index, element_size); // Check if it's a simple type (has _type_ attribute) if let Ok(type_attr) = proto_type.as_object().get_attr("_type_", vm) @@ -495,7 +500,7 @@ impl PyCPointer { let element_size = if let Some(ref proto_type) = stg_info.proto { proto_type.stg_info_opt().expect("proto has StgInfo").size } else { - core::mem::size_of::() + rustpython_host_env::ctypes::pointer_size() }; let type_code = stg_info .proto @@ -511,23 +516,8 @@ impl PyCPointer { if len == 0 { return Ok(vm.ctx.new_bytes(vec![]).into()); } - let mut result = Vec::with_capacity(len); - if step == 1 { - // Optimized contiguous copy - let start_addr = (ptr_value as isize + start * element_size as isize) as *const u8; - unsafe { - result.extend_from_slice(core::slice::from_raw_parts(start_addr, len)); - } - } else { - let mut cur = start; - for _ in 0..len { - let addr = (ptr_value as isize + cur * element_size as isize) as *const u8; - unsafe { - result.push(*addr); - } - cur += step; - } - } + let result = + unsafe { read_pointer_char_slice(ptr_value, start, len, step, element_size) }; return Ok(vm.ctx.new_bytes(result).into()); } @@ -536,23 +526,10 @@ impl PyCPointer { if len == 0 { return Ok(vm.ctx.new_str("").into()); } - let mut result = String::with_capacity(len); - let wchar_size = core::mem::size_of::(); - let mut cur = start; - for _ in 0..len { - let addr = (ptr_value as isize + cur * wchar_size as isize) as *const libc::wchar_t; - unsafe { - #[allow( - clippy::unnecessary_cast, - reason = "wchar_t is i32 on some platforms and u32 on others" - )] - if let Some(c) = char::from_u32(*addr as u32) { - result.push(c); - } - } - cur += step; - } - return Ok(vm.ctx.new_str(result).into()); + return Ok(vm + .ctx + .new_str(unsafe { read_pointer_wchar_slice(ptr_value, start, len, step) }) + .into()); } // other types → list with Pointer_item for each @@ -608,11 +585,10 @@ impl PyCPointer { let element_size = proto_type .stg_info_opt() - .map_or(core::mem::size_of::(), |info| info.size); + .map_or_else(rustpython_host_env::ctypes::pointer_size, |info| info.size); // Calculate address - let offset = index * element_size as isize; - let addr = (ptr_value as isize + offset) as usize; + let addr = pointer_item_address(ptr_value, index, element_size); // Write value at address // Handle Structure/Array types by copying their buffer @@ -622,10 +598,8 @@ impl PyCPointer { || cdata.fast_isinstance(PyCSimple::static_type())) { let src_buffer = cdata.buffer.read(); - let copy_len = src_buffer.len().min(element_size); unsafe { - let dest_ptr = addr as *mut u8; - core::ptr::copy_nonoverlapping(src_buffer.as_ptr(), dest_ptr, copy_len); + rustpython_host_env::ctypes::copy_bytes_to_address(addr, &src_buffer, element_size); } } else { // Handle z/Z specially to store converted value @@ -634,7 +608,11 @@ impl PyCPointer { { let (kept_alive, ptr_val) = super::base::ensure_z_null_terminated(bytes, vm); unsafe { - *(addr as *mut usize) = ptr_val; + rustpython_host_env::ctypes::write_value_to_address( + addr, + element_size, + AddressWriteValue::Pointer(ptr_val), + ); } zelf.0.keep_alive(index as usize, kept_alive); return zelf.0.keep_ref(index as usize, value.clone(), vm); @@ -643,7 +621,11 @@ impl PyCPointer { { let (holder, ptr_val) = super::base::str_to_wchar_bytes(s.as_wtf8(), vm); unsafe { - *(addr as *mut usize) = ptr_val; + rustpython_host_env::ctypes::write_value_to_address( + addr, + element_size, + AddressWriteValue::Pointer(ptr_val), + ); } return zelf.0.keep_ref(index as usize, holder, vm); } @@ -661,56 +643,13 @@ impl PyCPointer { type_code: Option<&str>, vm: &VirtualMachine, ) -> PyObjectRef { - unsafe { - let ptr = addr as *const u8; - match type_code { - // Single-byte types don't need read_unaligned - Some("c") => vm.ctx.new_bytes(vec![*ptr]).into(), - Some("b") => vm.ctx.new_int(*ptr as i8 as i32).into(), - Some("B") => vm.ctx.new_int(*ptr as i32).into(), - // Multi-byte types need read_unaligned for safety on strict-alignment architectures - Some("h") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const i16) as i32) - .into(), - Some("H") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const u16) as i32) - .into(), - Some("i" | "l") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const i32)) - .into(), - Some("I" | "L") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const u32)) - .into(), - Some("q") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const i64)) - .into(), - Some("Q") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const u64)) - .into(), - Some("f") => vm - .ctx - .new_float(core::ptr::read_unaligned(ptr as *const f32) as f64) - .into(), - Some("d" | "g") => vm - .ctx - .new_float(core::ptr::read_unaligned(ptr as *const f64)) - .into(), - Some("P" | "z" | "Z") => vm - .ctx - .new_int(core::ptr::read_unaligned(ptr as *const usize)) - .into(), - _ => { - // Default: read as bytes - let bytes = core::slice::from_raw_parts(ptr, size).to_vec(); - vm.ctx.new_bytes(bytes).into() - } - } + match unsafe { rustpython_host_env::ctypes::read_value_at_address(addr, size, type_code) } { + AddressValue::ByteString(byte) => vm.ctx.new_bytes(vec![byte]).into(), + AddressValue::Integer(IntegerValue::Signed(value)) => vm.ctx.new_int(value).into(), + AddressValue::Integer(IntegerValue::Unsigned(value)) => vm.ctx.new_int(value).into(), + AddressValue::Float(value) => vm.ctx.new_float(value).into(), + AddressValue::Pointer(value) => vm.ctx.new_int(value).into(), + AddressValue::Bytes(bytes) => vm.ctx.new_bytes(bytes).into(), } } @@ -723,8 +662,6 @@ impl PyCPointer { vm: &VirtualMachine, ) -> PyResult<()> { unsafe { - let ptr = addr as *mut u8; - // Handle c_char_p (z) and c_wchar_p (Z) - store pointer address // Note: PyBytes/PyStr cases are handled by caller (setitem_by_index) if let Some("z" | "Z") = type_code { @@ -735,7 +672,11 @@ impl PyCPointer { } else { return Err(vm.new_type_error("bytes/string or integer address expected")); }; - core::ptr::write_unaligned(ptr as *mut usize, ptr_val); + rustpython_host_env::ctypes::write_value_to_address( + addr, + size, + AddressWriteValue::Pointer(ptr_val), + ); return Ok(()); } @@ -743,56 +684,39 @@ impl PyCPointer { // Use write_unaligned for safety on strict-alignment architectures if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); - match size { - 1 => { - *ptr = i.to_u8().expect("int too large"); - } - 2 => { - core::ptr::write_unaligned( - ptr as *mut i16, - i.to_i16().expect("int too large"), - ); - } - 4 => { - core::ptr::write_unaligned( - ptr as *mut i32, - i.to_i32().expect("int too large"), - ); - } - 8 => { - core::ptr::write_unaligned( - ptr as *mut i64, - i.to_i64().expect("int too large"), - ); - } + let bytes; + let write_value = match size { + 1 => AddressWriteValue::U8(i.to_u8().expect("int too large")), + 2 => AddressWriteValue::I16(i.to_i16().expect("int too large")), + 4 => AddressWriteValue::I32(i.to_i32().expect("int too large")), + 8 => AddressWriteValue::I64(i.to_i64().expect("int too large")), _ => { - let bytes = i.to_signed_bytes_le(); - let copy_len = bytes.len().min(size); - core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + bytes = i.to_signed_bytes_le(); + AddressWriteValue::Bytes(&bytes) } - } + }; + rustpython_host_env::ctypes::write_value_to_address(addr, size, write_value); return Ok(()); } // Try to get value as float if let Ok(float_val) = value.try_float(vm) { let f = float_val.to_f64(); - match size { - 4 => { - core::ptr::write_unaligned(ptr as *mut f32, f as f32); - } - 8 => { - core::ptr::write_unaligned(ptr as *mut f64, f); - } - _ => {} - } + rustpython_host_env::ctypes::write_value_to_address( + addr, + size, + AddressWriteValue::Float(f), + ); return Ok(()); } // Try bytes if let Ok(bytes) = value.try_bytes_like(vm, |b| b.to_vec()) { - let copy_len = bytes.len().min(size); - core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + rustpython_host_env::ctypes::write_value_to_address( + addr, + size, + AddressWriteValue::Bytes(&bytes), + ); return Ok(()); } diff --git a/crates/vm/src/stdlib/_ctypes/simple.rs b/crates/vm/src/stdlib/_ctypes/simple.rs index e50985c2ab9..d51a61130f3 100644 --- a/crates/vm/src/stdlib/_ctypes/simple.rs +++ b/crates/vm/src/stdlib/_ctypes/simple.rs @@ -1,11 +1,10 @@ use super::_ctypes::CArgObject; -use super::array::{PyCArray, WCHAR_SIZE, wchar_to_bytes}; +use super::array::PyCArray; use super::base::{ CDATA_BUFFER_METHODS, FfiArgValue, PyCData, StgInfo, StgInfoFlags, buffer_to_ffi_value, bytes_to_pyobject, }; use super::function::PyCFuncPtr; -use super::get_size; use super::pointer::PyCPointer; use crate::builtins::{PyByteArray, PyBytes, PyInt, PyNone, PyStr, PyType, PyTypeRef}; use crate::convert::ToPyObject; @@ -16,6 +15,10 @@ use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, Vir use alloc::borrow::Cow; use core::fmt::Debug; use num_traits::ToPrimitive; +use rustpython_host_env::ctypes::{ + SimpleStorageValue, simple_storage_value_to_bytes_endian, simple_type_align, + simple_type_pep3118_code, simple_type_size, write_simple_storage_buffer, zeroed_bytes, +}; /// Valid type codes for ctypes simple types pub(super) const SIMPLE_TYPE_CHARS: &str = cfg_select! { @@ -25,37 +28,10 @@ pub(super) const SIMPLE_TYPE_CHARS: &str = cfg_select! { _ => "cbBhHiIlLdfuzZqQPOv?g", }; -/// Convert ctypes type code to PEP 3118 format code. -/// Some ctypes codes need to be mapped to standard-size codes based on platform. -/// _ctypes_alloc_format_string_for_type -fn ctypes_code_to_pep3118(code: char) -> char { - match code { - // c_int: map based on sizeof(int) - 'i' if core::mem::size_of::() == 2 => 'h', - 'i' if core::mem::size_of::() == 4 => 'i', - 'i' if core::mem::size_of::() == 8 => 'q', - 'I' if core::mem::size_of::() == 2 => 'H', - 'I' if core::mem::size_of::() == 4 => 'I', - 'I' if core::mem::size_of::() == 8 => 'Q', - // c_long: map based on sizeof(long) - 'l' if core::mem::size_of::() == 4 => 'l', - 'l' if core::mem::size_of::() == 8 => 'q', - 'L' if core::mem::size_of::() == 4 => 'L', - 'L' if core::mem::size_of::() == 8 => 'Q', - // c_bool: map based on sizeof(bool) - typically 1 byte on all platforms - '?' if core::mem::size_of::() == 1 => '?', - '?' if core::mem::size_of::() == 2 => 'H', - '?' if core::mem::size_of::() == 4 => 'L', - '?' if core::mem::size_of::() == 8 => 'Q', - // Default: use the same code - _ => code, - } -} - /// _ctypes_alloc_format_string_for_type fn alloc_format_string_for_type(code: char, big_endian: bool) -> String { let prefix = if big_endian { ">" } else { "<" }; - let pep_code = ctypes_code_to_pep3118(code); + let pep_code = simple_type_pep3118_code(code); format!("{prefix}{pep_code}") } @@ -91,8 +67,8 @@ fn new_simple_type( ))); } - let size = get_size(&tp_str); - Ok(PyCSimple(PyCData::from_bytes(vec![0u8; size], None))) + let size = simple_type_size(&tp_str).expect("invalid ctypes simple type"); + Ok(PyCSimple(PyCData::from_bytes(zeroed_bytes(size), None))) } fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResult { @@ -430,14 +406,11 @@ impl PyCSimpleType { if let Some(funcptr) = value.downcast_ref::() { let ptr_val = { let buffer = funcptr._base.buffer.read(); - buffer - .first_chunk::<{ size_of::() }>() - .copied() - .map_or(0, usize::from_ne_bytes) + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) }; return Ok(CArgObject { tag: b'P', - value: FfiArgValue::Pointer(ptr_val), + value: FfiArgValue::pointer(ptr_val), obj: value.clone(), size: 0, offset: 0, @@ -450,14 +423,11 @@ impl PyCSimpleType { if matches!(value_type_code.as_deref(), Some("z" | "Z")) { let ptr_val = { let buffer = simple.0.buffer.read(); - buffer - .first_chunk::<{ size_of::() }>() - .copied() - .map_or(0, usize::from_ne_bytes) + rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer) }; return Ok(CArgObject { tag: b'Z', - value: FfiArgValue::Pointer(ptr_val), + value: FfiArgValue::pointer(ptr_val), obj: value.clone(), size: 0, offset: 0, @@ -470,7 +440,7 @@ impl PyCSimpleType { Some("O") => { return Ok(CArgObject { tag: b'O', - value: FfiArgValue::Pointer(value.get_id()), + value: FfiArgValue::pointer(value.get_id()), obj: value, size: 0, offset: 0, @@ -579,8 +549,8 @@ impl Initializer for PyCSimpleType { } // Initialize StgInfo - let size = super::get_size(&type_str); - let align = super::get_align(&type_str); + let size = simple_type_size(&type_str).expect("invalid ctypes simple type"); + let align = simple_type_align(&type_str).expect("invalid ctypes simple type"); let mut stg_info = StgInfo::new(size, align); // Set format for PEP 3118 buffer protocol @@ -739,210 +709,173 @@ fn value_to_bytes_endian( swapped: bool, vm: &VirtualMachine, ) -> Vec { - // Helper macro for endian conversion - macro_rules! to_bytes { - ($val:expr) => { - if swapped { - // Use opposite endianness - #[cfg(target_endian = "little")] - { - $val.to_be_bytes().to_vec() - } - #[cfg(target_endian = "big")] - { - $val.to_le_bytes().to_vec() - } - } else { - $val.to_ne_bytes().to_vec() - } - }; - } - - match _type_ { + let storage_value = match _type_ { "c" => { // c_char - single byte (bytes, bytearray, or int 0-255) if let Some(bytes) = value.downcast_ref::() && !bytes.is_empty() { - return vec![bytes.as_bytes()[0]]; - } - if let Some(bytearray) = value.downcast_ref::() { + SimpleStorageValue::Byte(bytes.as_bytes()[0]) + } else if let Some(bytearray) = value.downcast_ref::() { let buf = bytearray.borrow_buf(); if !buf.is_empty() { - return vec![buf[0]]; + SimpleStorageValue::Byte(buf[0]) + } else { + SimpleStorageValue::Zero } - } - if let Ok(int_val) = value.try_int(vm) + } else if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_u8() { - return vec![v]; + SimpleStorageValue::Byte(v) + } else { + SimpleStorageValue::Zero } - vec![0] } "u" => { // c_wchar - platform-dependent size (2 on Windows, 4 on Unix) if let Some(s) = value.downcast_ref::() { let mut cps = s.as_wtf8().code_points(); if let (Some(c), None) = (cps.next(), cps.next()) { - let mut buffer = vec![0u8; WCHAR_SIZE]; - wchar_to_bytes(c.to_u32(), &mut buffer); - if swapped { - buffer.reverse(); - } - return buffer; + SimpleStorageValue::Wchar(c.to_u32()) + } else { + SimpleStorageValue::Zero } + } else { + SimpleStorageValue::Zero } - vec![0; WCHAR_SIZE] } "b" => { // c_byte - signed char (1 byte) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as i8; - return vec![v as u8]; + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0] } "B" => { // c_ubyte - unsigned char (1 byte) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as u8; - return vec![v]; + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0] } "h" => { // c_short (2 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as i16; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 2] } "H" => { // c_ushort (2 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as u16; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 2] } "i" => { // c_int (4 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as i32; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 4] } "I" => { // c_uint (4 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as u32; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 4] } "l" => { // c_long (platform dependent) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_long; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - const SIZE: usize = core::mem::size_of::(); - vec![0; SIZE] } "L" => { // c_ulong (platform dependent) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_ulong; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - const SIZE: usize = core::mem::size_of::(); - vec![0; SIZE] } "q" => { // c_longlong (8 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as i64; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 8] } "Q" => { // c_ulonglong (8 bytes) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().expect("int too large") as u64; - return to_bytes!(v); + SimpleStorageValue::Signed(int_val.as_bigint().to_i128().expect("int too large")) + } else { + SimpleStorageValue::Zero } - vec![0; 8] } "f" => { // c_float (4 bytes) - also accepts int if let Ok(float_val) = value.try_float(vm) { - return to_bytes!(float_val.to_f64() as f32); - } - if let Ok(int_val) = value.try_int(vm) + SimpleStorageValue::Float(float_val.to_f64()) + } else if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_f64() { - return to_bytes!(v as f32); + SimpleStorageValue::Float(v) + } else { + SimpleStorageValue::Zero } - vec![0; 4] } "d" => { // c_double (8 bytes) - also accepts int if let Ok(float_val) = value.try_float(vm) { - return to_bytes!(float_val.to_f64()); - } - if let Ok(int_val) = value.try_int(vm) + SimpleStorageValue::Float(float_val.to_f64()) + } else if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_f64() { - return to_bytes!(v); + SimpleStorageValue::Float(v) + } else { + SimpleStorageValue::Zero } - vec![0; 8] } "g" => { // long double - platform dependent size // Store as f64, zero-pad to platform long double size // Note: This may lose precision on platforms where long double > 64 bits - let f64_val = if let Ok(float_val) = value.try_float(vm) { + let value = if let Ok(float_val) = value.try_float(vm) { float_val.to_f64() } else if let Ok(int_val) = value.try_int(vm) { int_val.as_bigint().to_f64().unwrap_or(0.0) } else { 0.0 }; - let f64_bytes = if swapped { - #[cfg(target_endian = "little")] - { - f64_val.to_be_bytes().to_vec() - } - #[cfg(target_endian = "big")] - { - f64_val.to_le_bytes().to_vec() - } - } else { - f64_val.to_ne_bytes().to_vec() - }; - // Pad to long double size - let long_double_size = super::get_size("g"); - let mut result = f64_bytes; - result.resize(long_double_size, 0); - result + SimpleStorageValue::Float(value) } "?" => { // c_bool (1 byte) if let Ok(b) = value.to_owned().try_to_bool(vm) { - return vec![if b { 1 } else { 0 }]; + SimpleStorageValue::Bool(b) + } else { + SimpleStorageValue::Zero } - vec![0] } "v" => { // VARIANT_BOOL: True = 0xFFFF (-1 as i16), False = 0x0000 if let Ok(b) = value.to_owned().try_to_bool(vm) { - let val: i16 = if b { -1 } else { 0 }; - return to_bytes!(val); + SimpleStorageValue::Bool(b) + } else { + SimpleStorageValue::Zero } - vec![0; 2] } "P" => { // c_void_p - pointer type (platform pointer size) @@ -951,9 +884,10 @@ fn value_to_bytes_endian( .as_bigint() .to_usize() .expect("int too large for pointer"); - return to_bytes!(v); + SimpleStorageValue::Pointer(v) + } else { + SimpleStorageValue::Zero } - vec![0; core::mem::size_of::()] } "z" => { // c_char_p - pointer to char (stores pointer value from int) @@ -963,9 +897,10 @@ fn value_to_bytes_endian( .as_bigint() .to_usize() .expect("int too large for pointer"); - return to_bytes!(v); + SimpleStorageValue::Pointer(v) + } else { + SimpleStorageValue::Zero } - vec![0; core::mem::size_of::()] } "Z" => { // c_wchar_p - pointer to wchar_t (stores pointer value from int) @@ -975,19 +910,20 @@ fn value_to_bytes_endian( .as_bigint() .to_usize() .expect("int too large for pointer"); - return to_bytes!(v); + SimpleStorageValue::Pointer(v) + } else { + SimpleStorageValue::Zero } - vec![0; core::mem::size_of::()] } "O" => { // py_object - store object id as non-zero marker // The actual object is stored in _objects // Use object's id as a non-zero placeholder (indicates non-NULL) - let id = value.get_id(); - to_bytes!(id) + SimpleStorageValue::ObjectId(value.get_id()) } - _ => vec![0], - } + _ => SimpleStorageValue::Zero, + }; + simple_storage_value_to_bytes_endian(_type_, storage_value, swapped) } /// Check if value is a c_char array or pointer(c_char) @@ -1049,7 +985,7 @@ impl Constructor for PyCSimple { if _type_ == "z" { if let Some(bytes) = v.downcast_ref::() { let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm); - let buffer = ptr.to_ne_bytes().to_vec(); + let buffer = rustpython_host_env::ctypes::pointer_bytes(ptr); let cdata = PyCData::from_bytes(buffer, Some(v.clone())); *cdata.base.write() = Some(kept_alive); return Self(cdata).into_ref_with_type(vm, cls).map(Into::into); @@ -1058,7 +994,7 @@ impl Constructor for PyCSimple { && let Some(s) = v.downcast_ref::() { let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_wtf8(), vm); - let buffer = ptr.to_ne_bytes().to_vec(); + let buffer = rustpython_host_env::ctypes::pointer_bytes(ptr); let cdata = PyCData::from_bytes(buffer, Some(holder)); return Self(cdata).into_ref_with_type(vm, cls).map(Into::into); } @@ -1166,58 +1102,30 @@ impl PyCSimple { // Special handling for c_char_p (z) and c_wchar_p (Z) // z_get, Z_get - dereference pointer to get string if type_code == "z" { - // c_char_p: read pointer from buffer, dereference to get bytes string let buffer = zelf.0.buffer.read(); - let ptr = super::base::read_ptr_from_buffer(&buffer); - if ptr == 0 { - return Ok(vm.ctx.none()); - } - // Read null-terminated string at the address - unsafe { - let cstr = core::ffi::CStr::from_ptr(ptr as _); - return Ok(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()); - } + return match rustpython_host_env::ctypes::decode_type_code(&type_code, &buffer) { + rustpython_host_env::ctypes::DecodedValue::Bytes(value) => { + Ok(vm.ctx.new_bytes(value).into()) + } + rustpython_host_env::ctypes::DecodedValue::None => Ok(vm.ctx.none()), + _ => unreachable!("decode_type_code('z') only returns bytes or None"), + }; } if type_code == "Z" { - // c_wchar_p: read pointer from buffer, dereference to get wide string let buffer = zelf.0.buffer.read(); - let ptr = super::base::read_ptr_from_buffer(&buffer); - if ptr == 0 { - return Ok(vm.ctx.none()); - } - // Read null-terminated wide string at the address - // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide for surrogate pairs - // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 - unsafe { - let w_ptr = ptr as *const libc::wchar_t; - let len = libc::wcslen(w_ptr); - let wchars = core::slice::from_raw_parts(w_ptr, len); - #[cfg(windows)] - { - use rustpython_common::wtf8::Wtf8Buf; - let wide: Vec = wchars.to_vec(); - let wtf8 = Wtf8Buf::from_wide(&wide); - return Ok(vm.ctx.new_str(wtf8).into()); - } - #[cfg(not(windows))] - { - #[allow( - clippy::useless_conversion, - reason = "wchar_t is i32 on some platforms and u32 on others" - )] - let s: String = wchars - .iter() - .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) - .collect(); - return Ok(vm.ctx.new_str(s).into()); + return match rustpython_host_env::ctypes::decode_type_code(&type_code, &buffer) { + rustpython_host_env::ctypes::DecodedValue::String(value) => { + Ok(vm.ctx.new_str(value).into()) } - } + rustpython_host_env::ctypes::DecodedValue::None => Ok(vm.ctx.none()), + _ => unreachable!("decode_type_code('Z') only returns string or None"), + }; } // O_get: py_object - read PyObject pointer from buffer if type_code == "O" { let buffer = zelf.0.buffer.read(); - let ptr = super::base::read_ptr_from_buffer(&buffer); + let ptr = rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer); if ptr == 0 { return Err(vm.new_value_error("PyObject is NULL")); } @@ -1243,23 +1151,7 @@ impl PyCSimple { }; let cls_ref = cls.to_owned(); - bytes_to_pyobject(&cls_ref, &buffer_data, vm).or_else(|_| { - // Fallback: return bytes as integer based on type - match type_code.as_str() { - "c" => { - if !buffer.is_empty() { - Ok(vm.ctx.new_bytes(vec![buffer[0]]).into()) - } else { - Ok(vm.ctx.new_bytes(vec![0]).into()) - } - } - "?" => { - let val = buffer.first().copied().unwrap_or(0); - Ok(vm.ctx.new_bool(val != 0).into()) - } - _ => Ok(vm.ctx.new_int(0).into()), - } - }) + Ok(bytes_to_pyobject(&cls_ref, &buffer_data, vm)) } #[pygetset(setter)] @@ -1281,7 +1173,8 @@ impl PyCSimple { if type_code == "z" { if let Some(bytes) = value.downcast_ref::() { let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm); - *zelf.0.buffer.write() = alloc::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.buffer.write() = + alloc::borrow::Cow::Owned(rustpython_host_env::ctypes::pointer_bytes(ptr)); *zelf.0.objects.write() = Some(value); *zelf.0.base.write() = Some(kept_alive); return Ok(()); @@ -1290,7 +1183,8 @@ impl PyCSimple { && let Some(s) = value.downcast_ref::() { let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_wtf8(), vm); - *zelf.0.buffer.write() = alloc::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.buffer.write() = + alloc::borrow::Cow::Owned(rustpython_host_env::ctypes::pointer_bytes(ptr)); *zelf.0.objects.write() = Some(holder); return Ok(()); } @@ -1310,20 +1204,7 @@ impl PyCSimple { // If the buffer is borrowed (from shared memory), write in-place // Otherwise replace with new owned buffer let mut buffer = zelf.0.buffer.write(); - match &mut *buffer { - Cow::Borrowed(slice) => { - // SAFETY: For from_buffer, the slice points to writable shared memory. - // Python's from_buffer requires writable buffer, so this is safe. - let ptr = slice.as_ptr() as *mut u8; - let len = slice.len().min(buffer_bytes.len()); - unsafe { - core::ptr::copy_nonoverlapping(buffer_bytes.as_ptr(), ptr, len); - } - } - Cow::Owned(vec) => { - vec.copy_from_slice(&buffer_bytes); - } - } + write_simple_storage_buffer(&mut buffer, &buffer_bytes); // For c_char_p (type "z"), c_wchar_p (type "Z"), and py_object (type "O"), // keep the reference in _objects @@ -1375,53 +1256,13 @@ impl PyCSimple { /// The value must be kept alive until after the FFI call completes. pub(crate) fn to_ffi_value( &self, - ty: libffi::middle::Type, + ty: rustpython_host_env::ctypes::FfiType, _vm: &VirtualMachine, ) -> Option { let buffer = self.0.buffer.read(); - let bytes: &[u8] = &buffer; - - let ret = if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { - let byte = *bytes.first()?; - FfiArgValue::U8(byte) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { - let byte = *bytes.first()?; - FfiArgValue::I8(byte as i8) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<2>()?; - FfiArgValue::U16(u16::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<2>()?; - FfiArgValue::I16(i16::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<4>()?; - FfiArgValue::U32(u32::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<4>()?; - FfiArgValue::I32(i32::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<8>()?; - FfiArgValue::U64(u64::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<8>()?; - FfiArgValue::I64(i64::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f32().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<4>()?; - FfiArgValue::F32(f32::from_ne_bytes(bytes)) - } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f64().as_raw_ptr()) { - let bytes = *bytes.first_chunk::<8>()?; - FfiArgValue::F64(f64::from_ne_bytes(bytes)) - } else if core::ptr::eq( - ty.as_raw_ptr(), - libffi::middle::Type::pointer().as_raw_ptr(), - ) { - let bytes = *buffer.first_chunk::<{ size_of::() }>()?; - let val = usize::from_ne_bytes(bytes); - FfiArgValue::Pointer(val) - } else { - return None; - }; - Some(ret) + Some(FfiArgValue::Scalar( + rustpython_host_env::ctypes::ffi_value_from_type(&buffer, ty)?, + )) } } diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index a0056523746..f178ae85e3a 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -23,6 +23,7 @@ use crate::{ AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyModule, }; pub use _io::{OpenArgs, io_open as open}; +use rustpython_host_env::io as host_io; fn file_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult { file.get_attr("closed", vm)?.try_to_bool(vm) @@ -148,13 +149,7 @@ mod _io { #[allow(clippy::let_and_return)] fn validate_whence(whence: i32) -> bool { - let x = (0..=2).contains(&whence); - cfg_select! { - any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux") => { - x || matches!(whence, libc::SEEK_DATA | libc::SEEK_HOLE) - } - _ => x, - } + host_io::validate_whence(whence) } fn ensure_unclosed(file: &PyObject, msg: &str, vm: &VirtualMachine) -> PyResult<()> { @@ -178,7 +173,7 @@ mod _io { if exc.fast_isinstance(vm.ctx.exceptions.os_error) && let Ok(errno_attr) = exc.as_object().get_attr("errno", vm) && let Ok(errno_val) = i32::try_from_object(vm, errno_attr) - && errno_val == libc::EINTR + && host_io::is_interrupted_errno(errno_val) { vm.check_signals()?; return Ok(None); @@ -5081,7 +5076,8 @@ mod _io { // check file descriptor validity #[cfg(all(unix, feature = "host_env"))] if let Ok(crate::ospath::OsPathOrFd::Fd(fd)) = file.clone().try_into_value(vm) { - nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD).map_err(|_| vm.new_last_errno_error())?; + rustpython_host_env::fcntl::validate_fd(fd.as_raw()) + .map_err(|_| vm.new_last_errno_error())?; } // Construct a RawIO (subclass of RawIOBase) @@ -5343,108 +5339,7 @@ mod fileio { types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - use std::io::Read; - - bitflags::bitflags! { - #[derive(Copy, Clone, Debug, PartialEq)] - struct Mode: u8 { - const CREATED = 0b0001; - const READABLE = 0b0010; - const WRITABLE = 0b0100; - const APPENDING = 0b1000; - } - } - - enum ModeError { - Invalid, - BadRwa, - } - - impl ModeError { - fn error_msg(&self, mode_str: &str) -> String { - match self { - Self::Invalid => format!("invalid mode: {mode_str}"), - Self::BadRwa => { - "Must have exactly one of create/read/write/append mode and at most one plus" - .to_owned() - } - } - } - } - - fn compute_mode(mode_str: &str) -> Result<(Mode, i32), ModeError> { - let mut flags = 0; - let mut plus = false; - let mut rwa = false; - let mut mode = Mode::empty(); - for c in mode_str.bytes() { - match c { - b'x' => { - if rwa { - return Err(ModeError::BadRwa); - } - rwa = true; - mode.insert(Mode::WRITABLE | Mode::CREATED); - flags |= libc::O_EXCL | libc::O_CREAT; - } - b'r' => { - if rwa { - return Err(ModeError::BadRwa); - } - rwa = true; - mode.insert(Mode::READABLE); - } - b'w' => { - if rwa { - return Err(ModeError::BadRwa); - } - rwa = true; - mode.insert(Mode::WRITABLE); - flags |= libc::O_CREAT | libc::O_TRUNC; - } - b'a' => { - if rwa { - return Err(ModeError::BadRwa); - } - rwa = true; - mode.insert(Mode::WRITABLE | Mode::APPENDING); - flags |= libc::O_APPEND | libc::O_CREAT; - } - b'+' => { - if plus { - return Err(ModeError::BadRwa); - } - plus = true; - mode.insert(Mode::READABLE | Mode::WRITABLE); - } - b'b' => {} - _ => return Err(ModeError::Invalid), - } - } - - if !rwa { - return Err(ModeError::BadRwa); - } - - if mode.contains(Mode::READABLE | Mode::WRITABLE) { - flags |= libc::O_RDWR - } else if mode.contains(Mode::READABLE) { - flags |= libc::O_RDONLY - } else { - flags |= libc::O_WRONLY - } - - #[cfg(windows)] - { - flags |= libc::O_BINARY | libc::O_NOINHERIT; - } - #[cfg(unix)] - { - flags |= libc::O_CLOEXEC - } - - Ok((mode, flags as _)) - } + use rustpython_host_env::io as host_io; #[pyattr] #[pyclass(module = "_io", name, base = _RawIOBase)] @@ -5453,7 +5348,7 @@ mod fileio { _base: _RawIOBase, fd: AtomicCell, closefd: AtomicCell, - mode: AtomicCell, + mode: AtomicCell, seekable: AtomicCell>, blksize: AtomicCell, finalizing: AtomicCell, @@ -5477,7 +5372,7 @@ mod fileio { _base: Default::default(), fd: AtomicCell::new(-1), closefd: AtomicCell::new(true), - mode: AtomicCell::new(Mode::empty()), + mode: AtomicCell::new(host_io::FileMode::empty()), seekable: AtomicCell::new(None), blksize: AtomicCell::new(super::DEFAULT_BUFFER_SIZE as _), finalizing: AtomicCell::new(false), @@ -5516,8 +5411,10 @@ mod fileio { .mode .unwrap_or_else(|| PyUtf8Str::from("rb").into_ref(&vm.ctx)); let mode_str = mode_obj.as_str(); - let (mode, flags) = - compute_mode(mode_str).map_err(|e| vm.new_value_error(e.error_msg(mode_str)))?; + let parsed = host_io::parse_fileio_mode(mode_str) + .map_err(|e| vm.new_value_error(e.error_msg(mode_str)))?; + let mode = parsed.mode; + let flags = parsed.flags; zelf.mode.store(mode); let (fd, filename) = if let Some(fd) = arg_fd { @@ -5542,9 +5439,9 @@ mod fileio { } else { let path = OsPath::try_from_fspath(name.clone(), vm)?; #[cfg(any(unix, target_os = "wasi"))] - let fd = crt_fd::open(&path.clone().into_cstring(vm)?, flags, 0o666); + let fd = host_io::open_path(&path.clone().into_cstring(vm)?, flags, 0o666); #[cfg(windows)] - let fd = crt_fd::wopen(&path.to_wide_cstring(vm)?, flags, 0o666); + let fd = host_io::open_path(&path.to_wide_cstring(vm)?, flags, 0o666); let filename = OsPathOrFd::Path(path); match fd { Ok(fd) => (fd.into_raw(), Some(filename)), @@ -5561,48 +5458,17 @@ mod fileio { // TODO: _Py_set_inheritable - let fd_fstat = rustpython_host_env::fileutils::fstat(fd); - - #[cfg(windows)] - { - if let Err(err) = fd_fstat { - // If the fd is invalid, prevent destructor from trying to close it - if err.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_INVALID_HANDLE as i32) - { - zelf.fd.store(-1); + match host_io::inspect_file_target(fd) { + Ok(info) => { + if let Some(blksize) = info.blksize { + zelf.blksize.store(blksize); } - return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } - } - #[cfg(any(unix, target_os = "wasi"))] - { - match fd_fstat { - Ok(status) => { - if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR { - // If fd was passed by user, don't close it on error - if !fd_is_own { - zelf.fd.store(-1); - } - let err = std::io::Error::from_raw_os_error(libc::EISDIR); - return Err(OSErrorBuilder::with_filename(&err, filename, vm)); - } - // Store st_blksize for _blksize property - if status.st_blksize > 1 { - #[allow( - clippy::useless_conversion, - reason = "needed for 32-bit platforms" - )] - zelf.blksize.store(i64::from(status.st_blksize)); - } - } - Err(err) => { - if err.raw_os_error() == Some(libc::EBADF) { - // fd is invalid, prevent destructor from trying to close it - zelf.fd.store(-1); - return Err(OSErrorBuilder::with_filename(&err, filename, vm)); - } + Err(err) => { + if host_io::should_forget_fd_after_inspect_error(&err, fd_is_own) { + zelf.fd.store(-1); } + return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } } @@ -5616,8 +5482,8 @@ mod fileio { return Err(e); } - if mode.contains(Mode::APPENDING) { - let _ = os::lseek(fd, 0, libc::SEEK_END, vm); + if mode.contains(host_io::FileMode::APPENDING) { + let _ = host_io::seek_to_end(fd); } Ok(()) @@ -5698,7 +5564,7 @@ mod fileio { if self.fd.load() < 0 { return Err(io_closed_error(vm)); } - Ok(self.mode.load().contains(Mode::READABLE)) + Ok(self.mode.load().contains(host_io::FileMode::READABLE)) } #[pymethod] @@ -5706,33 +5572,12 @@ mod fileio { if self.fd.load() < 0 { return Err(io_closed_error(vm)); } - Ok(self.mode.load().contains(Mode::WRITABLE)) + Ok(self.mode.load().contains(host_io::FileMode::WRITABLE)) } #[pygetset] fn mode(&self) -> &'static str { - let mode = self.mode.load(); - if mode.contains(Mode::CREATED) { - if mode.contains(Mode::READABLE) { - "xb+" - } else { - "xb" - } - } else if mode.contains(Mode::APPENDING) { - if mode.contains(Mode::READABLE) { - "ab+" - } else { - "ab" - } - } else if mode.contains(Mode::READABLE) { - if mode.contains(Mode::WRITABLE) { - "rb+" - } else { - "rb" - } - } else { - "wb" - } + self.mode.load().raw_mode() } #[pymethod] @@ -5741,7 +5586,7 @@ mod fileio { read_byte: OptionalSize, vm: &VirtualMachine, ) -> PyResult>> { - if !zelf.mode.load().contains(Mode::READABLE) { + if !zelf.mode.load().contains(host_io::FileMode::READABLE) { return Err(new_unsupported_operation( vm, "File or stream is not readable".to_owned(), @@ -5752,14 +5597,14 @@ mod fileio { let mut bytes = vec![0; read_byte]; // Loop on EINTR (PEP 475) let n = loop { - match vm.allow_threads(|| crt_fd::read(handle, &mut bytes)) { + match vm.allow_threads(|| host_io::read_once(handle, &mut bytes)) { Ok(n) => break n, - Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + Err(e) if host_io::is_interrupted_error(&e) => { vm.check_signals()?; continue; } // Non-blocking mode: return None if EAGAIN - Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + Err(e) if host_io::is_would_block_error(&e) => { return Ok(None); } Err(e) => return Err(Self::io_error(zelf, e, vm)), @@ -5771,17 +5616,14 @@ mod fileio { let mut bytes = vec![]; // Loop on EINTR (PEP 475) loop { - match vm.allow_threads(|| { - let mut h = handle; - h.read_to_end(&mut bytes) - }) { - Ok(_) => break, - Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + match vm.allow_threads(|| host_io::read_all(handle, &mut bytes)) { + Ok(()) => break, + Err(e) if host_io::is_interrupted_error(&e) => { vm.check_signals()?; continue; } // Non-blocking mode: return None if EAGAIN (only if no data read yet) - Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + Err(e) if host_io::is_would_block_error(&e) => { if bytes.is_empty() { return Ok(None); } @@ -5802,7 +5644,7 @@ mod fileio { obj: ArgMemoryBuffer, vm: &VirtualMachine, ) -> PyResult> { - if !zelf.mode.load().contains(Mode::READABLE) { + if !zelf.mode.load().contains(host_io::FileMode::READABLE) { return Err(new_unsupported_operation( vm, "File or stream is not readable".to_owned(), @@ -5814,14 +5656,14 @@ mod fileio { let mut buf = obj.borrow_buf_mut(); // Loop on EINTR (PEP 475) let ret = loop { - match vm.allow_threads(|| crt_fd::read(handle, &mut buf)) { + match vm.allow_threads(|| host_io::read_once(handle, &mut buf)) { Ok(n) => break n, - Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + Err(e) if host_io::is_interrupted_error(&e) => { vm.check_signals()?; continue; } // Non-blocking mode: return None if EAGAIN - Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + Err(e) if host_io::is_would_block_error(&e) => { return Ok(None); } Err(e) => return Err(Self::io_error(zelf, e, vm)), @@ -5837,7 +5679,7 @@ mod fileio { obj: ArgBytesLike, vm: &VirtualMachine, ) -> PyResult> { - if !zelf.mode.load().contains(Mode::WRITABLE) { + if !zelf.mode.load().contains(host_io::FileMode::WRITABLE) { return Err(new_unsupported_operation( vm, "File or stream is not writable".to_owned(), @@ -5848,14 +5690,14 @@ mod fileio { // Loop on EINTR (PEP 475) let len = loop { - match obj.with_ref(|b| vm.allow_threads(|| crt_fd::write(handle, b))) { + match obj.with_ref(|b| vm.allow_threads(|| host_io::write_once(handle, b))) { Ok(n) => break n, - Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + Err(e) if host_io::is_interrupted_error(&e) => { vm.check_signals()?; continue; } // Non-blocking mode: return None if EAGAIN - Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => return Ok(None), + Err(e) if host_io::is_would_block_error(&e) => return Ok(None), Err(e) => return Err(Self::io_error(zelf, e, vm)), } }; @@ -5877,7 +5719,7 @@ mod fileio { } let fd = zelf.fd.swap(-1); let close_err = if fd >= 0 { - crt_fd::close(unsafe { crt_fd::Owned::from_raw(fd) }) + host_io::close_owned_fd(unsafe { crt_fd::Owned::from_raw(fd) }) .map_err(|err| Self::io_error(zelf, err, vm)) .err() } else { @@ -5897,7 +5739,7 @@ mod fileio { fn seekable(&self, vm: &VirtualMachine) -> PyResult { let fd = self.get_fd(vm)?; Ok(self.seekable.load().unwrap_or_else(|| { - let seekable = os::lseek(fd, 0, libc::SEEK_CUR, vm).is_ok(); + let seekable = host_io::is_seekable(fd); self.seekable.store(Some(seekable)); seekable })) @@ -5914,13 +5756,13 @@ mod fileio { let fd = self.get_fd(vm)?; let offset = get_offset(offset, vm)?; - os::lseek(fd, offset, how, vm) + host_io::seek(fd, offset, how).map_err(|e| e.into_pyexception(vm)) } #[pymethod] fn tell(&self, vm: &VirtualMachine) -> PyResult { let fd = self.get_fd(vm)?; - os::lseek(fd, 0, libc::SEEK_CUR, vm) + host_io::tell(fd).map_err(|e| e.into_pyexception(vm)) } #[pymethod] @@ -5928,7 +5770,7 @@ mod fileio { let fd = self.get_fd(vm)?; let len = match len.flatten() { Some(l) => get_offset(l, vm)?, - None => os::lseek(fd, 0, libc::SEEK_CUR, vm)?, + None => host_io::tell(fd).map_err(|e| e.into_pyexception(vm))?, }; os::ftruncate(fd, len).map_err(|e| e.into_pyexception(vm))?; Ok(len) @@ -5937,7 +5779,7 @@ mod fileio { #[pymethod] fn isatty(&self, vm: &VirtualMachine) -> PyResult { let fd = self.fileno(vm)?; - Ok(os::isatty(fd)) + Ok(host_io::isatty(fd)) } #[pymethod] @@ -6001,45 +5843,19 @@ mod winconsoleio { types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - use windows_sys::Win32::{ - Foundation::{self, GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, - Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte}, - Storage::FileSystem::{ - CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFullPathNameW, OPEN_EXISTING, - }, - System::Console::{ - GetConsoleMode, GetNumberOfConsoleInputEvents, ReadConsoleW, WriteConsoleW, - }, - }; - - type HANDLE = Foundation::HANDLE; + use rustpython_host_env::io as host_io; + use rustpython_host_env::nt as host_nt; + type HANDLE = host_nt::Handle; const SMALLBUF: usize = 4; const BUFMAX: usize = 32 * 1024 * 1024; fn handle_from_fd(fd: i32) -> HANDLE { - unsafe { rustpython_host_env::suppress_iph!(libc::get_osfhandle(fd)) as HANDLE } + host_nt::handle_from_fd(fd) } fn is_invalid_handle(handle: HANDLE) -> bool { - handle == INVALID_HANDLE_VALUE || handle.is_null() - } - - /// Check if a HANDLE is a console and what type ('r', 'w', or '\0'). - fn get_console_type(handle: HANDLE) -> char { - if is_invalid_handle(handle) { - return '\0'; - } - let mut mode: u32 = 0; - if unsafe { GetConsoleMode(handle, &mut mode) } == 0 { - return '\0'; - } - let mut peek_count: u32 = 0; - if unsafe { GetNumberOfConsoleInputEvents(handle, &mut peek_count) } != 0 { - 'r' - } else { - 'w' - } + host_nt::is_invalid_handle(handle) } /// Check if a Python object (fd or path string) refers to a console. @@ -6047,11 +5863,7 @@ mod winconsoleio { pub(super) fn pyio_get_console_type(path_or_fd: &PyObject, vm: &VirtualMachine) -> char { // Try as integer fd first if let Ok(fd) = i32::try_from_object(vm, path_or_fd.to_owned()) { - if fd >= 0 { - let handle = handle_from_fd(fd); - return get_console_type(handle); - } - return '\0'; + return host_nt::console_type_from_fd(fd); } // Try as string path @@ -6062,80 +5874,7 @@ mod winconsoleio { // Surrogate strings can't be console device names return '\0'; }; - - if name_str.eq_ignore_ascii_case("CONIN$") { - return 'r'; - } - if name_str.eq_ignore_ascii_case("CONOUT$") { - return 'w'; - } - if name_str.eq_ignore_ascii_case("CON") { - return 'x'; - } - - // Resolve full path and check for console device names - let wide: Vec = name_str.encode_utf16().chain(core::iter::once(0)).collect(); - let mut buf = [0u16; 260]; // MAX_PATH - let length = unsafe { - GetFullPathNameW( - wide.as_ptr(), - buf.len() as u32, - buf.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - if length == 0 || length as usize > buf.len() { - return '\0'; - } - let full_path = &buf[..length as usize]; - // Skip \\?\ or \\.\ prefix - let path_part = if full_path.len() >= 4 - && full_path[0] == b'\\' as u16 - && full_path[1] == b'\\' as u16 - && (full_path[2] == b'.' as u16 || full_path[2] == b'?' as u16) - && full_path[3] == b'\\' as u16 - { - &full_path[4..] - } else { - full_path - }; - - let path_str = String::from_utf16_lossy(path_part); - if path_str.eq_ignore_ascii_case("CONIN$") { - 'r' - } else if path_str.eq_ignore_ascii_case("CONOUT$") { - 'w' - } else if path_str.eq_ignore_ascii_case("CON") { - 'x' - } else { - '\0' - } - } - - /// Find the last valid UTF-8 boundary in a byte slice. - fn find_last_utf8_boundary(buf: &[u8], len: usize) -> usize { - let len = len.min(buf.len()); - for count in 1..=4.min(len) { - let c = buf[len - count]; - if c < 0x80 { - return len; - } - if c >= 0xc0 { - let expected = if c < 0xe0 { - 2 - } else if c < 0xf0 { - 3 - } else { - 4 - }; - if count < expected { - // Incomplete multibyte sequence - return len - count; - } - return len; - } - } - len + host_nt::console_type_from_name(name_str) } #[pyattr] @@ -6282,55 +6021,10 @@ mod winconsoleio { .chain(core::iter::once(0)) .collect::>(); - let access = if writable { - GENERIC_WRITE - } else { - GENERIC_READ - }; - - // Try read/write first, fall back to specific access - let mut handle: HANDLE = unsafe { - CreateFileW( - wide.as_ptr(), - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - if is_invalid_handle(handle) { - handle = unsafe { - CreateFileW( - wide.as_ptr(), - access, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - } - - if is_invalid_handle(handle) { - return Err(std::io::Error::last_os_error().to_pyexception(vm)); - } - - let osf_flags = if writable { - libc::O_WRONLY | libc::O_BINARY | 0x80 /* O_NOINHERIT */ - } else { - libc::O_RDONLY | libc::O_BINARY | 0x80 /* O_NOINHERIT */ - }; + fd = host_nt::open_console_path_fd(wide.as_ptr(), writable) + .map_err(|err| err.to_pyexception(vm))?; - fd = unsafe { libc::open_osfhandle(handle as isize, osf_flags) }; - if fd < 0 { - unsafe { - Foundation::CloseHandle(handle); - } - return Err(std::io::Error::last_os_error().to_pyexception(vm)); - } + _name_wide = Some(wide); } else { // When opened by fd, never close the fd (user owns it) zelf.closefd.store(false); @@ -6341,7 +6035,7 @@ mod winconsoleio { // Validate console type if console_type == '\0' { let handle = handle_from_fd(fd); - console_type = get_console_type(handle); + console_type = host_nt::console_type(handle); } if console_type == '\0' { @@ -6371,9 +6065,8 @@ mod winconsoleio { fn internal_close(zelf: &WindowsConsoleIO) { let fd = zelf.fd.swap(-1); if fd >= 0 && zelf.closefd.load() { - unsafe { - libc::close(fd); - } + let _ = + host_io::close_owned_fd(unsafe { crate::host_env::crt_fd::Owned::from_raw(fd) }); } } @@ -6482,12 +6175,9 @@ mod winconsoleio { } let fd = zelf.fd.swap(-1); let close_err: Option = if fd >= 0 { - let result = unsafe { libc::close(fd) }; - if result < 0 { - Some(std::io::Error::last_os_error().into_pyexception(vm)) - } else { - None - } + host_io::close_owned_fd(unsafe { crate::host_env::crt_fd::Owned::from_raw(fd) }) + .err() + .map(|e| e.into_pyexception(vm)) } else { None }; @@ -6554,116 +6244,18 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - // Each character may take up to 4 bytes in UTF-8. - let mut wlen = (len / 4) as u32; - if wlen == 0 { - wlen = 1; - } - let dest = &mut *buf_ref; - - // Copy from internal buffer first - let mut read_len = { - let mut buf = self.buf.lock(); - Self::copy_from_buf(&mut buf, dest) - }; - if read_len > 0 { - wlen = wlen.saturating_sub(1); - } - if read_len >= len || wlen == 0 { - return Ok(read_len); - } - - // Read from console - let mut wbuf = vec![0u16; wlen as usize]; - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wlen, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 { - return Ok(read_len); - } - - // Check for Ctrl+Z (EOF) - if nread > 0 && wbuf[0] == 0x1A { - return Ok(read_len); - } - - // Convert wchar to UTF-8 - let remaining = len - read_len; - let u8n; - if remaining < 4 { - // Buffer the result in the internal small buffer - let mut buf = self.buf.lock(); - let converted = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - buf.as_mut_ptr() as _, - SMALLBUF as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if converted > 0 { - u8n = Self::copy_from_buf(&mut buf, &mut dest[read_len..]) as i32; - } else { - u8n = 0; - } - } else { - u8n = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - dest[read_len..].as_mut_ptr() as _, - remaining as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; + let mut smallbuf = self.buf.lock(); + match host_nt::read_console_into(handle, dest, &mut smallbuf) { + Ok(read_len) => Ok(read_len), + Err(host_nt::ReadConsoleError::BufferTooSmall { + available, + required, + }) => Err(vm.new_system_error(format!( + "Buffer had room for {available} bytes but {required} bytes required", + ))), + Err(host_nt::ReadConsoleError::Io(err)) => Err(err.into_pyexception(vm)), } - - if u8n > 0 { - read_len += u8n as usize; - } else { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() == Some(122) { - // ERROR_INSUFFICIENT_BUFFER - let needed = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if needed > 0 { - return Err(vm.new_system_error(format!( - "Buffer had room for {remaining} bytes but {needed} bytes required", - ))); - } - } - return Err(err.into_pyexception(vm)); - } - - Ok(read_len) } #[pymethod] @@ -6677,77 +6269,9 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - let mut result = Vec::new(); - - // Copy any buffered bytes first - { - let mut buf = self.buf.lock(); - let mut tmp = [0u8; SMALLBUF]; - let n = Self::copy_from_buf(&mut buf, &mut tmp); - result.extend_from_slice(&tmp[..n]); - } - - let mut wbuf = vec![0u16; 8192]; - loop { - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wbuf.len() as u32, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 { - break; - } - // Ctrl+Z at start -> EOF - if wbuf[0] == 0x1A { - break; - } - // Convert to UTF-8 - let needed = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if needed == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - let offset = result.len(); - result.resize(offset + needed as usize, 0); - let written = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - result[offset..].as_mut_ptr() as _, - needed, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if written == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - // If we didn't fill the buffer, no more data - if nread < wbuf.len() as u32 { - break; - } - } - + let mut smallbuf = self.buf.lock(); + let result = host_nt::read_console_all(handle, &mut smallbuf) + .map_err(|err| err.into_pyexception(vm))?; Ok(vm.ctx.new_bytes(result).into()) } @@ -6775,105 +6299,30 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - let len = size as usize; - - let mut wlen = (len / 4) as u32; - if wlen == 0 { - wlen = 1; - } - let mut read_len = { let mut ibuf = self.buf.lock(); Self::copy_from_buf(&mut ibuf, &mut buf) }; - if read_len > 0 { - wlen = wlen.saturating_sub(1); - } - if read_len >= len || wlen == 0 { - buf.truncate(read_len); - return Ok(vm.ctx.new_bytes(buf).into()); - } - - let mut wbuf = vec![0u16; wlen as usize]; - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wlen, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 || wbuf[0] == 0x1A { + if read_len >= size as usize { buf.truncate(read_len); return Ok(vm.ctx.new_bytes(buf).into()); } - - let remaining = len - read_len; - let u8n; - if remaining < 4 { + { let mut ibuf = self.buf.lock(); - let converted = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - ibuf.as_mut_ptr() as _, - SMALLBUF as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if converted > 0 { - u8n = Self::copy_from_buf(&mut ibuf, &mut buf[read_len..]) as i32; - } else { - u8n = 0; - } - } else { - u8n = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - buf[read_len..].as_mut_ptr() as _, - remaining as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - } - - if u8n > 0 { - read_len += u8n as usize; - } else { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() == Some(122) { - // ERROR_INSUFFICIENT_BUFFER - let needed = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - core::ptr::null_mut(), - 0, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if needed > 0 { + match host_nt::read_console_into(handle, &mut buf[read_len..], &mut ibuf) { + Ok(n) => read_len += n, + Err(host_nt::ReadConsoleError::BufferTooSmall { + available, + required, + }) => { return Err(vm.new_system_error(format!( - "Buffer had room for {remaining} bytes but {needed} bytes required", + "Buffer had room for {available} bytes but {required} bytes required", ))); } + Err(host_nt::ReadConsoleError::Io(err)) => { + return Err(err.into_pyexception(vm)); + } } - return Err(err.into_pyexception(vm)); } buf.truncate(read_len); @@ -6903,72 +6352,8 @@ mod winconsoleio { return Ok(0); } - let mut len = data.len().min(BUFMAX); - - // Cap at 32766/2 wchars * 3 bytes (UTF-8 to wchar ratio is at most 3:1) - let max_wlen: u32 = 32766 / 2; - len = len.min(max_wlen as usize * 3); - - // Reduce len until wlen fits within max_wlen - let wlen; - loop { - len = find_last_utf8_boundary(data, len); - let w = unsafe { - MultiByteToWideChar( - CP_UTF8, - 0, - data.as_ptr(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; - if w as u32 <= max_wlen { - wlen = w; - break; - } - len /= 2; - } - if wlen == 0 { - return Ok(0); - } - - let mut wbuf = vec![0u16; wlen as usize]; - let wlen = unsafe { - MultiByteToWideChar( - CP_UTF8, - 0, - data.as_ptr(), - len as i32, - wbuf.as_mut_ptr(), - wlen, - ) - }; - if wlen == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - - let mut n_written: u32 = 0; - let res = unsafe { - WriteConsoleW( - handle, - wbuf.as_ptr() as _, - wlen as u32, - &mut n_written, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - - // If we wrote fewer wchars than expected, recalculate bytes consumed - if n_written < wlen as u32 { - // Binary search to find how many input bytes correspond to n_written wchars - len = wchar_to_utf8_count(data, len, n_written); - } - - Ok(len) + host_nt::write_console_utf8(handle, data, BUFMAX) + .map_err(|err| err.into_pyexception(vm)) } #[pymethod(name = "__reduce__")] @@ -6977,43 +6362,6 @@ mod winconsoleio { } } - /// Find how many UTF-8 bytes correspond to n wide chars. - fn wchar_to_utf8_count(data: &[u8], mut len: usize, mut n: u32) -> usize { - let mut start: usize = 0; - loop { - let mut mid = 0; - for i in (len / 2)..=len { - mid = find_last_utf8_boundary(data, i); - if mid != 0 { - break; - } - } - if mid == len { - return start + len; - } - if mid == 0 { - mid = if len > 1 { len - 1 } else { 1 }; - } - let wlen = unsafe { - MultiByteToWideChar( - CP_UTF8, - 0, - data[start..].as_ptr(), - mid as i32, - core::ptr::null_mut(), - 0, - ) - } as u32; - if wlen <= n { - start += mid; - len -= mid; - n -= wlen; - } else { - len = mid; - } - } - } - impl Destructor for WindowsConsoleIO { fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { if let Some(cio) = zelf.downcast_ref::() { diff --git a/crates/vm/src/stdlib/_signal.rs b/crates/vm/src/stdlib/_signal.rs index 3c05f867d6f..b9c41d6a4ce 100644 --- a/crates/vm/src/stdlib/_signal.rs +++ b/crates/vm/src/stdlib/_signal.rs @@ -4,6 +4,8 @@ pub(crate) use _signal::module_def; #[pymodule] pub(crate) mod _signal { + #![allow(unreachable_pub)] + #[cfg(any(unix, windows))] use crate::convert::{IntoPyException, TryFromBorrowedObject}; use crate::{Py, PyObjectRef, PyResult, VirtualMachine, signal}; @@ -13,8 +15,12 @@ pub(crate) mod _signal { function::{ArgIntoFloat, OptionalArg}, }; use core::sync::atomic::{self, Ordering}; + #[cfg(any(unix, windows))] + use rustpython_host_env::signal as host_signal; #[cfg(unix)] use rustpython_host_env::signal::{double_to_timeval, itimerval_to_tuple}; + #[cfg(unix)] + use std::os::fd::AsFd; #[allow(non_camel_case_types)] type sighandler_t = cfg_select! { @@ -26,7 +32,7 @@ pub(crate) mod _signal { windows => { type WakeupFdRaw = libc::SOCKET; struct WakeupFd(WakeupFdRaw); - const INVALID_WAKEUP: libc::SOCKET = windows_sys::Win32::Networking::WinSock::INVALID_SOCKET; + const INVALID_WAKEUP: libc::SOCKET = host_signal::INVALID_SOCKET; static WAKEUP: atomic::AtomicUsize = atomic::AtomicUsize::new(INVALID_WAKEUP); // windows doesn't use the same fds for files and sockets like windows does, so we need // this to know whether to send() or write() @@ -57,14 +63,12 @@ pub(crate) mod _signal { } #[cfg(unix)] - pub(crate) use libc::SIG_ERR; - - #[cfg(unix)] - pub(crate) use nix::unistd::alarm as sig_alarm; + #[allow(unused_imports)] + pub use libc::SIG_ERR; #[cfg(unix)] #[pyattr] - pub(crate) use libc::{SIG_DFL, SIG_IGN}; + pub use libc::{SIG_DFL, SIG_IGN}; // pthread_sigmask 'how' constants #[cfg(unix)] @@ -73,54 +77,32 @@ pub(crate) mod _signal { #[cfg(not(unix))] #[pyattr] - pub(crate) const SIG_DFL: sighandler_t = 0; - + pub const SIG_DFL: sighandler_t = 0; #[cfg(not(unix))] #[pyattr] - pub(crate) const SIG_IGN: sighandler_t = 1; - + pub const SIG_IGN: sighandler_t = 1; #[cfg(not(unix))] #[allow(dead_code)] - pub(crate) const SIG_ERR: sighandler_t = -1 as _; - - #[cfg(all(unix, not(target_os = "redox")))] - unsafe extern "C" { - fn siginterrupt(sig: i32, flag: i32) -> i32; - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - mod ffi { - unsafe extern "C" { - pub(super) fn getitimer( - which: libc::c_int, - curr_value: *mut libc::itimerval, - ) -> libc::c_int; - pub(super) fn setitimer( - which: libc::c_int, - new_value: *const libc::itimerval, - old_value: *mut libc::itimerval, - ) -> libc::c_int; - } - } + pub const SIG_ERR: sighandler_t = -1 as _; #[pyattr] use crate::signal::NSIG; #[cfg(any(unix, windows))] #[pyattr] - pub(crate) use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM}; + pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM}; #[cfg(windows)] #[pyattr] - const SIGBREAK: i32 = 21; // _SIGBREAK + const SIGBREAK: i32 = host_signal::SIGBREAK; // Windows-specific control events for GenerateConsoleCtrlEvent #[cfg(windows)] #[pyattr] - const CTRL_C_EVENT: u32 = 0; + const CTRL_C_EVENT: u32 = host_signal::CTRL_C_EVENT; #[cfg(windows)] #[pyattr] - const CTRL_BREAK_EVENT: u32 = 1; + const CTRL_BREAK_EVENT: u32 = host_signal::CTRL_BREAK_EVENT; #[cfg(unix)] #[pyattr] @@ -175,10 +157,9 @@ pub(crate) mod _signal { let sig_ign = vm.new_pyobj(SIG_IGN as u8); for signum in 1..NSIG { - let handler = unsafe { libc::signal(signum as i32, SIG_IGN) }; - if handler != SIG_ERR { - unsafe { libc::signal(signum as i32, handler) }; - } + let Some(handler) = (unsafe { host_signal::probe_handler(signum as i32) }) else { + continue; + }; let py_handler = if handler == SIG_DFL { Some(sig_dfl.clone()) } else if handler == SIG_IGN { @@ -210,7 +191,7 @@ pub(crate) mod _signal { #[cfg(any(unix, windows))] #[pyfunction] - pub(crate) fn signal( + pub fn signal( signalnum: i32, handler: PyObjectRef, vm: &VirtualMachine, @@ -218,16 +199,7 @@ pub(crate) mod _signal { signal::assert_in_range(signalnum, vm)?; #[cfg(windows)] { - const VALID_SIGNALS: &[i32] = &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ]; - if !VALID_SIGNALS.contains(&signalnum) { + if !host_signal::is_valid_signal(signalnum) { return Err(vm.new_value_error(format!("signal number {signalnum} out of range"))); } } @@ -246,14 +218,13 @@ pub(crate) mod _signal { }; signal::check_signals(vm)?; - let old = unsafe { libc::signal(signalnum, sig_handler) }; - if old == SIG_ERR { - return Err(vm.new_os_error("Failed to set signal".to_owned())); - } - #[cfg(all(unix, not(target_os = "redox")))] - unsafe { - siginterrupt(signalnum, 1); - } + let old = unsafe { host_signal::install_handler(signalnum, sig_handler) }; + let _old = match old { + Ok(old) => old, + Err(_) => { + return Err(vm.new_os_error("Failed to set signal".to_owned())); + } + }; let signal_handlers = vm.signal_handlers.get_or_init(signal::new_signal_handlers); let old_handler = signal_handlers.borrow_mut()[signalnum as usize].replace(handler); @@ -273,18 +244,13 @@ pub(crate) mod _signal { #[cfg(unix)] #[pyfunction] fn alarm(time: u32) -> u32 { - let prev_time = if time == 0 { - sig_alarm::cancel() - } else { - sig_alarm::set(time) - }; - prev_time.unwrap_or(0) + rustpython_host_env::signal::alarm(time) } #[cfg(unix)] #[pyfunction] fn pause(vm: &VirtualMachine) -> PyResult<()> { - unsafe { libc::pause() }; + host_signal::pause(); signal::check_signals(vm)?; Ok(()) } @@ -303,35 +269,25 @@ pub(crate) mod _signal { it_value: double_to_timeval(seconds), it_interval: double_to_timeval(interval), }; - let mut old = core::mem::MaybeUninit::::uninit(); - #[cfg(any(target_os = "linux", target_os = "android"))] - let ret = unsafe { ffi::setitimer(which, &new, old.as_mut_ptr()) }; - #[cfg(not(any(target_os = "linux", target_os = "android")))] - let ret = unsafe { libc::setitimer(which, &new, old.as_mut_ptr()) }; - if ret != 0 { - let err = std::io::Error::last_os_error(); - let itimer_error = itimer_error(vm); - return Err(vm.new_exception_msg(itimer_error, err.to_string().into())); + match host_signal::setitimer(which, &new) { + Ok(old) => Ok(itimerval_to_tuple(&old)), + Err(err) => { + let itimer_error = itimer_error(vm); + Err(vm.new_exception_msg(itimer_error, err.to_string().into())) + } } - let old = unsafe { old.assume_init() }; - Ok(itimerval_to_tuple(&old)) } #[cfg(unix)] #[pyfunction] fn getitimer(which: i32, vm: &VirtualMachine) -> PyResult<(f64, f64)> { - let mut old = core::mem::MaybeUninit::::uninit(); - #[cfg(any(target_os = "linux", target_os = "android"))] - let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; - #[cfg(not(any(target_os = "linux", target_os = "android")))] - let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; - if ret != 0 { - let err = std::io::Error::last_os_error(); - let itimer_error = itimer_error(vm); - return Err(vm.new_exception_msg(itimer_error, err.to_string().into())); + match host_signal::getitimer(which) { + Ok(old) => Ok(itimerval_to_tuple(&old)), + Err(err) => { + let itimer_error = itimer_error(vm); + Err(vm.new_exception_msg(itimer_error, err.to_string().into())) + } } - let old = unsafe { old.assume_init() }; - Ok(itimerval_to_tuple(&old)) } #[pyfunction] @@ -365,54 +321,25 @@ pub(crate) mod _signal { #[cfg(windows)] let is_socket = if fd != INVALID_WAKEUP { - use windows_sys::Win32::Networking::WinSock; - - crate::windows::init_winsock(); - let mut res = 0i32; - let mut res_size = core::mem::size_of::() as i32; - let res = unsafe { - WinSock::getsockopt( - fd, - WinSock::SOL_SOCKET, - WinSock::SO_ERROR, - &mut res as *mut i32 as *mut _, - &mut res_size, - ) - }; - // if getsockopt succeeded, fd is for sure a socket - let is_socket = res == 0; - if !is_socket { - let err = std::io::Error::last_os_error(); - // if getsockopt failed for some other reason, throw - if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { - return Err(err.into_pyexception(vm)); + host_signal::wakeup_fd_is_socket(fd).map_err(|err| { + if err.kind() == std::io::ErrorKind::InvalidInput { + vm.new_value_error("invalid fd") + } else { + err.into_pyexception(vm) } - // Validate that fd is a valid file descriptor using fstat - // First check if SOCKET can be safely cast to i32 (file descriptor) - let fd_i32 = i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd"))?; - // Verify the fd is valid by trying to fstat it - let borrowed_fd = - unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd_i32) } - .map_err(|e| e.into_pyexception(vm))?; - rustpython_host_env::fileutils::fstat(borrowed_fd) - .map_err(|e| e.into_pyexception(vm))?; - } - is_socket + })? } else { false }; #[cfg(unix)] - if let Ok(fd) = unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd) } { - use nix::fcntl; - let oflags = fcntl::fcntl(fd, fcntl::F_GETFL).map_err(|e| e.into_pyexception(vm))?; - let nonblock = - fcntl::OFlag::from_bits_truncate(oflags).contains(fcntl::OFlag::O_NONBLOCK); - if !nonblock { - return Err(vm.new_value_error(format!( - "the fd {} must be in non-blocking mode", - fd.as_raw() - ))); - } + if let Ok(fd) = unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd) } + && rustpython_host_env::fcntl::get_blocking(fd.as_fd()) + .map_err(|e| e.into_pyexception(vm))? + { + return Err(vm.new_value_error(format!( + "the fd {} must be in non-blocking mode", + fd.as_raw() + ))); } let old_fd = WAKEUP.swap(fd, Ordering::Relaxed); @@ -450,33 +377,14 @@ pub(crate) mod _signal { } let flags = flags.unwrap_or(0); - let ret = unsafe { - libc::syscall( - libc::SYS_pidfd_send_signal, - pidfd, - sig, - core::ptr::null::(), - flags, - ) as libc::c_long - }; - - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_signal::pidfd_send_signal(pidfd, sig, flags).map_err(|_| vm.new_last_errno_error()) } #[cfg(all(unix, not(target_os = "redox")))] #[pyfunction(name = "siginterrupt")] fn py_siginterrupt(signum: i32, flag: i32, vm: &VirtualMachine) -> PyResult<()> { signal::assert_in_range(signum, vm)?; - let res = unsafe { siginterrupt(signum, flag) }; - if res < 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_signal::siginterrupt(signum, flag).map_err(|_| vm.new_last_errno_error()) } /// CPython: signal_raise_signal (signalmodule.c) @@ -488,25 +396,14 @@ pub(crate) mod _signal { // On Windows, only certain signals are supported #[cfg(windows)] { - // Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGBREAK(21), SIGABRT(22) - const VALID_SIGNALS: &[i32] = &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ]; - if !VALID_SIGNALS.contains(&signalnum) { + if !host_signal::is_valid_signal(signalnum) { return Err(vm .new_errno_error(libc::EINVAL, "Invalid argument") .upcast()); } } - let res = unsafe { libc::raise(signalnum) }; - if res != 0 { + if host_signal::raise_signal(signalnum).is_err() { return Err(vm.new_os_error(format!("raise_signal failed for signal {signalnum}"))); } @@ -523,13 +420,7 @@ pub(crate) mod _signal { if signalnum < 1 || signalnum >= signal::NSIG as i32 { return Err(vm.new_value_error(format!("signal number {signalnum} out of range"))); } - let s = unsafe { libc::strsignal(signalnum) }; - if s.is_null() { - Ok(None) - } else { - let cstr = unsafe { core::ffi::CStr::from_ptr(s) }; - Ok(Some(cstr.to_string_lossy().into_owned())) - } + Ok(host_signal::strsignal(signalnum)) } #[cfg(windows)] @@ -538,18 +429,7 @@ pub(crate) mod _signal { if signalnum < 1 || signalnum >= signal::NSIG as i32 { return Err(vm.new_value_error(format!("signal number {signalnum} out of range"))); } - // Windows doesn't have strsignal(), provide our own mapping - let name = match signalnum { - libc::SIGINT => "Interrupt", - libc::SIGILL => "Illegal instruction", - libc::SIGFPE => "Floating-point exception", - libc::SIGSEGV => "Segmentation fault", - libc::SIGTERM => "Terminated", - SIGBREAK => "Break", - libc::SIGABRT => "Aborted", - _ => return Ok(None), - }; - Ok(Some(name.to_owned())) + Ok(host_signal::strsignal(signalnum)) } /// CPython: signal_valid_signals (signalmodule.c) @@ -558,39 +438,16 @@ pub(crate) mod _signal { use crate::PyPayload; use crate::builtins::PySet; let set = PySet::default().into_ref(&vm.ctx); - cfg_select! { - unix => { - // Use sigfillset to get all valid signals - let mut mask: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: mask is a valid pointer - if unsafe { libc::sigfillset(&mut mask) } != 0 { - return Err(vm.new_os_error("sigfillset failed".to_owned())); - } - // Convert the filled mask to a Python set - for signum in 1..signal::NSIG { - if unsafe { libc::sigismember(&mask, signum as i32) } == 1 { - set.add(vm.ctx.new_int(signum as i32).into(), vm)?; - } - } - } - windows => { - // Windows only supports a limited set of signals - for &signum in &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ] { - set.add(vm.ctx.new_int(signum).into(), vm)?; - } - } - _ => { - // Empty set for platforms without signal support (e.g., WASM) - let _ = &set; - } + #[cfg(any(unix, windows))] + for signum in host_signal::valid_signals(signal::NSIG) + .map_err(|_| vm.new_os_error("sigfillset failed".to_owned()))? + { + set.add(vm.ctx.new_int(signum).into(), vm)?; + } + #[cfg(not(any(unix, windows)))] + { + // Empty set for platforms without signal support (e.g., WASM) + let _ = &set; } Ok(set.into()) } @@ -601,8 +458,7 @@ pub(crate) mod _signal { use crate::builtins::PySet; let set = PySet::default().into_ref(&vm.ctx); for signum in 1..signal::NSIG { - // SAFETY: mask is a valid sigset_t - if unsafe { libc::sigismember(mask, signum as i32) } == 1 { + if host_signal::sigset_contains(mask, signum as i32) { set.add(vm.ctx.new_int(signum as i32).into(), vm)?; } } @@ -619,11 +475,7 @@ pub(crate) mod _signal { use crate::convert::IntoPyException; // Initialize sigset - let mut sigset: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: sigset is a valid pointer - if unsafe { libc::sigemptyset(&mut sigset) } != 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } + let mut sigset = host_signal::sigemptyset().map_err(|e| e.into_pyexception(vm))?; // Add signals to the set for sig in mask.iter(vm)? { @@ -643,19 +495,11 @@ pub(crate) mod _signal { signal::NSIG - 1 ))); } - // SAFETY: sigset is a valid pointer and signum is validated - if unsafe { libc::sigaddset(&mut sigset, signum) } != 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } + host_signal::sigaddset(&mut sigset, signum).map_err(|e| e.into_pyexception(vm))?; } - // Call pthread_sigmask - let mut old_mask: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: all pointers are valid - let err = unsafe { libc::pthread_sigmask(how, &sigset, &mut old_mask) }; - if err != 0 { - return Err(std::io::Error::from_raw_os_error(err).into_pyexception(vm)); - } + let old_mask = + host_signal::pthread_sigmask(how, &sigset).map_err(|e| e.into_pyexception(vm))?; // Check for pending signals signal::check_signals(vm)?; @@ -665,35 +509,18 @@ pub(crate) mod _signal { } #[cfg(any(unix, windows))] - pub(crate) extern "C" fn run_signal(signum: i32) { + pub extern "C" fn run_signal(signum: i32) { signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed); signal::set_triggered(); #[cfg(windows)] - if signum == libc::SIGINT - && let Some(handle) = signal::get_sigint_event() - { - unsafe { - windows_sys::Win32::System::Threading::SetEvent(handle as _); - } - } - let wakeup_fd = WAKEUP.load(Ordering::Relaxed); - if wakeup_fd != INVALID_WAKEUP { - let sigbyte = signum as u8; - #[cfg(windows)] - if WAKEUP_IS_SOCKET.load(Ordering::Relaxed) { - let _res = unsafe { - windows_sys::Win32::Networking::WinSock::send( - wakeup_fd, - &sigbyte as *const u8 as *const _, - 1, - 0, - ) - }; - return; - } - let _res = unsafe { libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1) }; - // TODO: handle _res < 1, support warn_on_full_buffer - } + host_signal::notify_signal( + signum, + WAKEUP.load(Ordering::Relaxed), + WAKEUP_IS_SOCKET.load(Ordering::Relaxed), + signal::get_sigint_event(), + ); + #[cfg(unix)] + host_signal::notify_signal(signum, WAKEUP.load(Ordering::Relaxed)); } /// Reset wakeup fd after fork in child process. diff --git a/crates/vm/src/stdlib/_stat.rs b/crates/vm/src/stdlib/_stat.rs index 8809c1d0bf8..f461221daff 100644 --- a/crates/vm/src/stdlib/_stat.rs +++ b/crates/vm/src/stdlib/_stat.rs @@ -2,6 +2,11 @@ pub(crate) use _stat::module_def; #[pymodule] mod _stat { + #![allow(unreachable_pub)] + + #[cfg(windows)] + use rustpython_host_env::nt as host_nt; + // Use libc::mode_t for Mode to match the system's definition #[cfg(unix)] type Mode = libc::mode_t; @@ -25,65 +30,65 @@ mod _stat { } #[pyattr] - pub(super) const S_IFDIR: Mode = libc_const!( + pub const S_IFDIR: Mode = libc_const!( #[cfg(unix)] S_IFDIR, 0o040000 ); #[pyattr] - pub(super) const S_IFCHR: Mode = libc_const!( + pub const S_IFCHR: Mode = libc_const!( #[cfg(unix)] S_IFCHR, 0o020000 ); #[pyattr] - pub(super) const S_IFBLK: Mode = libc_const!( + pub const S_IFBLK: Mode = libc_const!( #[cfg(unix)] S_IFBLK, 0o060000 ); #[pyattr] - pub(super) const S_IFREG: Mode = libc_const!( + pub const S_IFREG: Mode = libc_const!( #[cfg(unix)] S_IFREG, 0o100000 ); #[pyattr] - pub(super) const S_IFIFO: Mode = libc_const!( + pub const S_IFIFO: Mode = libc_const!( #[cfg(unix)] S_IFIFO, 0o010000 ); #[pyattr] - pub(super) const S_IFLNK: Mode = libc_const!( + pub const S_IFLNK: Mode = libc_const!( #[cfg(unix)] S_IFLNK, 0o120000 ); #[pyattr] - pub(super) const S_IFSOCK: Mode = libc_const!( + pub const S_IFSOCK: Mode = libc_const!( #[cfg(unix)] S_IFSOCK, 0o140000 ); #[pyattr] - pub(super) const S_IFDOOR: Mode = 0; // TODO: RUSTPYTHON Support Solaris + pub const S_IFDOOR: Mode = 0; // TODO: RUSTPYTHON Support Solaris #[pyattr] - pub(super) const S_IFPORT: Mode = 0; // TODO: RUSTPYTHON Support Solaris + pub const S_IFPORT: Mode = 0; // TODO: RUSTPYTHON Support Solaris // TODO: RUSTPYTHON Support BSD // https://man.freebsd.org/cgi/man.cgi?stat(2) #[pyattr] - pub(super) const S_IFWHT: Mode = if cfg!(target_os = "macos") { + pub const S_IFWHT: Mode = if cfg!(target_os = "macos") { 0o160000 } else { 0 @@ -92,133 +97,133 @@ mod _stat { // Permission bits #[pyattr] - pub(super) const S_ISUID: Mode = libc_const!( + pub const S_ISUID: Mode = libc_const!( #[cfg(unix)] S_ISUID, 0o4000 ); #[pyattr] - pub(super) const S_ISGID: Mode = libc_const!( + pub const S_ISGID: Mode = libc_const!( #[cfg(unix)] S_ISGID, 0o2000 ); #[pyattr] - pub(super) const S_ENFMT: Mode = libc_const!( + pub const S_ENFMT: Mode = libc_const!( #[cfg(unix)] S_ISGID, 0o2000 ); #[pyattr] - pub(super) const S_ISVTX: Mode = libc_const!( + pub const S_ISVTX: Mode = libc_const!( #[cfg(unix)] S_ISVTX, 0o1000 ); #[pyattr] - pub(super) const S_IRWXU: Mode = libc_const!( + pub const S_IRWXU: Mode = libc_const!( #[cfg(unix)] S_IRWXU, 0o0700 ); #[pyattr] - pub(super) const S_IRUSR: Mode = libc_const!( + pub const S_IRUSR: Mode = libc_const!( #[cfg(unix)] S_IRUSR, 0o0400 ); #[pyattr] - pub(super) const S_IREAD: Mode = libc_const!( + pub const S_IREAD: Mode = libc_const!( #[cfg(unix)] S_IRUSR, 0o0400 ); #[pyattr] - pub(super) const S_IWUSR: Mode = libc_const!( + pub const S_IWUSR: Mode = libc_const!( #[cfg(unix)] S_IWUSR, 0o0200 ); #[pyattr] - pub(super) const S_IXUSR: Mode = libc_const!( + pub const S_IXUSR: Mode = libc_const!( #[cfg(unix)] S_IXUSR, 0o0100 ); #[pyattr] - pub(super) const S_IRWXG: Mode = libc_const!( + pub const S_IRWXG: Mode = libc_const!( #[cfg(unix)] S_IRWXG, 0o0070 ); #[pyattr] - pub(super) const S_IRGRP: Mode = libc_const!( + pub const S_IRGRP: Mode = libc_const!( #[cfg(unix)] S_IRGRP, 0o0040 ); #[pyattr] - pub(super) const S_IWGRP: Mode = libc_const!( + pub const S_IWGRP: Mode = libc_const!( #[cfg(unix)] S_IWGRP, 0o0020 ); #[pyattr] - pub(super) const S_IXGRP: Mode = libc_const!( + pub const S_IXGRP: Mode = libc_const!( #[cfg(unix)] S_IXGRP, 0o0010 ); #[pyattr] - pub(super) const S_IRWXO: Mode = libc_const!( + pub const S_IRWXO: Mode = libc_const!( #[cfg(unix)] S_IRWXO, 0o0007 ); #[pyattr] - pub(super) const S_IROTH: Mode = libc_const!( + pub const S_IROTH: Mode = libc_const!( #[cfg(unix)] S_IROTH, 0o0004 ); #[pyattr] - pub(super) const S_IWOTH: Mode = libc_const!( + pub const S_IWOTH: Mode = libc_const!( #[cfg(unix)] S_IWOTH, 0o0002 ); #[pyattr] - pub(super) const S_IXOTH: Mode = libc_const!( + pub const S_IXOTH: Mode = libc_const!( #[cfg(unix)] S_IXOTH, 0o0001 ); #[pyattr] - pub(super) const S_IWRITE: Mode = libc_const!( + pub const S_IWRITE: Mode = libc_const!( #[cfg(all(unix, not(target_os = "android"), not(target_os = "redox")))] S_IWRITE, 0o0200 ); #[pyattr] - pub(super) const S_IEXEC: Mode = libc_const!( + pub const S_IEXEC: Mode = libc_const!( #[cfg(all(unix, not(target_os = "android"), not(target_os = "redox")))] S_IEXEC, 0o0100 @@ -228,7 +233,7 @@ mod _stat { #[cfg(windows)] #[pyattr] - pub(super) use windows_sys::Win32::Storage::FileSystem::{ + pub use host_nt::{ FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_NORMAL, @@ -240,144 +245,142 @@ mod _stat { // Windows reparse point tags #[cfg(windows)] #[pyattr] - pub(super) const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; - + pub const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; #[cfg(windows)] #[pyattr] - pub(super) const IO_REPARSE_TAG_MOUNT_POINT: u32 = 0xA0000003; - + pub const IO_REPARSE_TAG_MOUNT_POINT: u32 = 0xA0000003; #[cfg(windows)] #[pyattr] - pub(super) const IO_REPARSE_TAG_APPEXECLINK: u32 = 0x8000001B; + pub const IO_REPARSE_TAG_APPEXECLINK: u32 = 0x8000001B; // Unix file flags (if on Unix) #[pyattr] - pub(super) const UF_NODUMP: u32 = libc_const!( + pub const UF_NODUMP: u32 = libc_const!( #[cfg(target_os = "macos")] UF_NODUMP, 0x00000001 ); #[pyattr] - pub(super) const UF_IMMUTABLE: u32 = libc_const!( + pub const UF_IMMUTABLE: u32 = libc_const!( #[cfg(target_os = "macos")] UF_IMMUTABLE, 0x00000002 ); #[pyattr] - pub(super) const UF_APPEND: u32 = libc_const!( + pub const UF_APPEND: u32 = libc_const!( #[cfg(target_os = "macos")] UF_APPEND, 0x00000004 ); #[pyattr] - pub(super) const UF_OPAQUE: u32 = libc_const!( + pub const UF_OPAQUE: u32 = libc_const!( #[cfg(target_os = "macos")] UF_OPAQUE, 0x00000008 ); #[pyattr] - pub(super) const UF_COMPRESSED: u32 = libc_const!( + pub const UF_COMPRESSED: u32 = libc_const!( #[cfg(target_os = "macos")] UF_COMPRESSED, 0x00000020 ); #[pyattr] - pub(super) const UF_HIDDEN: u32 = libc_const!( + pub const UF_HIDDEN: u32 = libc_const!( #[cfg(target_os = "macos")] UF_HIDDEN, 0x00008000 ); #[pyattr] - pub(super) const SF_ARCHIVED: u32 = libc_const!( + pub const SF_ARCHIVED: u32 = libc_const!( #[cfg(target_os = "macos")] SF_ARCHIVED, 0x00010000 ); #[pyattr] - pub(super) const SF_IMMUTABLE: u32 = libc_const!( + pub const SF_IMMUTABLE: u32 = libc_const!( #[cfg(target_os = "macos")] SF_IMMUTABLE, 0x00020000 ); #[pyattr] - pub(super) const SF_APPEND: u32 = libc_const!( + pub const SF_APPEND: u32 = libc_const!( #[cfg(target_os = "macos")] SF_APPEND, 0x00040000 ); #[pyattr] - pub(super) const SF_SETTABLE: u32 = if cfg!(target_os = "macos") { + pub const SF_SETTABLE: u32 = if cfg!(target_os = "macos") { 0x3fff0000 } else { 0xffff0000 }; #[pyattr] - pub(super) const UF_NOUNLINK: u32 = 0x00000010; + pub const UF_NOUNLINK: u32 = 0x00000010; #[pyattr] - pub(super) const SF_NOUNLINK: u32 = 0x00100000; + pub const SF_NOUNLINK: u32 = 0x00100000; #[pyattr] - pub(super) const SF_SNAPSHOT: u32 = 0x00200000; + pub const SF_SNAPSHOT: u32 = 0x00200000; #[pyattr] - pub(super) const SF_FIRMLINK: u32 = 0x00800000; + pub const SF_FIRMLINK: u32 = 0x00800000; #[pyattr] - pub(super) const SF_DATALESS: u32 = 0x40000000; + pub const SF_DATALESS: u32 = 0x40000000; // MacOS specific #[cfg(target_os = "macos")] #[pyattr] - pub(super) const SF_SUPPORTED: u32 = 0x009f0000; + pub const SF_SUPPORTED: u32 = 0x009f0000; #[cfg(target_os = "macos")] #[pyattr] - pub(super) const SF_SYNTHETIC: u32 = 0xc0000000; + pub const SF_SYNTHETIC: u32 = 0xc0000000; // Stat result indices #[pyattr] - pub(super) const ST_MODE: u32 = 0; + pub const ST_MODE: u32 = 0; #[pyattr] - pub(super) const ST_INO: u32 = 1; + pub const ST_INO: u32 = 1; #[pyattr] - pub(super) const ST_DEV: u32 = 2; + pub const ST_DEV: u32 = 2; #[pyattr] - pub(super) const ST_NLINK: u32 = 3; + pub const ST_NLINK: u32 = 3; #[pyattr] - pub(super) const ST_UID: u32 = 4; + pub const ST_UID: u32 = 4; #[pyattr] - pub(super) const ST_GID: u32 = 5; + pub const ST_GID: u32 = 5; #[pyattr] - pub(super) const ST_SIZE: u32 = 6; + pub const ST_SIZE: u32 = 6; #[pyattr] - pub(super) const ST_ATIME: u32 = 7; + pub const ST_ATIME: u32 = 7; #[pyattr] - pub(super) const ST_MTIME: u32 = 8; + pub const ST_MTIME: u32 = 8; #[pyattr] - pub(super) const ST_CTIME: u32 = 9; + pub const ST_CTIME: u32 = 9; const S_IFMT: Mode = 0o170000; diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index c9589c1ec52..b6fbf146a96 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -31,6 +31,8 @@ pub(crate) mod _thread { lock_api::{RawMutex as RawMutexT, RawMutexTimed, RawReentrantMutex}, }; use rustpython_common::str::levenshtein::{MOVE_COST, levenshtein_distance}; + #[cfg(any(unix, windows))] + use rustpython_host_env::thread as host_thread; use std::thread; // PYTHREAD_NAME: show current thread name @@ -381,50 +383,17 @@ pub(crate) mod _thread { /// Set the name of the current thread #[pyfunction] fn set_name(name: PyUtf8StrRef) { - #[cfg(target_os = "linux")] - { - use alloc::ffi::CString; - if let Ok(c_name) = CString::new(name.as_str()) { - // pthread_setname_np on Linux has a 16-byte limit including null terminator - // TODO: Potential UTF-8 boundary issue when truncating thread name on Linux. - // https://github.com/RustPython/RustPython/pull/6726/changes#r2689379171 - let truncated = if c_name.as_bytes().len() > 15 { - CString::new(&c_name.as_bytes()[..15]).unwrap_or(c_name) - } else { - c_name - }; - unsafe { - libc::pthread_setname_np(libc::pthread_self(), truncated.as_ptr()); - } - } - } - #[cfg(target_os = "macos")] - { - use alloc::ffi::CString; - if let Ok(c_name) = CString::new(name.as_str()) { - unsafe { - libc::pthread_setname_np(c_name.as_ptr()); - } - } - } - #[cfg(windows)] - { - // Windows doesn't have a simple pthread_setname_np equivalent - // SetThreadDescription requires Windows 10+ - let _ = name; - } - #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] - { - let _ = name; - } + #[cfg(any(unix, windows))] + host_thread::set_current_thread_name(name.as_str()); + #[cfg(not(any(unix, windows)))] + let _ = name; } /// Get OS-level thread ID (pthread_self on Unix) /// This is important for fork compatibility - the ID must remain stable after fork #[cfg(unix)] fn current_thread_id() -> u64 { - // pthread_self() for fork compatibility - unsafe { libc::pthread_self() as u64 } + host_thread::current_thread_id() } #[cfg(not(unix))] diff --git a/crates/vm/src/stdlib/_winapi.rs b/crates/vm/src/stdlib/_winapi.rs index 113d5bd2de4..3ceed26d693 100644 --- a/crates/vm/src/stdlib/_winapi.rs +++ b/crates/vm/src/stdlib/_winapi.rs @@ -9,75 +9,54 @@ mod _winapi { Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, builtins::PyStrRef, common::lock::PyMutex, - convert::{ToPyException, ToPyResult}, + convert::ToPyException, function::{ArgMapping, ArgSequence, OptionalArg}, types::Constructor, windows::{WinHandle, WindowsSysResult}, }; use core::ptr::{null, null_mut}; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::overlapped as host_overlapped; use rustpython_host_env::winapi as host_winapi; use rustpython_host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::{HANDLE, MAX_PATH}; #[pyattr] - use windows_sys::Win32::{ - Foundation::{ - DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, ERROR_ACCESS_DENIED, - ERROR_ALREADY_EXISTS, ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, - ERROR_NETNAME_DELETED, ERROR_NO_DATA, ERROR_NO_SYSTEM_RESOURCES, - ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED, - ERROR_PRIVILEGE_NOT_HELD, ERROR_SEM_TIMEOUT, GENERIC_READ, GENERIC_WRITE, STILL_ACTIVE, - WAIT_ABANDONED_0, WAIT_OBJECT_0, WAIT_TIMEOUT, - }, - Globalization::{ - LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, LCMAP_KATAKANA, - LCMAP_LINGUISTIC_CASING, LCMAP_LOWERCASE, LCMAP_SIMPLIFIED_CHINESE, LCMAP_TITLECASE, - LCMAP_TRADITIONAL_CHINESE, LCMAP_UPPERCASE, - }, - Storage::FileSystem::{ - COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK, - COPY_FILE_FAIL_IF_EXISTS, COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD, - COPY_FILE_OPEN_SOURCE_FOR_WRITE, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC, - COPY_FILE_REQUEST_SECURITY_PRIVILEGES, COPY_FILE_RESTARTABLE, - COPY_FILE_RESUME_FROM_PAUSE, COPYFILE2_CALLBACK_CHUNK_FINISHED, - COPYFILE2_CALLBACK_CHUNK_STARTED, COPYFILE2_CALLBACK_ERROR, - COPYFILE2_CALLBACK_POLL_CONTINUE, COPYFILE2_CALLBACK_STREAM_FINISHED, - COPYFILE2_CALLBACK_STREAM_STARTED, COPYFILE2_PROGRESS_CANCEL, - COPYFILE2_PROGRESS_CONTINUE, COPYFILE2_PROGRESS_PAUSE, COPYFILE2_PROGRESS_QUIET, - COPYFILE2_PROGRESS_STOP, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, - FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, - FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, - PIPE_ACCESS_INBOUND, SYNCHRONIZE, - }, - System::{ - Console::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}, - Memory::{ - FILE_MAP_ALL_ACCESS, FILE_MAP_COPY, FILE_MAP_EXECUTE, FILE_MAP_READ, - FILE_MAP_WRITE, MEM_COMMIT, MEM_FREE, MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE, - MEM_RESERVE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, - PAGE_EXECUTE_WRITECOPY, PAGE_GUARD, PAGE_NOACCESS, PAGE_NOCACHE, PAGE_READONLY, - PAGE_READWRITE, PAGE_WRITECOMBINE, PAGE_WRITECOPY, SEC_COMMIT, SEC_IMAGE, - SEC_LARGE_PAGES, SEC_NOCACHE, SEC_RESERVE, SEC_WRITECOMBINE, - }, - Pipes::{ - NMPWAIT_WAIT_FOREVER, PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, - PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, - }, - SystemServices::LOCALE_NAME_MAX_LENGTH, - Threading::{ - ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, - CREATE_BREAKAWAY_FROM_JOB, CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, - CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, DETACHED_PROCESS, HIGH_PRIORITY_CLASS, - IDLE_PRIORITY_CLASS, INFINITE, NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, - PROCESS_DUP_HANDLE, REALTIME_PRIORITY_CLASS, STARTF_FORCEOFFFEEDBACK, - STARTF_FORCEONFEEDBACK, STARTF_PREVENTPINNING, STARTF_RUNFULLSCREEN, - STARTF_TITLEISAPPID, STARTF_TITLEISLINKNAME, STARTF_UNTRUSTEDSOURCE, - STARTF_USECOUNTCHARS, STARTF_USEFILLATTRIBUTE, STARTF_USEHOTKEY, - STARTF_USEPOSITION, STARTF_USESHOWWINDOW, STARTF_USESIZE, STARTF_USESTDHANDLES, - }, - }, - UI::WindowsAndMessaging::SW_HIDE, + use host_winapi::{ + ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, + COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK, COPY_FILE_FAIL_IF_EXISTS, + COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD, COPY_FILE_OPEN_SOURCE_FOR_WRITE, + COPY_FILE_REQUEST_COMPRESSED_TRAFFIC, COPY_FILE_REQUEST_SECURITY_PRIVILEGES, + COPY_FILE_RESTARTABLE, COPY_FILE_RESUME_FROM_PAUSE, COPYFILE2_CALLBACK_CHUNK_FINISHED, + COPYFILE2_CALLBACK_CHUNK_STARTED, COPYFILE2_CALLBACK_ERROR, + COPYFILE2_CALLBACK_POLL_CONTINUE, COPYFILE2_CALLBACK_STREAM_FINISHED, + COPYFILE2_CALLBACK_STREAM_STARTED, COPYFILE2_PROGRESS_CANCEL, COPYFILE2_PROGRESS_CONTINUE, + COPYFILE2_PROGRESS_PAUSE, COPYFILE2_PROGRESS_QUIET, COPYFILE2_PROGRESS_STOP, + CREATE_BREAKAWAY_FROM_JOB, CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, + CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, DETACHED_PROCESS, DUPLICATE_CLOSE_SOURCE, + DUPLICATE_SAME_ACCESS, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_BROKEN_PIPE, + ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_NETNAME_DELETED, ERROR_NO_DATA, + ERROR_NO_SYSTEM_RESOURCES, ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED, + ERROR_PRIVILEGE_NOT_HELD, ERROR_SEM_TIMEOUT, FILE_FLAG_FIRST_PIPE_INSTANCE, + FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_MAP_ALL_ACCESS, + FILE_MAP_COPY, FILE_MAP_EXECUTE, FILE_MAP_READ, FILE_MAP_WRITE, FILE_TYPE_CHAR, + FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, GENERIC_READ, GENERIC_WRITE, + HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, + LCMAP_KATAKANA, LCMAP_LINGUISTIC_CASING, LCMAP_LOWERCASE, LCMAP_SIMPLIFIED_CHINESE, + LCMAP_TITLECASE, LCMAP_TRADITIONAL_CHINESE, LCMAP_UPPERCASE, LOCALE_NAME_MAX_LENGTH, + MEM_COMMIT, MEM_FREE, MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE, MEM_RESERVE, + NMPWAIT_WAIT_FOREVER, NORMAL_PRIORITY_CLASS, OPEN_EXISTING, PAGE_EXECUTE, + PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, PAGE_GUARD, + PAGE_NOACCESS, PAGE_NOCACHE, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOMBINE, + PAGE_WRITECOPY, PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, PIPE_READMODE_MESSAGE, + PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, PROCESS_ALL_ACCESS, + PROCESS_DUP_HANDLE, REALTIME_PRIORITY_CLASS, SEC_COMMIT, SEC_IMAGE, SEC_LARGE_PAGES, + SEC_NOCACHE, SEC_RESERVE, SEC_WRITECOMBINE, STARTF_FORCEOFFFEEDBACK, + STARTF_FORCEONFEEDBACK, STARTF_PREVENTPINNING, STARTF_RUNFULLSCREEN, STARTF_TITLEISAPPID, + STARTF_TITLEISLINKNAME, STARTF_UNTRUSTEDSOURCE, STARTF_USECOUNTCHARS, + STARTF_USEFILLATTRIBUTE, STARTF_USEHOTKEY, STARTF_USEPOSITION, STARTF_USESHOWWINDOW, + STARTF_USESIZE, STARTF_USESTDHANDLES, STD_ERROR_HANDLE, STD_INPUT_HANDLE, + STD_OUTPUT_HANDLE, STILL_ACTIVE, SW_HIDE, SYNCHRONIZE, WAIT_ABANDONED_0, WAIT_OBJECT_0, + WAIT_TIMEOUT, }; #[pyattr] @@ -86,12 +65,15 @@ mod _winapi { #[pyattr] const INVALID_HANDLE_VALUE: isize = -1; + #[pyattr] + const INFINITE: u32 = host_winapi::INFINITE_TIMEOUT; + #[pyattr] const COPY_FILE_DIRECTORY: u32 = 0x00000080; #[pyfunction] fn CloseHandle(handle: WinHandle) -> WindowsSysResult { - WindowsSysResult(unsafe { windows_sys::Win32::Foundation::CloseHandle(handle.0) }) + WindowsSysResult(host_winapi::close_handle(handle.0)) } /// CreateFile - Create or open a file or I/O device. @@ -110,44 +92,26 @@ mod _winapi { _template_file: PyObjectRef, // Always NULL (0) vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::CreateFileW; - let file_name_wide = file_name.as_wtf8().to_wide_with_nul(); - - let handle = unsafe { - CreateFileW( - file_name_wide.as_ptr(), - desired_access, - share_mode, - null(), - creation_disposition, - flags_and_attributes, - null_mut(), - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_file_w( + file_name_wide.as_ptr(), + desired_access, + share_mode, + creation_disposition, + flags_and_attributes, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn GetStdHandle( - std_handle: windows_sys::Win32::System::Console::STD_HANDLE, + std_handle: host_winapi::StdHandle, vm: &VirtualMachine, ) -> PyResult> { - let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - Ok(if handle.is_null() { - // NULL handle - return None - None - } else { - Some(WinHandle(handle)) - }) + host_winapi::get_std_handle(std_handle) + .map(|handle| handle.map(WinHandle)) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -156,20 +120,9 @@ mod _winapi { size: u32, vm: &VirtualMachine, ) -> PyResult<(WinHandle, WinHandle)> { - use windows_sys::Win32::Foundation::HANDLE; - let (read, write) = unsafe { - let mut read = core::mem::MaybeUninit::::uninit(); - let mut write = core::mem::MaybeUninit::::uninit(); - WindowsSysResult(windows_sys::Win32::System::Pipes::CreatePipe( - read.as_mut_ptr(), - write.as_mut_ptr(), - core::ptr::null(), - size, - )) - .to_pyresult(vm)?; - (read.assume_init(), write.assume_init()) - }; - Ok((WinHandle(read), WinHandle(write))) + host_winapi::create_pipe(size) + .map(|(read, write)| (WinHandle(read), WinHandle(write))) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -182,22 +135,16 @@ mod _winapi { options: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::HANDLE; - let target = unsafe { - let mut target = core::mem::MaybeUninit::::uninit(); - WindowsSysResult(windows_sys::Win32::Foundation::DuplicateHandle( - src_process.0, - src.0, - target_process.0, - target.as_mut_ptr(), - access, - inherit, - options.unwrap_or(0), - )) - .to_pyresult(vm)?; - target.assume_init() - }; - Ok(WinHandle(target)) + host_winapi::duplicate_handle( + src_process.0, + src.0, + target_process.0, + access, + inherit, + options.unwrap_or(0), + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -211,16 +158,8 @@ mod _winapi { } #[pyfunction] - fn GetFileType( - h: WinHandle, - vm: &VirtualMachine, - ) -> PyResult { - let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(h.0) }; - if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { - Err(vm.new_last_os_error()) - } else { - Ok(file_type) - } + fn GetFileType(h: WinHandle, vm: &VirtualMachine) -> PyResult { + host_winapi::get_file_type(h.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -260,31 +199,29 @@ mod _winapi { args: CreateProcessArgs, vm: &VirtualMachine, ) -> PyResult<(WinHandle, WinHandle, u32, u32)> { - let mut si: windows_sys::Win32::System::Threading::STARTUPINFOEXW = - unsafe { core::mem::zeroed() }; - si.StartupInfo.cb = core::mem::size_of_val(&si) as _; - macro_rules! si_attr { ($attr:ident, $t:ty) => {{ - si.StartupInfo.$attr = >::try_from_object( + >::try_from_object( vm, args.startup_info.get_attr(stringify!($attr), vm)?, )? .unwrap_or(0) as _ }}; ($attr:ident) => {{ - si.StartupInfo.$attr = >::try_from_object( + >::try_from_object( vm, args.startup_info.get_attr(stringify!($attr), vm)?, )? .unwrap_or(0) }}; } - si_attr!(dwFlags); - si_attr!(wShowWindow); - si_attr!(hStdInput, isize); - si_attr!(hStdOutput, isize); - si_attr!(hStdError, isize); + let startup_info = host_winapi::StartupInfoData { + flags: si_attr!(dwFlags), + show_window: si_attr!(wShowWindow), + std_input: si_attr!(hStdInput, isize), + std_output: si_attr!(hStdOutput, isize), + std_error: si_attr!(hStdError, isize), + }; let mut env = args .env_mapping @@ -292,11 +229,7 @@ mod _winapi { .transpose()?; let env = env.as_mut().map_or_else(null_mut, |v| v.as_mut_ptr()); - let mut attrlist = - getattributelist(args.startup_info.get_attr("lpAttributeList", vm)?, vm)?; - si.lpAttributeList = attrlist - .as_mut() - .map_or_else(null_mut, |l| l.attrlist.as_mut_ptr() as _); + let handle_list = get_handle_list(args.startup_info.get_attr("lpAttributeList", vm)?, vm)?; let wstr = |s: PyStrRef| { let ws = widestring::WideCString::from_str(s.expect_str()) @@ -329,31 +262,23 @@ mod _winapi { .as_mut() .map_or_else(null_mut, |w| w.as_mut_ptr()); - let procinfo = unsafe { - let mut procinfo = core::mem::MaybeUninit::uninit(); - WindowsSysResult(windows_sys::Win32::System::Threading::CreateProcessW( - app_name, - command_line, - core::ptr::null(), - core::ptr::null(), - args.inherit_handles, - args.creation_flags - | windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT - | windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT, - env as _, - current_dir, - &mut si as *mut _ as *mut _, - procinfo.as_mut_ptr(), - )) - .into_pyresult(vm)?; - procinfo.assume_init() - }; + let procinfo = host_winapi::create_process( + app_name, + command_line, + args.inherit_handles, + args.creation_flags, + env, + current_dir, + startup_info, + handle_list, + ) + .map_err(|e| e.to_pyexception(vm))?; Ok(( - WinHandle(procinfo.hProcess), - WinHandle(procinfo.hThread), - procinfo.dwProcessId, - procinfo.dwThreadId, + WinHandle(procinfo.process), + WinHandle(procinfo.thread), + procinfo.process_id, + procinfo.thread_id, )) } @@ -364,33 +289,20 @@ mod _winapi { process_id: u32, vm: &VirtualMachine, ) -> PyResult { - let handle = unsafe { - windows_sys::Win32::System::Threading::OpenProcess( - desired_access, - i32::from(inherit_handle), - process_id, - ) - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_process(desired_access, inherit_handle, process_id) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn ExitProcess(exit_code: u32) { - unsafe { windows_sys::Win32::System::Threading::ExitProcess(exit_code) } + host_winapi::exit_process(exit_code) } #[pyfunction] fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool { let exe_name = exe_name.as_wtf8().to_wide_with_nul(); - let return_value = unsafe { - windows_sys::Win32::System::Environment::NeedCurrentDirectoryForExePathW( - exe_name.as_ptr(), - ) - }; - return_value != 0 + host_winapi::need_current_directory_for_exe_path_w(exe_name.as_ptr()) } #[pyfunction] @@ -401,8 +313,7 @@ mod _winapi { ) -> PyResult<()> { let src_path = std::path::Path::new(src_path.expect_str()); let dest_path = std::path::Path::new(dest_path.expect_str()); - - junction::create(src_path, dest_path).map_err(|e| e.to_pyexception(vm)) + host_winapi::create_junction(src_path, dest_path).map_err(|e| e.to_pyexception(vm)) } fn getenvironment(env: ArgMapping, vm: &VirtualMachine) -> PyResult> { @@ -416,125 +327,40 @@ mod _winapi { return Err(vm.new_runtime_error("environment changed size during iteration")); } - // Deduplicate case-insensitive keys, keeping the last value - use std::collections::HashMap; - let mut last_entry: HashMap = HashMap::new(); + let mut entries = Vec::with_capacity(keys.len()); for (k, v) in keys.into_iter().zip(values) { let k = PyStrRef::try_from_object(vm, k)?; - let k = k.expect_str(); + let k = k.expect_str().to_owned(); let v = PyStrRef::try_from_object(vm, v)?; - let v = v.expect_str(); - if k.contains('\0') || v.contains('\0') { - return Err(crate::exceptions::cstring_error(vm)); - } - if k.is_empty() || k[1..].contains('=') { - return Err(vm.new_value_error("illegal environment variable name")); - } - let key_upper = k.to_uppercase(); - let mut entry = widestring::WideString::new(); - entry.push_str(k); - entry.push_str("="); - entry.push_str(v); - entry.push_str("\0"); - last_entry.insert(key_upper, entry); + let v = v.expect_str().to_owned(); + entries.push((k, v)); } - // Sort by uppercase key for case-insensitive ordering - let mut entries: Vec<(String, widestring::WideString)> = last_entry.into_iter().collect(); - entries.sort_by(|a, b| a.0.cmp(&b.0)); - - let mut out = widestring::WideString::new(); - for (_, entry) in entries { - out.push(entry); - } - // Each entry ends with \0, so one more \0 terminates the block. - // For empty env, we need \0\0 as a valid empty environment block. - if out.is_empty() { - out.push_str("\0"); - } - out.push_str("\0"); - Ok(out.into_vec()) - } - - struct AttrList { - handlelist: Option>, - attrlist: Vec, - } - impl Drop for AttrList { - fn drop(&mut self) { - unsafe { - windows_sys::Win32::System::Threading::DeleteProcThreadAttributeList( - self.attrlist.as_mut_ptr() as *mut _, - ) - }; - } + host_winapi::build_environment_block(entries).map_err(|err| match err { + host_winapi::BuildEnvironmentBlockError::ContainsNul => { + crate::exceptions::cstring_error(vm) + } + host_winapi::BuildEnvironmentBlockError::IllegalName => { + vm.new_value_error("illegal environment variable name") + } + }) } - fn getattributelist(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult> { - >::try_from_object(vm, obj)? - .map(|mapping| { - let handlelist = mapping - .as_ref() - .get_item("handle_list", vm) - .ok() - .and_then(|obj| { - >>::try_from_object(vm, obj) - .map(|s| match s { - Some(s) if !s.is_empty() => Some(s.into_vec()), - _ => None, - }) - .transpose() - }) - .transpose()?; - - let attr_count = handlelist.is_some() as u32; - let (result, mut size) = unsafe { - let mut size = core::mem::MaybeUninit::uninit(); - let result = WindowsSysResult( - windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( - core::ptr::null_mut(), - attr_count, - 0, - size.as_mut_ptr(), - ), - ); - (result, size.assume_init()) - }; - if !result.is_err() - || unsafe { windows_sys::Win32::Foundation::GetLastError() } - != windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER - { - return Err(vm.new_last_os_error()); - } - let mut attrlist = vec![0u8; size]; - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( - attrlist.as_mut_ptr() as *mut _, - attr_count, - 0, - &mut size, - ) - }) - .into_pyresult(vm)?; - let mut attrs = AttrList { - handlelist, - attrlist, - }; - if let Some(ref mut handlelist) = attrs.handlelist { - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::UpdateProcThreadAttribute( - attrs.attrlist.as_mut_ptr() as _, - 0, - (2 & 0xffff) | 0x20000, // PROC_THREAD_ATTRIBUTE_HANDLE_LIST - handlelist.as_mut_ptr() as _, - (handlelist.len() * core::mem::size_of::()) as _, - core::ptr::null_mut(), - core::ptr::null(), - ) + fn get_handle_list(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult>> { + let Some(mapping) = >::try_from_object(vm, obj)? else { + return Ok(None); + }; + mapping + .as_ref() + .get_item("handle_list", vm) + .ok() + .and_then(|obj| { + >>::try_from_object(vm, obj) + .map(|s| match s { + Some(s) if !s.is_empty() => Some(s.into_vec()), + _ => None, }) - .into_pyresult(vm)?; - } - Ok(attrs) + .transpose() }) .transpose() } @@ -543,18 +369,13 @@ mod _winapi { fn WaitForSingleObject(h: WinHandle, ms: i64, vm: &VirtualMachine) -> PyResult { // Negative values (e.g., -1) map to INFINITE (0xFFFFFFFF) let ms = if ms < 0 { - windows_sys::Win32::System::Threading::INFINITE + host_winapi::INFINITE_TIMEOUT } else if ms > u32::MAX as i64 { return Err(vm.new_overflow_error("timeout value is too large")); } else { ms as u32 }; - let ret = unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(h.0, ms) }; - if ret == windows_sys::Win32::Foundation::WAIT_FAILED { - Err(vm.new_last_os_error()) - } else { - Ok(ret) - } + host_winapi::wait_for_single_object(h.0, ms).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -564,13 +385,10 @@ mod _winapi { milliseconds: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::WAIT_FAILED; - use windows_sys::Win32::System::Threading::WaitForMultipleObjects as WinWaitForMultipleObjects; - - let handles: Vec = handle_seq + let handles: Vec = handle_seq .into_vec() .into_iter() - .map(|h| h as HANDLE) + .map(|h| h as host_winapi::Handle) .collect(); if handles.is_empty() { @@ -581,40 +399,18 @@ mod _winapi { return Err(vm.new_value_error("WaitForMultipleObjects supports at most 64 handles")); } - let ret = unsafe { - WinWaitForMultipleObjects( - handles.len() as u32, - handles.as_ptr(), - if wait_all { 1 } else { 0 }, - milliseconds, - ) - }; - - if ret == WAIT_FAILED { - Err(vm.new_last_os_error()) - } else { - Ok(ret) - } + host_winapi::wait_for_multiple_objects(&handles, wait_all, milliseconds) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn GetExitCodeProcess(h: WinHandle, vm: &VirtualMachine) -> PyResult { - unsafe { - let mut ec = core::mem::MaybeUninit::uninit(); - WindowsSysResult(windows_sys::Win32::System::Threading::GetExitCodeProcess( - h.0, - ec.as_mut_ptr(), - )) - .to_pyresult(vm)?; - Ok(ec.assume_init()) - } + host_winapi::get_exit_code_process(h.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn TerminateProcess(h: WinHandle, exit_code: u32) -> WindowsSysResult { - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::TerminateProcess(h.0, exit_code) - }) + WindowsSysResult(host_winapi::terminate_process(h.0, exit_code)) } #[pyfunction] @@ -623,22 +419,10 @@ mod _winapi { name: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { - let handle = unsafe { - match name.flatten() { - Some(name) => { - let name_wide = name.as_wtf8().to_wide_with_nul(); - windows_sys::Win32::System::JobObjects::CreateJobObjectW( - null(), - name_wide.as_ptr(), - ) - } - None => windows_sys::Win32::System::JobObjects::CreateJobObjectW(null(), null()), - } - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + let name = name.flatten().map(|name| name.as_wtf8().to_wide_with_nul()); + host_winapi::create_job_object_w(name.as_ref().map_or(null(), |name| name.as_ptr())) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -647,59 +431,25 @@ mod _winapi { process: WinHandle, vm: &VirtualMachine, ) -> PyResult<()> { - let ret = unsafe { - windows_sys::Win32::System::JobObjects::AssignProcessToJobObject(job.0, process.0) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::assign_process_to_job_object(job.0, process.0) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn TerminateJobObject(job: WinHandle, exit_code: u32, vm: &VirtualMachine) -> PyResult<()> { - let ret = - unsafe { windows_sys::Win32::System::JobObjects::TerminateJobObject(job.0, exit_code) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::terminate_job_object(job.0, exit_code).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn SetJobObjectKillOnClose(job: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::JobObjects::{ - JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, - SetInformationJobObject, - }; - let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { core::mem::zeroed() }; - info.BasicLimitInformation.LimitFlags = - windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - let ret = unsafe { - SetInformationJobObject( - job.0, - JobObjectExtendedLimitInformation, - &info as *const _ as *const core::ffi::c_void, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::set_job_object_kill_on_close(job.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn GetModuleFileName(handle: isize, vm: &VirtualMachine) -> PyResult { - let mut path: Vec = vec![0; MAX_PATH as usize]; - - let length = unsafe { - windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW( - handle as windows_sys::Win32::Foundation::HMODULE, - path.as_mut_ptr(), - path.len() as u32, - ) - }; + let mut path: Vec = vec![0; host_winapi::MAX_PATH_USIZE]; + + let length = host_winapi::get_module_file_name(handle as _, &mut path); if length == 0 { return Err(vm.new_runtime_error("GetModuleFileName failed")); } @@ -716,22 +466,14 @@ mod _winapi { vm: &VirtualMachine, ) -> PyResult { let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - windows_sys::Win32::System::Threading::OpenMutexW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_mutex_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn ReleaseMutex(handle: WinHandle) -> WindowsSysResult { - WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle.0) }) + WindowsSysResult(host_winapi::release_mutex(handle.0)) } // LOCALE_NAME_INVARIANT is an empty string in Windows API @@ -755,13 +497,14 @@ mod _winapi { src: PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Globalization::{ - LCMAP_BYTEREV, LCMAP_HASH, LCMAP_SORTHANDLE, LCMAP_SORTKEY, - LCMapStringEx as WinLCMapStringEx, - }; - // Reject unsupported flags - if flags & (LCMAP_SORTHANDLE | LCMAP_HASH | LCMAP_BYTEREV | LCMAP_SORTKEY) != 0 { + if flags + & (host_winapi::LCMAP_SORTHANDLE_FLAG + | host_winapi::LCMAP_HASH_FLAG + | host_winapi::LCMAP_BYTEREV_FLAG + | host_winapi::LCMAP_SORTKEY_FLAG) + != 0 + { return Err(vm.new_value_error("unsupported flags")); } @@ -773,46 +516,13 @@ mod _winapi { return Err(vm.new_overflow_error("input string is too long")); } - // First call to get required buffer size - let dest_size = unsafe { - WinLCMapStringEx( - locale_wide.as_ptr(), - flags, - src_wide.as_ptr(), - src_wide.len() as i32, - null_mut(), - 0, - null(), - null(), - 0, - ) - }; - - if dest_size <= 0 { - return Err(vm.new_last_os_error()); - } - - // Second call to perform the mapping - let mut dest = vec![0u16; dest_size as usize]; - let nmapped = unsafe { - WinLCMapStringEx( - locale_wide.as_ptr(), - flags, - src_wide.as_ptr(), - src_wide.len() as i32, - dest.as_mut_ptr(), - dest_size, - null(), - null(), - 0, - ) - }; - - if nmapped <= 0 { - return Err(vm.new_last_os_error()); - } - - dest.truncate(nmapped as usize); + let dest = host_winapi::lc_map_string_ex( + locale_wide.as_ptr(), + flags, + src_wide.as_ptr(), + src_wide.len() as i32, + ) + .map_err(|e| e.to_pyexception(vm))?; // Convert UTF-16 back to WTF-8 (handles surrogates properly) let result = Wtf8Buf::from_wide(&dest); @@ -842,28 +552,18 @@ mod _winapi { /// CreateNamedPipe - Create a named pipe #[pyfunction] fn CreateNamedPipe(args: CreateNamedPipeArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::System::Pipes::CreateNamedPipeW; - let name_wide = args.name.as_wtf8().to_wide_with_nul(); - - let handle = unsafe { - CreateNamedPipeW( - name_wide.as_ptr(), - args.open_mode, - args.pipe_mode, - args.max_instances, - args.out_buffer_size, - args.in_buffer_size, - args.default_timeout, - null(), // security_attributes - NULL for now - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_named_pipe_w( + name_wide.as_ptr(), + args.open_mode, + args.pipe_mode, + args.max_instances, + args.out_buffer_size, + args.in_buffer_size, + args.default_timeout, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } // ==================== Overlapped class ==================== @@ -873,144 +573,51 @@ mod _winapi { #[pyclass(name = "Overlapped", module = "_winapi")] #[derive(Debug, PyPayload)] struct Overlapped { - inner: PyMutex, - } - - struct OverlappedInner { - // Box ensures the OVERLAPPED struct stays at a stable heap address - // even when the containing Overlapped Python object is moved during - // into_pyobject(). The OS holds a pointer to this struct for pending - // I/O operations, so it must not be relocated. - overlapped: Box, - handle: HANDLE, - pending: bool, - completed: bool, - read_buffer: Option>, - write_buffer: Option>, - } - - impl core::fmt::Debug for OverlappedInner { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("OverlappedInner") - .field("handle", &self.handle) - .field("pending", &self.pending) - .field("completed", &self.completed) - .finish() - } + inner: PyMutex, } - unsafe impl Sync for OverlappedInner {} - unsafe impl Send for OverlappedInner {} - #[pyclass(with(Constructor))] impl Overlapped { - fn new_with_handle(handle: HANDLE) -> Self { - use windows_sys::Win32::System::Threading::CreateEventW; - - let event = unsafe { CreateEventW(null(), 1, 0, null()) }; - let mut overlapped: windows_sys::Win32::System::IO::OVERLAPPED = - unsafe { core::mem::zeroed() }; - overlapped.hEvent = event; - - Self { - inner: PyMutex::new(OverlappedInner { - overlapped: Box::new(overlapped), - handle, - pending: false, - completed: false, - read_buffer: None, - write_buffer: None, - }), - } + fn new_with_handle(handle: host_winapi::Handle, vm: &VirtualMachine) -> PyResult { + host_overlapped::Operation::new(handle) + .map(|inner| Self { + inner: PyMutex::new(inner), + }) + .map_err(|e| e.to_pyexception(vm)) } #[pymethod] fn GetOverlappedResult(&self, wait: bool, vm: &VirtualMachine) -> PyResult<(u32, u32)> { - use windows_sys::Win32::Foundation::{ - ERROR_IO_INCOMPLETE, ERROR_MORE_DATA, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, - GetLastError, - }; - use windows_sys::Win32::System::IO::GetOverlappedResult; - let mut inner = self.inner.lock(); - - let mut transferred: u32 = 0; - - let ret = unsafe { - GetOverlappedResult( - inner.handle, - &*inner.overlapped, - &mut transferred, - if wait { 1 } else { 0 }, - ) - }; - - let err = if ret == 0 { - unsafe { GetLastError() } - } else { - ERROR_SUCCESS - }; - - match err { - ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_OPERATION_ABORTED => { - inner.completed = true; - inner.pending = false; - } - ERROR_IO_INCOMPLETE => {} - _ => { - inner.pending = false; - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - - if inner.completed - && let Some(read_buffer) = &mut inner.read_buffer - && transferred != read_buffer.len() as u32 - { - read_buffer.truncate(transferred as usize); - } - - Ok((transferred, err)) + inner + .get_result(wait) + .map(|result| (result.transferred, result.error)) + .map_err(|e| e.to_pyexception(vm)) } #[pymethod] fn getbuffer(&self, vm: &VirtualMachine) -> PyResult> { let inner = self.inner.lock(); - if !inner.completed { + if !inner.is_completed() { return Err(vm.new_value_error( "can't get read buffer before GetOverlappedResult() signals the operation completed", )); } Ok(inner - .read_buffer - .as_ref() - .map(|buf| vm.ctx.new_bytes(buf.clone()).into())) + .read_buffer() + .map(|buf| vm.ctx.new_bytes(buf.to_vec()).into())) } #[pymethod] fn cancel(&self, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::IO::CancelIoEx; - let mut inner = self.inner.lock(); - let ret = if inner.pending { - unsafe { CancelIoEx(inner.handle, &*inner.overlapped) } - } else { - 1 - }; - if ret == 0 { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - inner.pending = false; - Ok(()) + inner.cancel().map_err(|e| e.to_pyexception(vm)) } #[pygetset] fn event(&self) -> isize { let inner = self.inner.lock(); - inner.overlapped.hEvent as isize + inner.event() as isize } } @@ -1020,18 +627,9 @@ mod _winapi { fn py_new( _cls: &Py, _args: Self::Args, - _vm: &VirtualMachine, + vm: &VirtualMachine, ) -> PyResult { - Ok(Self::new_with_handle(null_mut())) - } - } - - impl Drop for OverlappedInner { - fn drop(&mut self) { - use windows_sys::Win32::Foundation::CloseHandle; - if !self.overlapped.hEvent.is_null() { - unsafe { CloseHandle(self.overlapped.hEvent) }; - } + Self::new_with_handle(null_mut(), vm) } } @@ -1046,122 +644,55 @@ mod _winapi { #[pyfunction] fn ConnectNamedPipe(args: ConnectNamedPipeArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, GetLastError, - }; - let handle = args.handle; let use_overlapped = args.overlapped.unwrap_or(false); if use_overlapped { - // Overlapped (async) mode - let ov = Overlapped::new_with_handle(handle.0); - - let _ret = { + let ov = Overlapped::new_with_handle(handle.0, vm)?; + { let mut inner = ov.inner.lock(); - unsafe { - windows_sys::Win32::System::Pipes::ConnectNamedPipe( - handle.0, - &mut *inner.overlapped, - ) - } - }; - - let err = unsafe { GetLastError() }; - match err { - ERROR_IO_PENDING => { - let mut inner = ov.inner.lock(); - inner.pending = true; - } - ERROR_PIPE_CONNECTED => { - let inner = ov.inner.lock(); - unsafe { - windows_sys::Win32::System::Threading::SetEvent(inner.overlapped.hEvent); - } - } - _ => { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } + inner + .connect_named_pipe() + .map_err(|e| e.to_pyexception(vm))?; } - Ok(ov.into_pyobject(vm)) } else { - // Synchronous mode - let ret = unsafe { - windows_sys::Win32::System::Pipes::ConnectNamedPipe(handle.0, null_mut()) - }; - - if ret == 0 { - let err = unsafe { GetLastError() }; - if err != ERROR_PIPE_CONNECTED { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - + host_winapi::connect_named_pipe(handle.0).map_err(|e| e.to_pyexception(vm))?; Ok(vm.ctx.none()) } } /// Helper for GetShortPathName and GetLongPathName - fn get_path_name_impl( - path: &PyStrRef, - api_fn: unsafe extern "system" fn(*const u16, *mut u16, u32) -> u32, - vm: &VirtualMachine, - ) -> PyResult { - let path_wide = path.as_wtf8().to_wide_with_nul(); - - // First call to get required buffer size - let size = unsafe { api_fn(path_wide.as_ptr(), null_mut(), 0) }; - - if size == 0 { - return Err(vm.new_last_os_error()); - } - - // Second call to get the actual path - let mut buffer: Vec = vec![0; size as usize]; - let result = - unsafe { api_fn(path_wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) }; - - if result == 0 { - return Err(vm.new_last_os_error()); - } - - // Truncate to actual length (excluding null terminator) - buffer.truncate(result as usize); - + fn path_name_result_to_pystr(wide: Vec, vm: &VirtualMachine) -> PyStrRef { // Convert UTF-16 back to WTF-8 (handles surrogates properly) - let result_str = Wtf8Buf::from_wide(&buffer); - Ok(vm.ctx.new_str(result_str)) + let result_str = Wtf8Buf::from_wide(&wide); + vm.ctx.new_str(result_str) } /// GetShortPathName - Return the short version of the provided path. #[pyfunction] fn GetShortPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW; - get_path_name_impl(&path, GetShortPathNameW, vm) + let path_wide = path.as_wtf8().to_wide_with_nul(); + let wide = host_winapi::get_short_path_name_w(path_wide.as_ptr()) + .map_err(|e| e.to_pyexception(vm))?; + Ok(path_name_result_to_pystr(wide, vm)) } /// GetLongPathName - Return the long version of the provided path. #[pyfunction] fn GetLongPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW; - get_path_name_impl(&path, GetLongPathNameW, vm) + let path_wide = path.as_wtf8().to_wide_with_nul(); + let wide = host_winapi::get_long_path_name_w(path_wide.as_ptr()) + .map_err(|e| e.to_pyexception(vm))?; + Ok(path_name_result_to_pystr(wide, vm)) } /// WaitNamedPipe - Wait for an instance of a named pipe to become available. #[pyfunction] fn WaitNamedPipe(name: PyStrRef, timeout: u32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Pipes::WaitNamedPipeW; - let name_wide = name.as_wtf8().to_wide_with_nul(); - - let success = unsafe { WaitNamedPipeW(name_wide.as_ptr(), timeout) }; - - if success == 0 { - return Err(vm.new_last_os_error()); - } - - Ok(()) + host_winapi::wait_named_pipe_w(name_wide.as_ptr(), timeout) + .map_err(|e| e.to_pyexception(vm)) } /// PeekNamedPipe - Peek at data in a named pipe without removing it. @@ -1171,60 +702,33 @@ mod _winapi { size: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Pipes::PeekNamedPipe as WinPeekNamedPipe; - let size = size.unwrap_or(0); if size < 0 { return Err(vm.new_value_error("negative size")); } - let mut navail: u32 = 0; - let mut nleft: u32 = 0; - if size > 0 { - let mut buf = vec![0u8; size as usize]; - let mut nread: u32 = 0; - - let ret = unsafe { - WinPeekNamedPipe( - handle.0, - buf.as_mut_ptr() as *mut _, - size as u32, - &mut nread, - &mut navail, - &mut nleft, - ) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - buf.truncate(nread as usize); + let result = host_winapi::peek_named_pipe(handle.0, Some(size as u32)) + .map_err(|e| e.to_pyexception(vm))?; + let buf = result.data.unwrap_or_default(); let bytes: PyObjectRef = vm.ctx.new_bytes(buf).into(); Ok(vm .ctx .new_tuple(vec![ bytes, - vm.ctx.new_int(navail).into(), - vm.ctx.new_int(nleft).into(), + vm.ctx.new_int(result.available).into(), + vm.ctx.new_int(result.left_this_message).into(), ]) .into()) } else { - let ret = unsafe { - WinPeekNamedPipe(handle.0, null_mut(), 0, null_mut(), &mut navail, &mut nleft) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - + let result = + host_winapi::peek_named_pipe(handle.0, None).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_int(navail).into(), - vm.ctx.new_int(nleft).into(), + vm.ctx.new_int(result.available).into(), + vm.ctx.new_int(result.left_this_message).into(), ]) .into()) } @@ -1239,41 +743,19 @@ mod _winapi { name: Option, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::CreateEventW as WinCreateEventW; - let _ = security_attributes; // Ignored, always NULL let name_wide = name.map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { - WinCreateEventW( - null(), - i32::from(manual_reset), - i32::from(initial_state), - name_ptr, - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_event_w(manual_reset, initial_state, name_ptr) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// SetEvent - Set the specified event object to the signaled state. #[pyfunction] fn SetEvent(event: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::SetEvent as WinSetEvent; - - let ret = unsafe { WinSetEvent(event.0) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - Ok(()) + host_winapi::set_event(event.0).map_err(|e| e.to_pyexception(vm)) } #[derive(FromArgs)] @@ -1289,96 +771,29 @@ mod _winapi { /// WriteFile - Write data to a file or I/O device. #[pyfunction] fn WriteFile(args: WriteFileArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::WriteFile as WinWriteFile; - let handle = args.handle; let use_overlapped = args.overlapped; let buf = args.buffer.borrow_buf(); - let len = core::cmp::min(buf.len(), u32::MAX as usize) as u32; if use_overlapped { - use windows_sys::Win32::Foundation::ERROR_IO_PENDING; - - let ov = Overlapped::new_with_handle(handle.0); + let ov = Overlapped::new_with_handle(handle.0, vm)?; let err = { let mut inner = ov.inner.lock(); - inner.write_buffer = Some(buf.to_vec()); - let write_buf = inner.write_buffer.as_ref().unwrap(); - let mut written: u32 = 0; - let ret = unsafe { - WinWriteFile( - handle.0, - write_buf.as_ptr() as *const _, - len, - &mut written, - &mut *inner.overlapped, - ) - }; - - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - - if ret == 0 && err != ERROR_IO_PENDING { - return Err(vm.new_last_os_error()); - } - if ret == 0 && err == ERROR_IO_PENDING { - inner.pending = true; - } - - err + inner.write(&buf).map_err(|e| e.to_pyexception(vm))? }; - // Without GIL, the Python-level PipeConnection._send_bytes has a - // race on _send_ov when the caller (SimpleQueue) skips locking on - // Windows. Wait for completion here so the caller never sees - // ERROR_IO_PENDING and never blocks in WaitForMultipleObjects, - // keeping the _send_ov window negligibly small. - if err == ERROR_IO_PENDING { - let event = ov.inner.lock().overlapped.hEvent; - vm.allow_threads(|| unsafe { - windows_sys::Win32::System::Threading::WaitForSingleObject( - event, - windows_sys::Win32::System::Threading::INFINITE, - ); - }); - let result = vm - .ctx - .new_tuple(vec![ov.into_pyobject(vm), vm.ctx.new_int(0u32).into()]); - return Ok(result.into()); - } - let result = vm .ctx .new_tuple(vec![ov.into_pyobject(vm), vm.ctx.new_int(err).into()]); return Ok(result.into()); } - let mut written: u32 = 0; - let ret = unsafe { - WinWriteFile( - handle.0, - buf.as_ptr() as *const _, - len, - &mut written, - null_mut(), - ) - }; - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } + let result = host_winapi::write_file(handle.0, &buf).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_int(written).into(), - vm.ctx.new_int(err).into(), + vm.ctx.new_int(result.written).into(), + vm.ctx.new_int(result.error).into(), ]) .into()) } @@ -1396,45 +811,15 @@ mod _winapi { /// ReadFile - Read data from a file or I/O device. #[pyfunction] fn ReadFile(args: ReadFileArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::ReadFile as WinReadFile; - let handle = args.handle; let size = args.size; let use_overlapped = args.overlapped; if use_overlapped { - use windows_sys::Win32::Foundation::ERROR_IO_PENDING; - - let ov = Overlapped::new_with_handle(handle.0); + let ov = Overlapped::new_with_handle(handle.0, vm)?; let err = { let mut inner = ov.inner.lock(); - inner.read_buffer = Some(vec![0u8; size as usize]); - let read_buf = inner.read_buffer.as_mut().unwrap(); - let mut nread: u32 = 0; - let ret = unsafe { - WinReadFile( - handle.0, - read_buf.as_mut_ptr() as *mut _, - size, - &mut nread, - &mut *inner.overlapped, - ) - }; - - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - - if ret == 0 && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA { - return Err(vm.new_last_os_error()); - } - if ret == 0 && err == ERROR_IO_PENDING { - inner.pending = true; - } - - err + inner.read(size).map_err(|e| e.to_pyexception(vm))? }; let result = vm .ctx @@ -1442,31 +827,12 @@ mod _winapi { return Ok(result.into()); } - let mut buf = vec![0u8; size as usize]; - let mut nread: u32 = 0; - let ret = unsafe { - WinReadFile( - handle.0, - buf.as_mut_ptr() as *mut _, - size, - &mut nread, - null_mut(), - ) - }; - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - if ret == 0 && err != ERROR_MORE_DATA { - return Err(vm.new_last_os_error()); - } - buf.truncate(nread as usize); + let result = host_winapi::read_file(handle.0, size).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_bytes(buf).into(), - vm.ctx.new_int(err).into(), + vm.ctx.new_bytes(result.data).into(), + vm.ctx.new_int(result.error).into(), ]) .into()) } @@ -1480,38 +846,21 @@ mod _winapi { collect_data_timeout: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::System::Pipes::SetNamedPipeHandleState as WinSetNamedPipeHandleState; - - let mut dw_args: [u32; 3] = [0; 3]; - let mut p_args: [*mut u32; 3] = [null_mut(); 3]; - let objs = [&mode, &max_collection_count, &collect_data_timeout]; - for (i, obj) in objs.iter().enumerate() { + let mut values = [None; 3]; + for (index, obj) in objs.iter().enumerate() { if !vm.is_none(obj) { - dw_args[i] = u32::try_from_object(vm, (*obj).clone())?; - p_args[i] = &mut dw_args[i]; + values[index] = Some(u32::try_from_object(vm, (*obj).clone())?); } } - - let ret = - unsafe { WinSetNamedPipeHandleState(named_pipe.0, p_args[0], p_args[1], p_args[2]) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::set_named_pipe_handle_state(named_pipe.0, values[0], values[1], values[2]) + .map_err(|e| e.to_pyexception(vm)) } /// ResetEvent - Reset the specified event object to the nonsignaled state. #[pyfunction] fn ResetEvent(event: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::ResetEvent as WinResetEvent; - - let ret = unsafe { WinResetEvent(event.0) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::reset_event(event.0).map_err(|e| e.to_pyexception(vm)) } /// CreateMutexW - Create or open a named or unnamed mutex object. @@ -1522,18 +871,12 @@ mod _winapi { name: Option, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::CreateMutexW as WinCreateMutexW; - let _ = security_attributes; let name_wide = name.map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { WinCreateMutexW(null(), i32::from(initial_owner), name_ptr) }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::create_mutex_w(initial_owner, name_ptr) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// OpenEventW - Open an existing named event object. @@ -1544,21 +887,10 @@ mod _winapi { name: PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::OpenEventW as WinOpenEventW; - let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - WinOpenEventW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_event_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } const MAXIMUM_WAIT_OBJECTS: usize = 64; @@ -1571,21 +903,15 @@ mod _winapi { milliseconds: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use alloc::sync::Arc; - use core::sync::atomic::{AtomicU32, Ordering}; - use windows_sys::Win32::Foundation::{CloseHandle, WAIT_FAILED, WAIT_OBJECT_0}; - use windows_sys::Win32::System::SystemInformation::GetTickCount64; - use windows_sys::Win32::System::Threading::{ - CreateEventW as WinCreateEventW, CreateThread, GetExitCodeThread, - INFINITE as WIN_INFINITE, ResumeThread, SetEvent as WinSetEvent, TerminateThread, - WaitForMultipleObjects, - }; - - let milliseconds = milliseconds.unwrap_or(WIN_INFINITE); + let milliseconds = milliseconds.unwrap_or(host_winapi::INFINITE_TIMEOUT); // Get handles from sequence let seq = ArgSequence::::try_from_object(vm, handle_seq)?; - let handles: Vec = seq.into_vec(); + let handles: Vec = seq + .into_vec() + .into_iter() + .map(|handle| handle as _) + .collect(); let nhandles = handles.len(); if nhandles == 0 { @@ -1603,299 +929,56 @@ mod _winapi { ))); } - // Create batches of handles - let batch_size = MAXIMUM_WAIT_OBJECTS - 1; // Leave room for cancel_event - let mut batches: Vec> = Vec::new(); - let mut i = 0; - while i < nhandles { - let end = core::cmp::min(i + batch_size, nhandles); - batches.push(handles[i..end].to_vec()); - i = end; - } - #[cfg(feature = "threading")] let sigint_event = { let is_main = crate::stdlib::_thread::get_ident() == vm.state.main_thread_ident.load(); if is_main { - let handle = crate::signal::get_sigint_event().unwrap_or_else(|| { - let handle = unsafe { WinCreateEventW(null(), 1, 0, null()) }; - if !handle.is_null() { - crate::signal::set_sigint_event(handle as isize); - } - handle as isize - }); - if handle == 0 { None } else { Some(handle) } + let handle = crate::signal::get_sigint_event().map_or_else( + || { + let handle = host_winapi::create_event_w(true, false, null()) + .unwrap_or(core::ptr::null_mut()); + if !handle.is_null() { + crate::signal::set_sigint_event(handle as isize); + } + handle + }, + |handle| handle as host_winapi::Handle, + ); + if handle.is_null() { None } else { Some(handle) } } else { None } }; #[cfg(not(feature = "threading"))] - let sigint_event: Option = None; - - if wait_all { - // For wait_all, we wait sequentially for each batch - let mut err: Option = None; - let deadline = if milliseconds != WIN_INFINITE { - Some(unsafe { GetTickCount64() } + milliseconds as u64) - } else { - None - }; - - for batch in &batches { - let timeout = if let Some(deadline) = deadline { - let now = unsafe { GetTickCount64() }; - if now >= deadline { - err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); - break; - } - (deadline - now) as u32 - } else { - WIN_INFINITE - }; - - let batch_handles: Vec<_> = batch.iter().map(|&h| h as _).collect(); - let result = unsafe { - WaitForMultipleObjects( - batch_handles.len() as u32, - batch_handles.as_ptr(), - 1, // wait_all = TRUE - timeout, - ) - }; - - if result == WAIT_FAILED { - err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - break; - } - if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); - break; - } - - if let Some(sigint_event) = sigint_event { - let sig_result = unsafe { - windows_sys::Win32::System::Threading::WaitForSingleObject( - sigint_event as _, - 0, - ) - }; - if sig_result == WAIT_OBJECT_0 { - err = Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT); - break; - } - if sig_result == WAIT_FAILED { - err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - break; - } - } - } - - if let Some(err) = err { - if err == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.timeout_error.to_owned(), - None, - "timed out", - ) - .upcast()); - } - if err == windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT { - return Err(vm - .new_errno_error(libc::EINTR, "Interrupted system call") - .upcast()); - } - return Err(vm.new_os_error(err as i32)); - } - - Ok(vm.ctx.none()) - } else { - // For wait_any, we use threads to wait on each batch in parallel - let cancel_event = unsafe { WinCreateEventW(null(), 1, 0, null()) }; // Manual reset, not signaled - if cancel_event.is_null() { - return Err(vm.new_last_os_error()); - } - - struct BatchData { - handles: Vec, - cancel_event: isize, - handle_base: usize, - result: AtomicU32, - thread: core::cell::UnsafeCell, - } - - unsafe impl Send for BatchData {} - unsafe impl Sync for BatchData {} - - let batch_data: Vec> = batches - .iter() - .enumerate() - .map(|(idx, batch)| { - let base = idx * batch_size; - let mut handles_with_cancel = batch.clone(); - handles_with_cancel.push(cancel_event as isize); - Arc::new(BatchData { - handles: handles_with_cancel, - cancel_event: cancel_event as isize, - handle_base: base, - result: AtomicU32::new(WAIT_FAILED), - thread: core::cell::UnsafeCell::new(0), - }) - }) - .collect(); - - // Thread function - extern "system" fn batch_wait_thread(param: *mut core::ffi::c_void) -> u32 { - let data = unsafe { &*(param as *const BatchData) }; - let handles: Vec<_> = data.handles.iter().map(|&h| h as _).collect(); - let result = unsafe { - WaitForMultipleObjects( - handles.len() as u32, - handles.as_ptr(), - 0, // wait_any - WIN_INFINITE, - ) - }; - data.result.store(result, Ordering::SeqCst); - - if result == WAIT_FAILED { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - unsafe { WinSetEvent(data.cancel_event as _) }; - return err; - } else if result >= windows_sys::Win32::Foundation::WAIT_ABANDONED_0 - && result - < windows_sys::Win32::Foundation::WAIT_ABANDONED_0 - + MAXIMUM_WAIT_OBJECTS as u32 - { - data.result.store(WAIT_FAILED, Ordering::SeqCst); - unsafe { WinSetEvent(data.cancel_event as _) }; - return windows_sys::Win32::Foundation::ERROR_ABANDONED_WAIT_0; - } - 0 - } - - // Create threads - let mut thread_handles: Vec = Vec::new(); - for data in &batch_data { - let thread = unsafe { - CreateThread( - null(), - 1, // Smallest stack - Some(batch_wait_thread), - Arc::as_ptr(data) as *const _ as *mut _, - 4, // CREATE_SUSPENDED - null_mut(), - ) - }; - if thread.is_null() { - // Cleanup on error - for h in &thread_handles { - unsafe { TerminateThread(*h as _, 0) }; - unsafe { CloseHandle(*h as _) }; - } - unsafe { CloseHandle(cancel_event) }; - return Err(vm.new_last_os_error()); - } - unsafe { *data.thread.get() = thread as isize }; - thread_handles.push(thread as isize); - } - - // Resume all threads - for &thread in &thread_handles { - unsafe { ResumeThread(thread as _) }; - } - - // Wait for any thread to complete - let mut thread_handles_raw: Vec<_> = thread_handles.iter().map(|&h| h as _).collect(); - if let Some(sigint_event) = sigint_event { - thread_handles_raw.push(sigint_event as _); - } - let result = unsafe { - WaitForMultipleObjects( - thread_handles_raw.len() as u32, - thread_handles_raw.as_ptr(), - 0, // wait_any - milliseconds, + let sigint_event: Option = None; + + match host_winapi::batched_wait_for_multiple_objects( + &handles, + wait_all, + milliseconds, + sigint_event, + ) { + Ok(host_winapi::BatchedWaitResult::All) => Ok(vm.ctx.none()), + Ok(host_winapi::BatchedWaitResult::Indices(indices)) => Ok(vm + .ctx + .new_list( + indices + .into_iter() + .map(|index| vm.ctx.new_int(index).into()) + .collect(), ) - }; - - let err = if result == WAIT_FAILED { - Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }) - } else if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) - } else if sigint_event.is_some() - && result == WAIT_OBJECT_0 + thread_handles_raw.len() as u32 - { - Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) - } else { - None - }; - - // Signal cancel event to stop other threads - unsafe { WinSetEvent(cancel_event) }; - - // Wait for all threads to finish - let thread_handles_only: Vec<_> = thread_handles.iter().map(|&h| h as _).collect(); - unsafe { - WaitForMultipleObjects( - thread_handles_only.len() as u32, - thread_handles_only.as_ptr(), - 1, // wait_all - WIN_INFINITE, + .into()), + Err(host_winapi::BatchedWaitError::Timeout) => Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.timeout_error.to_owned(), + None, + "timed out", ) - }; - - // Check for errors from threads - let mut thread_err = err; - for data in &batch_data { - if thread_err.is_none() && data.result.load(Ordering::SeqCst) == WAIT_FAILED { - let mut exit_code: u32 = 0; - let thread = unsafe { *data.thread.get() }; - if unsafe { GetExitCodeThread(thread as _, &mut exit_code) } == 0 { - thread_err = - Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - } else if exit_code != 0 { - thread_err = Some(exit_code); - } - } - let thread = unsafe { *data.thread.get() }; - unsafe { CloseHandle(thread as _) }; - } - - unsafe { CloseHandle(cancel_event) }; - - // Return result - if let Some(e) = thread_err { - if e == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.timeout_error.to_owned(), - None, - "timed out", - ) - .upcast()); - } - if e == windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT { - return Err(vm - .new_errno_error(libc::EINTR, "Interrupted system call") - .upcast()); - } - return Err(vm.new_os_error(e as i32)); - } - - // Collect triggered indices - let mut triggered_indices: Vec = Vec::new(); - for data in &batch_data { - let result = data.result.load(Ordering::SeqCst); - let triggered = result as i32 - WAIT_OBJECT_0 as i32; - // Check if it's a valid handle index (not the cancel_event which is last) - if triggered >= 0 && (triggered as usize) < data.handles.len() - 1 { - let index = data.handle_base + triggered as usize; - triggered_indices.push(vm.ctx.new_int(index).into()); - } - } - - Ok(vm.ctx.new_list(triggered_indices).into()) + .upcast()), + Err(host_winapi::BatchedWaitError::Interrupted) => Err(vm + .new_errno_error(libc::EINTR, "Interrupted system call") + .upcast()), + Err(host_winapi::BatchedWaitError::Os(err)) => Err(vm.new_os_error(err as i32)), } } @@ -1910,8 +993,6 @@ mod _winapi { name: Option, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Memory::CreateFileMappingW; - if let Some(ref n) = name && n.as_bytes().contains(&0) { @@ -1921,22 +1002,15 @@ mod _winapi { } let name_wide = name.as_ref().map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { - CreateFileMappingW( - file_handle.0, - null(), - protect, - max_size_high, - max_size_low, - name_ptr, - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::create_file_mapping_w( + file_handle.0, + protect, + max_size_high, + max_size_low, + name_ptr, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// OpenFileMapping - Open a named file mapping object. @@ -1947,26 +1021,15 @@ mod _winapi { name: PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Memory::OpenFileMappingW; - if name.as_bytes().contains(&0) { return Err( vm.new_value_error("OpenFileMapping: name must not contain null characters") ); } let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - OpenFileMappingW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_file_mapping_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// MapViewOfFile - Map a view of a file mapping into the address space. @@ -1979,57 +1042,26 @@ mod _winapi { number_bytes: usize, vm: &VirtualMachine, ) -> PyResult { - let address = unsafe { - windows_sys::Win32::System::Memory::MapViewOfFile( - file_map.0, - desired_access, - file_offset_high, - file_offset_low, - number_bytes, - ) - }; - - let ptr = address.Value; - if ptr.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(ptr as isize) + host_winapi::map_view_of_file( + file_map.0, + desired_access, + file_offset_high, + file_offset_low, + number_bytes, + ) + .map_err(|e| e.to_pyexception(vm)) } /// UnmapViewOfFile - Unmap a mapped view of a file. #[pyfunction] fn UnmapViewOfFile(address: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS; - - let view = MEMORY_MAPPED_VIEW_ADDRESS { - Value: address as *mut core::ffi::c_void, - }; - let ret = unsafe { windows_sys::Win32::System::Memory::UnmapViewOfFile(view) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::unmap_view_of_file(address).map_err(|e| e.to_pyexception(vm)) } /// VirtualQuerySize - Return the size of a memory region. #[pyfunction] fn VirtualQuerySize(address: isize, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::System::Memory::{MEMORY_BASIC_INFORMATION, VirtualQuery}; - - let mut mbi: MEMORY_BASIC_INFORMATION = unsafe { core::mem::zeroed() }; - let ret = unsafe { - VirtualQuery( - address as *const core::ffi::c_void, - &mut mbi, - core::mem::size_of::(), - ) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(mbi.RegionSize) + host_winapi::virtual_query_size(address).map_err(|e| e.to_pyexception(vm)) } /// CopyFile2 - Copy a file with extended parameters. @@ -2041,29 +1073,10 @@ mod _winapi { _progress_routine: OptionalArg, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::Storage::FileSystem::{ - COPYFILE2_EXTENDED_PARAMETERS, CopyFile2 as WinCopyFile2, - }; - let src_wide = existing_file_name.as_wtf8().to_wide_with_nul(); let dst_wide = new_file_name.as_wtf8().to_wide_with_nul(); - - let mut params: COPYFILE2_EXTENDED_PARAMETERS = unsafe { core::mem::zeroed() }; - params.dwSize = core::mem::size_of::() as u32; - params.dwCopyFlags = flags; - - let hr = unsafe { WinCopyFile2(src_wide.as_ptr(), dst_wide.as_ptr(), ¶ms) }; - - if hr < 0 { - // HRESULT failure - convert to Windows error code - let err = if (hr as u32 >> 16) == 0x8007 { - (hr as u32) & 0xFFFF - } else { - hr as u32 - }; - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - Ok(()) + host_winapi::copy_file2(src_wide.as_ptr(), dst_wide.as_ptr(), flags) + .map_err(|e| e.to_pyexception(vm)) } /// _mimetypes_read_windows_registry - Read MIME type associations from registry. @@ -2072,110 +1085,15 @@ mod _winapi { on_type_read: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::System::Registry::{ - HKEY, HKEY_CLASSES_ROOT, KEY_READ, REG_SZ, RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, - RegQueryValueExW, - }; - - let mut hkcr: HKEY = null_mut() as HKEY; - let err = unsafe { RegOpenKeyExW(HKEY_CLASSES_ROOT, null(), 0, KEY_READ, &mut hkcr) }; - if err != 0 { - return Err(vm.new_os_error(err as i32)); - } - scopeguard::defer! { unsafe { RegCloseKey(hkcr) }; } - - let mut i: u32 = 0; - let mut entries: Vec<(String, String)> = Vec::new(); - - loop { - let mut ext_buf = [0u16; 128]; - let mut cch_ext: u32 = ext_buf.len() as u32; - - let err = unsafe { - RegEnumKeyExW( - hkcr, - i, - ext_buf.as_mut_ptr(), - &mut cch_ext, - null_mut(), - null_mut(), - null_mut(), - null_mut(), - ) - }; - i += 1; - - if err == windows_sys::Win32::Foundation::ERROR_NO_MORE_ITEMS { - break; - } - if err != 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { - return Err(vm.new_os_error(err as i32)); - } - - // Only process keys starting with '.' - if cch_ext == 0 || ext_buf[0] != b'.' as u16 { - continue; - } - - let ext_wide = &ext_buf[..cch_ext as usize]; - - // Open subkey to read Content Type - let mut subkey: HKEY = null_mut() as HKEY; - let err = unsafe { RegOpenKeyExW(hkcr, ext_buf.as_ptr(), 0, KEY_READ, &mut subkey) }; - if err == windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND - || err == windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED - { - continue; - } - if err != 0 { - return Err(vm.new_os_error(err as i32)); + host_winapi::read_windows_mimetype_registry_in_batches(|entries| { + for (mime_type, ext) in entries.drain(..) { + on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; } - - let content_type_key: Vec = "Content Type\0".encode_utf16().collect(); - let mut type_buf = [0u16; 256]; - let mut cb_type: u32 = (type_buf.len() * 2) as u32; - let mut reg_type: u32 = 0; - - let err = unsafe { - RegQueryValueExW( - subkey, - content_type_key.as_ptr(), - null_mut(), - &mut reg_type, - type_buf.as_mut_ptr() as *mut u8, - &mut cb_type, - ) - }; - unsafe { RegCloseKey(subkey) }; - - if err != 0 || reg_type != REG_SZ || cb_type == 0 { - continue; - } - - // Convert wide strings to Rust strings - let type_len = (cb_type as usize / 2).saturating_sub(1); // exclude null terminator - let type_str = String::from_utf16_lossy(&type_buf[..type_len]); - let ext_str = String::from_utf16_lossy(ext_wide); - - if type_str.is_empty() { - continue; - } - - entries.push((type_str, ext_str)); - - // Flush buffer periodically to call Python callback - if entries.len() >= 64 { - for (mime_type, ext) in entries.drain(..) { - on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; - } - } - } - - // Process remaining entries - for (mime_type, ext) in entries { - on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; - } - - Ok(()) + Ok(()) + }) + .map_err(|err| match err { + host_winapi::MimeRegistryReadError::Os(err) => vm.new_os_error(err as i32), + host_winapi::MimeRegistryReadError::Callback(err) => err, + }) } } diff --git a/crates/vm/src/stdlib/_wmi.rs b/crates/vm/src/stdlib/_wmi.rs index 7236c74809e..d0f23ac6914 100644 --- a/crates/vm/src/stdlib/_wmi.rs +++ b/crates/vm/src/stdlib/_wmi.rs @@ -3,570 +3,15 @@ pub(crate) use _wmi::module_def; -// COM/WMI FFI declarations (not inside pymodule to avoid macro issues) -mod wmi_ffi { - #![allow(unsafe_op_in_unsafe_fn)] - use core::ffi::c_void; - - pub(super) type HRESULT = i32; - - #[repr(C)] - pub(super) struct GUID { - pub(super) data1: u32, - pub(super) data2: u16, - pub(super) data3: u16, - pub(super) data4: [u8; 8], - } - - // Opaque VARIANT type (24 bytes covers both 32-bit and 64-bit) - #[repr(C, align(8))] - pub(super) struct VARIANT([u64; 3]); - - impl VARIANT { - pub(super) fn zeroed() -> Self { - Self([0u64; 3]) - } - } - - // CLSID_WbemLocator = {4590F811-1D3A-11D0-891F-00AA004B2E24} - pub(super) const CLSID_WBEM_LOCATOR: GUID = GUID { - data1: 0x4590F811, - data2: 0x1D3A, - data3: 0x11D0, - data4: [0x89, 0x1F, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], - }; - - // IID_IWbemLocator = {DC12A687-737F-11CF-884D-00AA004B2E24} - pub(super) const IID_IWBEM_LOCATOR: GUID = GUID { - data1: 0xDC12A687, - data2: 0x737F, - data3: 0x11CF, - data4: [0x88, 0x4D, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], - }; - - // COM constants - pub(super) const COINIT_APARTMENTTHREADED: u32 = 0x2; - pub(super) const CLSCTX_INPROC_SERVER: u32 = 0x1; - pub(super) const RPC_C_AUTHN_LEVEL_DEFAULT: u32 = 0; - pub(super) const RPC_C_IMP_LEVEL_IMPERSONATE: u32 = 3; - pub(super) const RPC_C_AUTHN_LEVEL_CALL: u32 = 3; - pub(super) const RPC_C_AUTHN_WINNT: u32 = 10; - pub(super) const RPC_C_AUTHZ_NONE: u32 = 0; - pub(super) const EOAC_NONE: u32 = 0; - pub(super) const RPC_E_TOO_LATE: HRESULT = 0x80010119_u32 as i32; - - // WMI constants - pub(super) const WBEM_FLAG_FORWARD_ONLY: i32 = 0x20; - pub(super) const WBEM_FLAG_RETURN_IMMEDIATELY: i32 = 0x10; - pub(super) const WBEM_S_FALSE: HRESULT = 1; - pub(super) const WBEM_S_NO_MORE_DATA: HRESULT = 0x40005; - pub(super) const WBEM_INFINITE: i32 = -1; - pub(super) const WBEM_FLAVOR_MASK_ORIGIN: i32 = 0x60; - pub(super) const WBEM_FLAVOR_ORIGIN_SYSTEM: i32 = 0x40; - - #[link(name = "ole32")] - unsafe extern "system" { - pub(super) fn CoInitializeEx(pvReserved: *mut c_void, dwCoInit: u32) -> HRESULT; - - pub(super) fn CoUninitialize(); - - pub(super) fn CoInitializeSecurity( - pSecDesc: *const c_void, - cAuthSvc: i32, - asAuthSvc: *const c_void, - pReserved1: *const c_void, - dwAuthnLevel: u32, - dwImpLevel: u32, - pAuthList: *const c_void, - dwCapabilities: u32, - pReserved3: *const c_void, - ) -> HRESULT; - - pub(super) fn CoCreateInstance( - rclsid: *const GUID, - pUnkOuter: *mut c_void, - dwClsContext: u32, - riid: *const GUID, - ppv: *mut *mut c_void, - ) -> HRESULT; - - pub(super) fn CoSetProxyBlanket( - pProxy: *mut c_void, - dwAuthnSvc: u32, - dwAuthzSvc: u32, - pServerPrincName: *const u16, - dwAuthnLevel: u32, - dwImpLevel: u32, - pAuthInfo: *const c_void, - dwCapabilities: u32, - ) -> HRESULT; - } - - #[link(name = "oleaut32")] - unsafe extern "system" { - pub(super) fn SysAllocString(psz: *const u16) -> *mut u16; - pub(super) fn SysFreeString(bstrString: *mut u16); - pub(super) fn VariantClear(pvarg: *mut VARIANT) -> HRESULT; - } - - #[link(name = "propsys")] - unsafe extern "system" { - pub(super) fn VariantToString( - varIn: *const VARIANT, - pszBuf: *mut u16, - cchBuf: u32, - ) -> HRESULT; - } - - /// Release a COM object (IUnknown::Release, vtable index 2) - pub(super) unsafe fn com_release(this: *mut c_void) { - if !this.is_null() { - let vtable = *(this as *const *const usize); - let release: unsafe extern "system" fn(*mut c_void) -> u32 = - core::mem::transmute(*vtable.add(2)); - release(this); - } - } - - /// IWbemLocator::ConnectServer (vtable index 3) - #[allow(clippy::too_many_arguments)] - pub(super) unsafe fn locator_connect_server( - this: *mut c_void, - network_resource: *const u16, - user: *const u16, - password: *const u16, - locale: *const u16, - security_flags: i32, - authority: *const u16, - ctx: *mut c_void, - services: *mut *mut c_void, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - *const u16, - *const u16, - *const u16, - *const u16, - i32, - *const u16, - *mut c_void, - *mut *mut c_void, - ) -> HRESULT = core::mem::transmute(*vtable.add(3)); - method( - this, - network_resource, - user, - password, - locale, - security_flags, - authority, - ctx, - services, - ) - } - - /// IWbemServices::ExecQuery (vtable index 20) - pub(super) unsafe fn services_exec_query( - this: *mut c_void, - query_language: *const u16, - query: *const u16, - flags: i32, - ctx: *mut c_void, - enumerator: *mut *mut c_void, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - *const u16, - *const u16, - i32, - *mut c_void, - *mut *mut c_void, - ) -> HRESULT = core::mem::transmute(*vtable.add(20)); - method(this, query_language, query, flags, ctx, enumerator) - } - - /// IEnumWbemClassObject::Next (vtable index 4) - pub(super) unsafe fn enum_next( - this: *mut c_void, - timeout: i32, - count: u32, - objects: *mut *mut c_void, - returned: *mut u32, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - i32, - u32, - *mut *mut c_void, - *mut u32, - ) -> HRESULT = core::mem::transmute(*vtable.add(4)); - method(this, timeout, count, objects, returned) - } - - /// IWbemClassObject::BeginEnumeration (vtable index 8) - pub(super) unsafe fn object_begin_enumeration(this: *mut c_void, enum_flags: i32) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn(*mut c_void, i32) -> HRESULT = - core::mem::transmute(*vtable.add(8)); - method(this, enum_flags) - } - - /// IWbemClassObject::Next (vtable index 9) - pub(super) unsafe fn object_next( - this: *mut c_void, - flags: i32, - name: *mut *mut u16, - val: *mut VARIANT, - cim_type: *mut i32, - flavor: *mut i32, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - i32, - *mut *mut u16, - *mut VARIANT, - *mut i32, - *mut i32, - ) -> HRESULT = core::mem::transmute(*vtable.add(9)); - method(this, flags, name, val, cim_type, flavor) - } - - /// IWbemClassObject::EndEnumeration (vtable index 10) - pub(super) unsafe fn object_end_enumeration(this: *mut c_void) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn(*mut c_void) -> HRESULT = - core::mem::transmute(*vtable.add(10)); - method(this) - } -} - #[pymodule] mod _wmi { - use super::wmi_ffi::*; use crate::builtins::PyStrRef; use crate::convert::ToPyException; use crate::{PyResult, VirtualMachine}; - use core::ffi::c_void; - use core::ptr::{null, null_mut}; - use windows_sys::Win32::Foundation::{ - CloseHandle, ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_NOT_ENOUGH_MEMORY, GetLastError, - HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT, - }; - use windows_sys::Win32::Storage::FileSystem::{ReadFile, WriteFile}; - use windows_sys::Win32::System::Pipes::CreatePipe; - use windows_sys::Win32::System::Threading::{ - CreateEventW, CreateThread, GetExitCodeThread, SetEvent, WaitForSingleObject, - }; + use rustpython_host_env::wmi as host_wmi; const BUFFER_SIZE: usize = 8192; - fn hresult_from_win32(err: u32) -> HRESULT { - if err == 0 { - 0 - } else { - ((err & 0xFFFF) | 0x80070000) as HRESULT - } - } - - fn succeeded(hr: HRESULT) -> bool { - hr >= 0 - } - - fn failed(hr: HRESULT) -> bool { - hr < 0 - } - - fn wide_str(s: &str) -> Vec { - s.encode_utf16().chain(core::iter::once(0)).collect() - } - - unsafe fn wcslen(s: *const u16) -> usize { - unsafe { - let mut len = 0; - while *s.add(len) != 0 { - len += 1; - } - len - } - } - - unsafe fn wait_event(event: HANDLE, timeout: u32) -> u32 { - unsafe { - match WaitForSingleObject(event, timeout) { - WAIT_OBJECT_0 => 0, - WAIT_TIMEOUT => WAIT_TIMEOUT, - _ => GetLastError(), - } - } - } - - struct QueryThreadData { - query: Vec, - write_pipe: HANDLE, - init_event: HANDLE, - connect_event: HANDLE, - } - - // SAFETY: QueryThreadData contains HANDLEs (isize) which are safe to send across threads - unsafe impl Send for QueryThreadData {} - - unsafe extern "system" fn query_thread(param: *mut c_void) -> u32 { - unsafe { query_thread_impl(param) } - } - - unsafe fn query_thread_impl(param: *mut c_void) -> u32 { - unsafe { - let data = Box::from_raw(param as *mut QueryThreadData); - let write_pipe = data.write_pipe; - let init_event = data.init_event; - let connect_event = data.connect_event; - - let mut locator: *mut c_void = null_mut(); - let mut services: *mut c_void = null_mut(); - let mut enumerator: *mut c_void = null_mut(); - let mut hr: HRESULT = 0; - - // gh-125315: Copy the query string first - let bstr_query = SysAllocString(data.query.as_ptr()); - if bstr_query.is_null() { - hr = hresult_from_win32(ERROR_NOT_ENOUGH_MEMORY); - } - - drop(data); - - if succeeded(hr) { - hr = CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED); - } - - if failed(hr) { - CloseHandle(write_pipe); - if !bstr_query.is_null() { - SysFreeString(bstr_query); - } - return hr as u32; - } - - hr = CoInitializeSecurity( - null(), - -1, - null(), - null(), - RPC_C_AUTHN_LEVEL_DEFAULT, - RPC_C_IMP_LEVEL_IMPERSONATE, - null(), - EOAC_NONE, - null(), - ); - // gh-96684: CoInitializeSecurity will fail if another part of the app has - // already called it. - if hr == RPC_E_TOO_LATE { - hr = 0; - } - - if succeeded(hr) { - hr = CoCreateInstance( - &CLSID_WBEM_LOCATOR, - null_mut(), - CLSCTX_INPROC_SERVER, - &IID_IWBEM_LOCATOR, - &mut locator, - ); - } - if succeeded(hr) && SetEvent(init_event) == 0 { - hr = hresult_from_win32(GetLastError()); - } - - if succeeded(hr) { - let root_cimv2 = wide_str("ROOT\\CIMV2"); - let bstr_root = SysAllocString(root_cimv2.as_ptr()); - hr = locator_connect_server( - locator, - bstr_root, - null(), - null(), - null(), - 0, - null(), - null_mut(), - &mut services, - ); - if !bstr_root.is_null() { - SysFreeString(bstr_root); - } - } - if succeeded(hr) && SetEvent(connect_event) == 0 { - hr = hresult_from_win32(GetLastError()); - } - - if succeeded(hr) { - hr = CoSetProxyBlanket( - services, - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - null(), - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - null(), - EOAC_NONE, - ); - } - if succeeded(hr) { - let wql = wide_str("WQL"); - let bstr_wql = SysAllocString(wql.as_ptr()); - hr = services_exec_query( - services, - bstr_wql, - bstr_query, - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - null_mut(), - &mut enumerator, - ); - if !bstr_wql.is_null() { - SysFreeString(bstr_wql); - } - } - - // Enumerate results and write to pipe - let mut value: *mut c_void; - let mut start_of_enum = true; - let null_sep: u16 = 0; - let eq_sign: u16 = b'=' as u16; - - while succeeded(hr) { - let mut got: u32 = 0; - let mut written: u32 = 0; - value = null_mut(); - hr = enum_next(enumerator, WBEM_INFINITE, 1, &mut value, &mut got); - - if hr == WBEM_S_FALSE { - hr = 0; - break; - } - if failed(hr) || got != 1 || value.is_null() { - continue; - } - - if !start_of_enum - && WriteFile( - write_pipe, - &null_sep as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - { - hr = hresult_from_win32(GetLastError()); - com_release(value); - break; - } - start_of_enum = false; - - hr = object_begin_enumeration(value, 0); - if failed(hr) { - com_release(value); - break; - } - - while succeeded(hr) { - let mut prop_name: *mut u16 = null_mut(); - let mut prop_value = VARIANT::zeroed(); - let mut flavor: i32 = 0; - - hr = object_next( - value, - 0, - &mut prop_name, - &mut prop_value, - null_mut(), - &mut flavor, - ); - - if hr == WBEM_S_NO_MORE_DATA { - hr = 0; - break; - } - - if succeeded(hr) - && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM - { - let mut prop_str = [0u16; BUFFER_SIZE]; - hr = - VariantToString(&prop_value, prop_str.as_mut_ptr(), BUFFER_SIZE as u32); - - if succeeded(hr) { - let cb_str1 = (wcslen(prop_name) * 2) as u32; - let cb_str2 = (wcslen(prop_str.as_ptr()) * 2) as u32; - - if WriteFile( - write_pipe, - prop_name as *const _, - cb_str1, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - &eq_sign as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - prop_str.as_ptr() as *const _, - cb_str2, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - &null_sep as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - { - hr = hresult_from_win32(GetLastError()); - } - } - - VariantClear(&mut prop_value); - SysFreeString(prop_name); - } - } - - object_end_enumeration(value); - com_release(value); - } - - // Cleanup - if !bstr_query.is_null() { - SysFreeString(bstr_query); - } - if !enumerator.is_null() { - com_release(enumerator); - } - if !services.is_null() { - com_release(services); - } - if !locator.is_null() { - com_release(locator); - } - CoUninitialize(); - CloseHandle(write_pipe); - - hr as u32 - } - } - - /// Runs a WMI query against the local machine. - /// - /// This returns a single string with 'name=value' pairs in a flat array separated - /// by null characters. #[pyfunction] fn exec_query(query: PyStrRef, vm: &VirtualMachine) -> PyResult { let query_str = query.expect_str(); @@ -578,128 +23,14 @@ mod _wmi { return Err(vm.new_value_error("only SELECT queries are supported")); } - let query_wide = wide_str(query_str); - - let mut h_thread: HANDLE = null_mut(); - let mut err: u32 = 0; - let mut buffer = [0u16; BUFFER_SIZE]; - let mut offset: u32 = 0; - let mut bytes_read: u32 = 0; - - let mut read_pipe: HANDLE = null_mut(); - let mut write_pipe: HANDLE = null_mut(); - - unsafe { - let init_event = CreateEventW(null(), 1, 0, null()); - let connect_event = CreateEventW(null(), 1, 0, null()); - - if init_event.is_null() - || connect_event.is_null() - || CreatePipe(&mut read_pipe, &mut write_pipe, null(), 0) == 0 - { - err = GetLastError(); - } else { - let thread_data = Box::new(QueryThreadData { - query: query_wide, - write_pipe, - init_event, - connect_event, - }); - let thread_data_ptr = Box::into_raw(thread_data); - - h_thread = CreateThread( - null(), - 0, - Some(query_thread), - thread_data_ptr as *const _ as *mut _, - 0, - null_mut(), - ); - - if h_thread.is_null() { - err = GetLastError(); - // Thread didn't start, so recover data and close write pipe - let data = Box::from_raw(thread_data_ptr); - CloseHandle(data.write_pipe); - } + match host_wmi::exec_query(query_str) { + Ok(result) => Ok(result), + Err(host_wmi::ExecQueryError::MoreData) => { + Err(vm.new_os_error(format!("Query returns more than {BUFFER_SIZE} characters"))) } - - // gh-112278: Timeout for COM init and WMI connection - if err == 0 { - err = wait_event(init_event, 1000); - if err == 0 { - err = wait_event(connect_event, 100); - } - } - - // Read results from pipe - while err == 0 { - let buf_ptr = (buffer.as_mut_ptr() as *mut u8).add(offset as usize); - let buf_remaining = (BUFFER_SIZE * 2) as u32 - offset; - - if ReadFile( - read_pipe, - buf_ptr as *mut _, - buf_remaining, - &mut bytes_read, - null_mut(), - ) != 0 - { - offset += bytes_read; - if offset >= (BUFFER_SIZE * 2) as u32 { - err = ERROR_MORE_DATA; - } - } else { - err = GetLastError(); - } + Err(host_wmi::ExecQueryError::Code(err)) => { + Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)) } - - if !read_pipe.is_null() { - CloseHandle(read_pipe); - } - - if !h_thread.is_null() { - let thread_err: u32; - match WaitForSingleObject(h_thread, 100) { - WAIT_OBJECT_0 => { - let mut exit_code: u32 = 0; - if GetExitCodeThread(h_thread, &mut exit_code) == 0 { - thread_err = GetLastError(); - } else { - thread_err = exit_code; - } - } - WAIT_TIMEOUT => { - thread_err = WAIT_TIMEOUT; - } - _ => { - thread_err = GetLastError(); - } - } - if err == 0 || err == ERROR_BROKEN_PIPE { - err = thread_err; - } - - CloseHandle(h_thread); - } - - CloseHandle(init_event); - CloseHandle(connect_event); - } - - if err == ERROR_MORE_DATA { - return Err( - vm.new_os_error(format!("Query returns more than {BUFFER_SIZE} characters")) - ); - } else if err != 0 { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); } - - if offset == 0 { - return Ok(String::new()); - } - - let char_count = (offset as usize) / 2 - 1; - Ok(String::from_utf16_lossy(&buffer[..char_count])) } } diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index dba40d78f38..8358d41b2b4 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -737,7 +737,7 @@ mod builtins { } ReadlineResult::Io(e) => Err(vm.new_os_error(e.to_string())), #[cfg(unix)] - ReadlineResult::OsError(num) => Err(vm.new_os_error(num.to_string())), + ReadlineResult::OsError(num) => Err(vm.new_os_error(num)), ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), } } else { @@ -754,9 +754,7 @@ mod builtins { /// In this case, rustyline may hang because it uses raw mode. #[cfg(unix)] fn is_pty_child() -> bool { - use nix::unistd::{getpid, getsid}; - // If this process is a session leader, we're likely in a PTY child - getsid(None) == Ok(getpid()) + crate::host_env::posix::is_session_leader() } #[cfg(not(unix))] diff --git a/crates/vm/src/stdlib/errno.rs b/crates/vm/src/stdlib/errno.rs index d7a0a222a76..5b0d666984b 100644 --- a/crates/vm/src/stdlib/errno.rs +++ b/crates/vm/src/stdlib/errno.rs @@ -24,46 +24,7 @@ mod errno_mod { } #[cfg(any(unix, windows, target_os = "wasi"))] -pub mod errors { - pub use libc::*; - #[cfg(windows)] - pub use windows_sys::Win32::{ - Foundation::*, - Networking::WinSock::{ - WSABASEERR, WSADESCRIPTION_LEN, WSAEACCES, WSAEADDRINUSE, WSAEADDRNOTAVAIL, - WSAEAFNOSUPPORT, WSAEALREADY, WSAEBADF, WSAECANCELLED, WSAECONNABORTED, - WSAECONNREFUSED, WSAECONNRESET, WSAEDESTADDRREQ, WSAEDISCON, WSAEDQUOT, WSAEFAULT, - WSAEHOSTDOWN, WSAEHOSTUNREACH, WSAEINPROGRESS, WSAEINTR, WSAEINVAL, - WSAEINVALIDPROCTABLE, WSAEINVALIDPROVIDER, WSAEISCONN, WSAELOOP, WSAEMFILE, - WSAEMSGSIZE, WSAENAMETOOLONG, WSAENETDOWN, WSAENETRESET, WSAENETUNREACH, WSAENOBUFS, - WSAENOMORE, WSAENOPROTOOPT, WSAENOTCONN, WSAENOTEMPTY, WSAENOTSOCK, WSAEOPNOTSUPP, - WSAEPFNOSUPPORT, WSAEPROCLIM, WSAEPROTONOSUPPORT, WSAEPROTOTYPE, - WSAEPROVIDERFAILEDINIT, WSAEREFUSED, WSAEREMOTE, WSAESHUTDOWN, WSAESOCKTNOSUPPORT, - WSAESTALE, WSAETIMEDOUT, WSAETOOMANYREFS, WSAEUSERS, WSAEWOULDBLOCK, WSAID_ACCEPTEX, - WSAID_CONNECTEX, WSAID_DISCONNECTEX, WSAID_GETACCEPTEXSOCKADDRS, WSAID_TRANSMITFILE, - WSAID_TRANSMITPACKETS, WSAID_WSAPOLL, WSAID_WSARECVMSG, WSANO_DATA, WSANO_RECOVERY, - WSANOTINITIALISED, WSAPROTOCOL_LEN, WSASERVICE_NOT_FOUND, WSASYS_STATUS_LEN, - WSASYSCALLFAILURE, WSASYSNOTREADY, WSATRY_AGAIN, WSATYPE_NOT_FOUND, WSAVERNOTSUPPORTED, - }, - }; - #[cfg(windows)] - macro_rules! reexport_wsa { - ($($errname:ident),*$(,)?) => { - paste::paste! { - $(pub const $errname: i32 = windows_sys::Win32::Networking::WinSock:: [] as i32;)* - } - } - } - #[cfg(windows)] - reexport_wsa! { - EADDRINUSE, EADDRNOTAVAIL, EAFNOSUPPORT, EALREADY, ECONNABORTED, ECONNREFUSED, ECONNRESET, - EDESTADDRREQ, EDQUOT, EHOSTDOWN, EHOSTUNREACH, EINPROGRESS, EISCONN, ELOOP, EMSGSIZE, - ENETDOWN, ENETRESET, ENETUNREACH, ENOBUFS, ENOPROTOOPT, ENOTCONN, ENOTSOCK, EOPNOTSUPP, - EPFNOSUPPORT, EPROTONOSUPPORT, EPROTOTYPE, EREMOTE, ESHUTDOWN, ESOCKTNOSUPPORT, ESTALE, - ETIMEDOUT, ETOOMANYREFS, EUSERS, EWOULDBLOCK, - // TODO: EBADF should be here once winerrs are translated to errnos but it messes up some things atm - } -} +pub use rustpython_host_env::errno::errors; #[cfg(any(unix, windows, target_os = "wasi"))] macro_rules! e { diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index e3aa7432b71..774b3cf087d 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -13,10 +13,9 @@ mod msvcrt { use itertools::Itertools; use rustpython_host_env::msvcrt as host_msvcrt; use std::os::windows::io::AsRawHandle; - use windows_sys::Win32::System::Diagnostics::Debug; #[pyattr] - use windows_sys::Win32::System::Diagnostics::Debug::{ + use host_msvcrt::{ SEM_FAILCRITICALERRORS, SEM_NOALIGNMENTFAULTEXCEPT, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX, }; @@ -139,7 +138,7 @@ mod msvcrt { #[allow(non_snake_case)] #[pyfunction] - fn SetErrorMode(mode: Debug::THREAD_ERROR_MODE, _: &VirtualMachine) -> u32 { + fn SetErrorMode(mode: host_msvcrt::ErrorMode, _: &VirtualMachine) -> u32 { host_msvcrt::set_error_mode(mode) } } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 7b3dc5c8b4d..ed6061f7fd2 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -7,27 +7,19 @@ pub use module::raw_set_handle_inheritable; pub(crate) mod module { use crate::{ Py, PyResult, TryFromObject, VirtualMachine, - builtins::{ - PyBaseExceptionRef, PyBytes, PyDictRef, PyListRef, PyStr, PyStrRef, PyTupleRef, - }, + builtins::{PyBytes, PyDictRef, PyListRef, PyStr, PyStrRef, PyTupleRef}, convert::ToPyException, exceptions::OSErrorBuilder, function::{ArgMapping, Either, OptionalArg}, - host_env::{crt_fd, suppress_iph, windows::ToWideString}, + host_env::{crt_fd, windows::ToWideString}, ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; - use core::mem::MaybeUninit; use libc::intptr_t; use rustpython_common::wtf8::Wtf8Buf; use rustpython_host_env::nt as host_nt; + use std::os::windows::ffi::OsStringExt; use std::os::windows::io::AsRawHandle; - use std::{io, os::windows::ffi::OsStringExt}; - use windows_sys::Win32::{ - Foundation::{self, INVALID_HANDLE_VALUE}, - Storage::FileSystem, - System::{Console, Threading}, - }; #[pyattr] use libc::{O_BINARY, O_NOINHERIT, O_RANDOM, O_SEQUENTIAL, O_TEMPORARY, O_TEXT}; @@ -57,7 +49,7 @@ pub(crate) mod module { const TMP_MAX: i32 = i32::MAX; #[pyattr] - use windows_sys::Win32::System::LibraryLoader::{ + use host_nt::{ LOAD_LIBRARY_SEARCH_APPLICATION_DIR as _LOAD_LIBRARY_SEARCH_APPLICATION_DIR, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS as _LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR as _LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, @@ -67,11 +59,8 @@ pub(crate) mod module { #[pyfunction] pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult { - let attr = unsafe { FileSystem::GetFileAttributesW(path.to_wide_cstring(vm)?.as_ptr()) }; - Ok(attr != FileSystem::INVALID_FILE_ATTRIBUTES - && (mode & 2 == 0 - || attr & FileSystem::FILE_ATTRIBUTE_READONLY == 0 - || attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0)) + let _ = path.to_wide_cstring(vm)?; + Ok(host_nt::access(path.as_ref(), mode)) } #[pyfunction] @@ -81,59 +70,14 @@ pub(crate) mod module { dir_fd: DirFd<'static, 0>, vm: &VirtualMachine, ) -> PyResult<()> { - // On Windows, use DeleteFileW directly. - // Rust's std::fs::remove_file may have different behavior for read-only files. - // See Py_DeleteFileW. - use windows_sys::Win32::Storage::FileSystem::{ - DeleteFileW, FindClose, FindFirstFileW, RemoveDirectoryW, WIN32_FIND_DATAW, - }; - use windows_sys::Win32::System::SystemServices::{ - IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, - }; - let [] = dir_fd.0; - let wide_path = path.to_wide_cstring(vm)?; - let attrs = unsafe { FileSystem::GetFileAttributesW(wide_path.as_ptr()) }; - - let mut is_directory = false; - let mut is_link = false; - - if attrs != FileSystem::INVALID_FILE_ATTRIBUTES { - is_directory = (attrs & FileSystem::FILE_ATTRIBUTE_DIRECTORY) != 0; - - // Check if it's a symlink or junction point - if is_directory && (attrs & FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) != 0 { - let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; - let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; - if handle != INVALID_HANDLE_VALUE { - is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK - || find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT; - unsafe { FindClose(handle) }; - } - } - } - - let result = if is_directory && is_link { - unsafe { RemoveDirectoryW(wide_path.as_ptr()) } - } else { - unsafe { DeleteFileW(wide_path.as_ptr()) } - }; - - if result == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - Ok(()) + let _ = path.to_wide_cstring(vm)?; + host_nt::remove(path.as_ref()).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction] pub(super) fn _supports_virtual_terminal() -> bool { - let mut mode = 0; - let handle = unsafe { Console::GetStdHandle(Console::STD_ERROR_HANDLE) }; - if unsafe { Console::GetConsoleMode(handle, &mut mode) } == 0 { - return false; - } - mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 + host_nt::supports_virtual_terminal() } #[derive(FromArgs)] @@ -149,69 +93,15 @@ pub(crate) mod module { #[pyfunction] pub(super) fn symlink(args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { use crate::exceptions::ToOSErrorBuilder; - use core::sync::atomic::{AtomicBool, Ordering}; - use windows_sys::Win32::Storage::FileSystem::WIN32_FILE_ATTRIBUTE_DATA; - use windows_sys::Win32::Storage::FileSystem::{ - CreateSymbolicLinkW, FILE_ATTRIBUTE_DIRECTORY, GetFileAttributesExW, - SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE, SYMBOLIC_LINK_FLAG_DIRECTORY, - }; - - static HAS_UNPRIVILEGED_FLAG: AtomicBool = AtomicBool::new(true); - - fn check_dir(src: &OsPath, dst: &OsPath) -> bool { - use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard; - - let dst_parent = dst.as_path().parent(); - let Some(dst_parent) = dst_parent else { - return false; - }; - let resolved = if src.as_path().is_absolute() { - src.as_path().to_path_buf() - } else { - dst_parent.join(src.as_path()) - }; - let wide = match widestring::WideCString::from_os_str(&resolved) { - Ok(wide) => wide, - Err(_) => return false, - }; - let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core::mem::zeroed() }; - let ok = unsafe { - GetFileAttributesExW( - wide.as_ptr(), - GetFileExInfoStandard, - &mut info as *mut _ as *mut _, - ) - }; - ok != 0 && (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 - } - - let mut flags = 0u32; - if HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) { - flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - } - if args.target_is_directory.target_is_directory || check_dir(&args.src, &args.dst) { - flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - } - let src = args.src.to_wide_cstring(vm)?; let dst = args.dst.to_wide_cstring(vm)?; - - let mut result = unsafe { CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), flags) }; - if !result - && HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) - && unsafe { Foundation::GetLastError() } == Foundation::ERROR_INVALID_PARAMETER - { - let flags = flags & !SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - result = unsafe { CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), flags) }; - if result - || unsafe { Foundation::GetLastError() } != Foundation::ERROR_INVALID_PARAMETER - { - HAS_UNPRIVILEGED_FLAG.store(false, Ordering::Relaxed); - } - } - - if !result { - let err = io::Error::last_os_error(); + if let Err(err) = host_nt::symlink( + args.src.as_ref(), + args.dst.as_ref(), + &src, + &dst, + args.target_is_directory.target_is_directory, + ) { let builder = err.to_os_error_builder(vm); let builder = builder .filename(args.src.filename(vm)) @@ -236,10 +126,7 @@ pub(crate) mod module { fn environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in crate::host_env::os::vars() { - // Skip hidden Windows environment variables (e.g., =C:, =D:, =ExitCode) - // These are internal cmd.exe bookkeeping variables that store per-drive - // current directories and cannot be reliably modified via _wputenv(). + for (key, value) in host_nt::visible_env_vars() { if key.starts_with('=') { continue; } @@ -251,10 +138,7 @@ pub(crate) mod module { #[pyfunction] fn _create_environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in crate::host_env::os::vars() { - if key.starts_with('=') { - continue; - } + for (key, value) in host_nt::visible_env_vars() { environ.set_item(&key, vm.new_pyobj(value), vm).unwrap(); } environ @@ -274,10 +158,6 @@ pub(crate) mod module { const S_IWRITE: u32 = 128; - fn win32_hchmod(handle: Foundation::HANDLE, mode: u32, vm: &VirtualMachine) -> PyResult<()> { - host_nt::win32_hchmod(handle, mode, S_IWRITE).map_err(|e| e.to_pyexception(vm)) - } - fn fchmod_impl(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> { host_nt::fchmod(fd, mode, S_IWRITE).map_err(|e| e.to_pyexception(vm)) } @@ -319,30 +199,9 @@ pub(crate) mod module { let follow_symlinks = follow_symlinks.into_option().unwrap_or(false); if follow_symlinks { - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, - FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING, - }; - let wide = path.to_wide_cstring(vm)?; - let handle = unsafe { - CreateFileW( - wide.as_ptr(), - FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - let result = win32_hchmod(handle, mode, vm); - unsafe { Foundation::CloseHandle(handle) }; - result + host_nt::chmod_follow(&wide, mode, S_IWRITE) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } else { win32_lchmod(&path, mode, vm) } @@ -352,31 +211,8 @@ pub(crate) mod module { /// Uses FindFirstFileW to get the name as stored on the filesystem. #[pyfunction] fn _findfirstfile(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::host_env::windows::ToWideString; - use std::os::windows::ffi::OsStringExt; - use windows_sys::Win32::Storage::FileSystem::{ - FindClose, FindFirstFileW, WIN32_FIND_DATAW, - }; - - let wide_path = path.as_ref().to_wide_with_nul(); - let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; - - let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - - unsafe { FindClose(handle) }; - - // Convert the filename from the find data to a Rust string - // cFileName is a null-terminated wide string - let len = find_data - .cFileName - .iter() - .position(|&c| c == 0) - .unwrap_or(find_data.cFileName.len()); - let filename = std::ffi::OsString::from_wide(&find_data.cFileName[..len]); + let filename = host_nt::find_first_file_name(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let filename_str = filename .to_str() .ok_or_else(|| vm.new_unicode_decode_error("filename contains invalid UTF-8"))?; @@ -406,296 +242,53 @@ pub(crate) mod module { /// _testInfo - determine file type based on attributes and reparse tag fn _test_info(attributes: u32, reparse_tag: u32, disk_device: bool, tested_type: u32) -> bool { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - use windows_sys::Win32::System::SystemServices::{ - IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, - }; - - match tested_type { - PY_IFREG => { - // diskDevice && attributes && !(attributes & FILE_ATTRIBUTE_DIRECTORY) - disk_device && attributes != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 - } - PY_IFDIR => (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0, - PY_IFLNK => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag == IO_REPARSE_TAG_SYMLINK - } - PY_IFMNT => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag == IO_REPARSE_TAG_MOUNT_POINT - } - PY_IFLRP => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && is_reparse_tag_name_surrogate(reparse_tag) - } - PY_IFRRP => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag != 0 - && !is_reparse_tag_name_surrogate(reparse_tag) - } - _ => false, - } - } - - fn is_reparse_tag_name_surrogate(tag: u32) -> bool { - (tag & 0x20000000) != 0 - } - - fn file_info_error_is_trustworthy(error: u32) -> bool { - use windows_sys::Win32::Foundation; - matches!( - error, - Foundation::ERROR_FILE_NOT_FOUND - | Foundation::ERROR_PATH_NOT_FOUND - | Foundation::ERROR_NOT_READY - | Foundation::ERROR_BAD_NET_NAME - | Foundation::ERROR_BAD_NETPATH - | Foundation::ERROR_BAD_PATHNAME - | Foundation::ERROR_INVALID_NAME - | Foundation::ERROR_FILENAME_EXCED_RANGE - ) + host_nt::test_info(attributes, reparse_tag, disk_device, tested_type) } /// _testFileTypeByHandle - test file type using an open handle fn _test_file_type_by_handle( - handle: windows_sys::Win32::Foundation::HANDLE, + handle: host_nt::Handle, tested_type: u32, disk_only: bool, ) -> bool { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_TAG_INFO, FILE_BASIC_INFO, FILE_TYPE_DISK, - FileAttributeTagInfo as FileAttributeTagInfoClass, FileBasicInfo, - GetFileInformationByHandleEx, GetFileType, + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - - let disk_device = unsafe { GetFileType(handle) } == FILE_TYPE_DISK; - if disk_only && !disk_device { - return false; - } - - if tested_type != PY_IFREG && tested_type != PY_IFDIR { - // For symlinks/junctions, need FileAttributeTagInfo to get reparse tag - let mut info: FILE_ATTRIBUTE_TAG_INFO = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - handle, - FileAttributeTagInfoClass, - &mut info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return false; - } - _test_info( - info.FileAttributes, - info.ReparseTag, - disk_device, - tested_type, - ) - } else { - // For regular files/directories, FileBasicInfo is sufficient - let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - handle, - FileBasicInfo, - &mut info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return false; - } - _test_info(info.FileAttributes, 0, disk_device, tested_type) - } + host_nt::test_file_type_by_handle(handle, tested_type, disk_only) } /// _testFileTypeByName - test file type by path name fn _test_file_type_by_name(path: &std::path::Path, tested_type: u32) -> bool { - use crate::host_env::fileutils::windows::{ - FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, - }; - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_READ_ATTRIBUTES, OPEN_EXISTING, - }; - use windows_sys::Win32::Storage::FileSystem::{FILE_DEVICE_CD_ROM, FILE_DEVICE_DISK}; - use windows_sys::Win32::System::Ioctl::FILE_DEVICE_VIRTUAL_DISK; - - match get_file_information_by_name( - path.as_os_str(), - FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, - ) { - Ok(info) => { - let disk_device = matches!( - info.DeviceType, - FILE_DEVICE_DISK | FILE_DEVICE_VIRTUAL_DISK | FILE_DEVICE_CD_ROM - ); - let result = _test_info( - info.FileAttributes, - info.ReparseTag, - disk_device, - tested_type, - ); - if !result - || (tested_type != PY_IFREG && tested_type != PY_IFDIR) - || (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 - { - return result; - } - } - Err(err) => { - if let Some(code) = err.raw_os_error() - && file_info_error_is_trustworthy(code as u32) - { - return false; - } - } - } - - let wide_path = path.to_wide_with_nul(); - - let mut flags = FILE_FLAG_BACKUP_SEMANTICS; - if tested_type != PY_IFREG && tested_type != PY_IFDIR { - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - let handle = unsafe { - CreateFileW( - wide_path.as_ptr(), - FILE_READ_ATTRIBUTES, - 0, - core::ptr::null(), - OPEN_EXISTING, - flags, - core::ptr::null_mut(), - ) + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - - if handle != INVALID_HANDLE_VALUE { - let result = _test_file_type_by_handle(handle, tested_type, false); - unsafe { CloseHandle(handle) }; - return result; - } - - match unsafe { windows_sys::Win32::Foundation::GetLastError() } { - windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED - | windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION - | windows_sys::Win32::Foundation::ERROR_CANT_ACCESS_FILE - | windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER => { - let stat = if tested_type == PY_IFREG || tested_type == PY_IFDIR { - crate::windows::win32_xstat(path.as_os_str(), true) - } else { - crate::windows::win32_xstat(path.as_os_str(), false) - }; - if let Ok(st) = stat { - let disk_device = (st.st_mode & libc::S_IFREG as u16) != 0; - return _test_info( - st.st_file_attributes, - st.st_reparse_tag, - disk_device, - tested_type, - ); - } - } - _ => {} - } - - false + host_nt::test_file_type_by_name(path, tested_type) } /// _testFileExistsByName - test if path exists fn _test_file_exists_by_name(path: &std::path::Path, follow_links: bool) -> bool { - use crate::host_env::fileutils::windows::{ - FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, - }; - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_READ_ATTRIBUTES, OPEN_EXISTING, - }; - - match get_file_information_by_name( - path.as_os_str(), - FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, - ) { - Ok(info) => { - if (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 - || (!follow_links && is_reparse_tag_name_surrogate(info.ReparseTag)) - { - return true; - } - } - Err(err) => { - if let Some(code) = err.raw_os_error() - && file_info_error_is_trustworthy(code as u32) - { - return false; - } - } - } - - let wide_path = path.to_wide_with_nul(); - let mut flags = FILE_FLAG_BACKUP_SEMANTICS; - if !follow_links { - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - let handle = unsafe { - CreateFileW( - wide_path.as_ptr(), - FILE_READ_ATTRIBUTES, - 0, - core::ptr::null(), - OPEN_EXISTING, - flags, - core::ptr::null_mut(), - ) - }; - if handle != INVALID_HANDLE_VALUE { - if follow_links { - unsafe { CloseHandle(handle) }; - return true; - } - let is_regular_reparse_point = _test_file_type_by_handle(handle, PY_IFRRP, false); - unsafe { CloseHandle(handle) }; - if !is_regular_reparse_point { - return true; - } - let handle = unsafe { - CreateFileW( - wide_path.as_ptr(), - FILE_READ_ATTRIBUTES, - 0, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle != INVALID_HANDLE_VALUE { - unsafe { CloseHandle(handle) }; - return true; - } - } - - match unsafe { windows_sys::Win32::Foundation::GetLastError() } { - windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED - | windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION - | windows_sys::Win32::Foundation::ERROR_CANT_ACCESS_FILE - | windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER => { - let stat = crate::windows::win32_xstat(path.as_os_str(), follow_links); - return stat.is_ok(); - } - _ => {} - } - - false + host_nt::test_file_exists_by_name(path, follow_links) } /// _testFileType wrapper - handles both fd and path @@ -715,23 +308,8 @@ pub(crate) mod module { /// _testFileExists wrapper - handles both fd and path fn _test_file_exists(path_or_fd: &OsPathOrFd<'_>, follow_links: bool) -> bool { - use windows_sys::Win32::Storage::FileSystem::{FILE_TYPE_UNKNOWN, GetFileType}; - match path_or_fd { - OsPathOrFd::Fd(fd) => { - if let Ok(handle) = crate::host_env::crt_fd::as_handle(*fd) { - use std::os::windows::io::AsRawHandle; - let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) }; - // GetFileType(hfile) != FILE_TYPE_UNKNOWN || !GetLastError() - if file_type != FILE_TYPE_UNKNOWN { - return true; - } - // Check if GetLastError is 0 (no error means valid handle) - unsafe { windows_sys::Win32::Foundation::GetLastError() == 0 } - } else { - false - } - } + OsPathOrFd::Fd(fd) => host_nt::fd_exists(*fd), OsPathOrFd::Path(path) => _test_file_exists_by_name(path.as_ref(), follow_links), } } @@ -787,113 +365,18 @@ pub(crate) mod module { /// Check if a path is on a Windows Dev Drive. #[pyfunction] fn _path_isdevdrive(path: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::CloseHandle; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, - FILE_SHARE_WRITE, GetDriveTypeW, GetVolumePathNameW, OPEN_EXISTING, - }; - use windows_sys::Win32::System::IO::DeviceIoControl; - use windows_sys::Win32::System::Ioctl::FSCTL_QUERY_PERSISTENT_VOLUME_STATE; - use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED; - - // PERSISTENT_VOLUME_STATE_DEV_VOLUME flag - not yet in windows-sys - const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x00002000; - - // FILE_FS_PERSISTENT_VOLUME_INFORMATION structure - #[repr(C)] - struct FileFsPersistentVolumeInformation { - volume_flags: u32, - flag_mask: u32, - version: u32, - reserved: u32, - } - - let wide_path = path.to_wide_cstring(vm)?; - let mut volume = [0u16; Foundation::MAX_PATH as usize]; - - // Get volume path - let ret = unsafe { - GetVolumePathNameW(wide_path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - // Check if it's a fixed drive - if unsafe { GetDriveTypeW(volume.as_ptr()) } != DRIVE_FIXED { - return Ok(false); - } - - // Open the volume - let handle = unsafe { - CreateFileW( - volume.as_ptr(), - FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - // Query persistent volume state - let mut volume_state = FileFsPersistentVolumeInformation { - volume_flags: 0, - flag_mask: PERSISTENT_VOLUME_STATE_DEV_VOLUME, - version: 1, - reserved: 0, - }; - - let ret = unsafe { - DeviceIoControl( - handle, - FSCTL_QUERY_PERSISTENT_VOLUME_STATE, - &volume_state as *const _ as *const core::ffi::c_void, - core::mem::size_of::() as u32, - &mut volume_state as *mut _ as *mut core::ffi::c_void, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - core::ptr::null_mut(), - ) - }; - - unsafe { CloseHandle(handle) }; - - if ret == 0 { - let err = io::Error::last_os_error(); - // ERROR_INVALID_PARAMETER means not supported on this platform - if err.raw_os_error() == Some(Foundation::ERROR_INVALID_PARAMETER as i32) { - return Ok(false); - } - return Err(err.to_pyexception(vm)); - } - - Ok((volume_state.volume_flags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) != 0) - } - - // cwait is available on MSVC only - #[cfg(target_env = "msvc")] - unsafe extern "C" { - fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; + let _ = path.to_wide_cstring(vm)?; + host_nt::path_isdevdrive(path.as_ref()).map_err(|err| err.to_pyexception(vm)) } #[cfg(target_env = "msvc")] #[pyfunction] fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, u64)> { - let mut status: i32 = 0; - let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; - if pid == -1 { - Err(vm.new_last_errno_error()) - } else { - // Cast to unsigned to handle large exit codes (like 0xC000013A) - // then shift left by 8 to match POSIX waitpid format - let ustatus = (status as u32) as u64; - Ok((pid, ustatus << 8)) - } + let (pid, status) = host_nt::cwait(pid, opt).map_err(|_| vm.new_last_errno_error())?; + // Cast to unsigned to handle large exit codes (like 0xC000013A) + // then shift left by 8 to match POSIX waitpid format + let ustatus = (status as u32) as u64; + Ok((pid, ustatus << 8)) } #[cfg(target_env = "msvc")] @@ -904,31 +387,7 @@ pub(crate) mod module { #[pyfunction] fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { - let sig = sig as u32; - let pid = pid as u32; - - if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT { - let ret = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) }; - let res = if ret == 0 { - Err(vm.new_last_os_error()) - } else { - Ok(()) - }; - return res; - } - - let h = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) }; - if h.is_null() { - return Err(vm.new_last_os_error()); - } - let ret = unsafe { Threading::TerminateProcess(h, sig) }; - let res = if ret == 0 { - Err(vm.new_last_os_error()) - } else { - Ok(()) - }; - unsafe { Foundation::CloseHandle(h) }; - res + host_nt::kill(pid as u32, sig as u32).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] @@ -937,68 +396,13 @@ pub(crate) mod module { vm: &VirtualMachine, ) -> PyResult<_os::TerminalSizeData> { let fd = fd.unwrap_or(1); // default to stdout - - // Use _get_osfhandle for all fds let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; - let h = handle.as_raw_handle() as Foundation::HANDLE; - - let mut csbi = MaybeUninit::uninit(); - let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) }; - if ret == 0 { - // Check if error is due to lack of read access on a console handle - // ERROR_ACCESS_DENIED (5) means it's a console but without read permission - // In that case, try opening CONOUT$ directly with read access - let err = unsafe { Foundation::GetLastError() }; - if err != Foundation::ERROR_ACCESS_DENIED { - return Err(vm.new_last_os_error()); - } - let conout: Vec = "CONOUT$\0".encode_utf16().collect(); - let console_handle = unsafe { - FileSystem::CreateFileW( - conout.as_ptr(), - Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, - FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, - core::ptr::null(), - FileSystem::OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - if console_handle == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - let ret = - unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) }; - unsafe { Foundation::CloseHandle(console_handle) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - } - let csbi = unsafe { csbi.assume_init() }; - let w = csbi.srWindow; - let columns = (w.Right - w.Left + 1) as usize; - let lines = (w.Bottom - w.Top + 1) as usize; + let (columns, lines) = host_nt::get_terminal_size_handle(handle.as_raw_handle() as _) + .map_err(|_| vm.new_last_os_error())?; Ok(_os::TerminalSizeData { columns, lines }) } - #[cfg(target_env = "msvc")] - unsafe extern "C" { - fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t; - fn _wexecve( - cmdname: *const u16, - argv: *const *const u16, - envp: *const *const u16, - ) -> intptr_t; - fn _wspawnv(mode: i32, cmdname: *const u16, argv: *const *const u16) -> intptr_t; - fn _wspawnve( - mode: i32, - cmdname: *const u16, - argv: *const *const u16, - envp: *const *const u16, - ) -> intptr_t; - } - #[cfg(target_env = "msvc")] #[pyfunction] fn spawnv( @@ -1031,12 +435,8 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - let result = unsafe { suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_spawn.as_ptr())) }; - if result == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::spawnv(mode, path.as_ptr(), argv_spawn.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1100,19 +500,8 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - let result = unsafe { - suppress_iph!(_wspawnve( - mode, - path.as_ptr(), - argv_spawn.as_ptr(), - envp.as_ptr() - )) - }; - if result == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::spawnve(mode, path.as_ptr(), argv_spawn.as_ptr(), envp.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1148,11 +537,7 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_nt::execv(path.as_ptr(), argv_execv.as_ptr()).map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1219,117 +604,36 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - if (unsafe { suppress_iph!(_wexecve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr())) } - == -1) - { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_nt::execve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[pyfunction] fn _getfinalpathname(path: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, GetFinalPathNameByHandleW, OPEN_EXISTING, - VOLUME_NAME_DOS, - }; - - let wide = path.to_wide_cstring(vm)?; - let handle = unsafe { - CreateFileW( - wide.as_ptr(), - 0, - 0, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - - let mut buffer: Vec = vec![0; Foundation::MAX_PATH as usize]; - let result = loop { - let ret = unsafe { - GetFinalPathNameByHandleW( - handle, - buffer.as_mut_ptr(), - buffer.len() as u32, - VOLUME_NAME_DOS, - ) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - let _ = unsafe { Foundation::CloseHandle(handle) }; - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - if (ret as usize) < buffer.len() { - let final_path = std::ffi::OsString::from_wide(&buffer[..ret as usize]); - break Ok(path.mode().process_path(final_path, vm)); - } - buffer.resize(ret as usize, 0); - }; - - unsafe { Foundation::CloseHandle(handle) }; - result + let _ = path.to_wide_cstring(vm)?; + let final_path = host_nt::getfinalpathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(final_path, vm)) } #[pyfunction] fn _getfullpathname(path: OsPath, vm: &VirtualMachine) -> PyResult { - let wpath = path.to_wide_cstring(vm)?; - let mut buffer = vec![0u16; Foundation::MAX_PATH as usize]; - let ret = unsafe { - FileSystem::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - if ret as usize > buffer.len() { - buffer.resize(ret as usize, 0); - let ret = unsafe { - FileSystem::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - } - let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode().process_path(buffer.to_os_string(), vm)) + let _ = path.to_wide_cstring(vm)?; + let buffer = host_nt::getfullpathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(buffer, vm)) } #[pyfunction] fn _getvolumepathname(path: OsPath, vm: &VirtualMachine) -> PyResult { let wide = path.to_wide_cstring(vm)?; - let buflen = core::cmp::max(wide.len(), Foundation::MAX_PATH as usize); + let buflen = core::cmp::max(wide.len(), host_nt::MAX_PATH_USIZE); if buflen > u32::MAX as usize { return Err(vm.new_overflow_error("path too long")); } - let mut buffer = vec![0u16; buflen]; - let ret = unsafe { - FileSystem::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode().process_path(buffer.to_os_string(), vm)) + let buffer = host_nt::getvolumepathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(buffer, vm)) } /// Implements _Py_skiproot logic for Windows paths @@ -1488,15 +792,7 @@ pub(crate) mod module { .chain(core::iter::once(0)) // null-terminated .collect(); - let mut end: *const u16 = core::ptr::null(); - let hr = unsafe { - windows_sys::Win32::UI::Shell::PathCchSkipRoot(backslashed.as_ptr(), &mut end) - }; - if hr >= 0 { - assert!(!end.is_null()); - let len: usize = unsafe { end.offset_from(backslashed.as_ptr()) } - .try_into() - .expect("len must be non-negative"); + if let Some(len) = host_nt::path_skip_root(backslashed.as_ptr()) { assert!( len < backslashed.len(), // backslashed is null-terminated "path: {:?} {} < {}", @@ -1684,43 +980,13 @@ pub(crate) mod module { #[pyfunction] fn _getdiskusage(path: OsPath, vm: &VirtualMachine) -> PyResult<(u64, u64)> { - use FileSystem::GetDiskFreeSpaceExW; - - let wpath = path.to_wide_cstring(vm)?; - let mut _free_to_me: u64 = 0; - let mut total: u64 = 0; - let mut free: u64 = 0; - let ret = - unsafe { GetDiskFreeSpaceExW(wpath.as_ptr(), &mut _free_to_me, &mut total, &mut free) }; - if ret != 0 { - return Ok((total, free)); - } - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(Foundation::ERROR_DIRECTORY as i32) - && let Some(parent) = path.as_ref().parent() - { - let parent = widestring::WideCString::from_os_str(parent).unwrap(); - - let ret = unsafe { - GetDiskFreeSpaceExW(parent.as_ptr(), &mut _free_to_me, &mut total, &mut free) - }; - - return if ret == 0 { - Err(err.to_pyexception(vm)) - } else { - Ok((total, free)) - }; - } - Err(err.to_pyexception(vm)) + let _ = path.to_wide_cstring(vm)?; + host_nt::getdiskusage(path.as_ref()).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult { - let mut flags = 0; - if unsafe { Foundation::GetHandleInformation(handle as _, &mut flags) } == 0 { - return Err(vm.new_last_os_error()); - } - Ok(flags & Foundation::HANDLE_FLAG_INHERIT != 0) + host_nt::get_handle_inheritable(handle).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] @@ -1732,139 +998,41 @@ pub(crate) mod module { #[pyfunction] fn getlogin(vm: &VirtualMachine) -> PyResult { - let mut buffer = [0u16; 257]; - let mut size = buffer.len() as u32; - - let success = unsafe { - windows_sys::Win32::System::WindowsProgramming::GetUserNameW( - buffer.as_mut_ptr(), - &mut size, - ) - }; - - if success != 0 { - // Convert the buffer (which is UTF-16) to a Rust String - let username = std::ffi::OsString::from_wide(&buffer[..(size - 1) as usize]); - Ok(username.to_str().unwrap().to_string()) - } else { - Err(vm.new_os_error(format!("Error code: {success}"))) - } + host_nt::getlogin().map_err(|_| vm.new_os_error("Error code: 0".to_owned())) } pub fn raw_set_handle_inheritable(handle: intptr_t, inheritable: bool) -> std::io::Result<()> { - let flags = if inheritable { - Foundation::HANDLE_FLAG_INHERIT - } else { - 0 - }; - let res = unsafe { - Foundation::SetHandleInformation(handle as _, Foundation::HANDLE_FLAG_INHERIT, flags) - }; - if res == 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } + host_nt::set_handle_inheritable(handle, inheritable) } #[pyfunction] fn listdrives(vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_MORE_DATA; - - let mut buffer = [0u16; 256]; - let len = - unsafe { FileSystem::GetLogicalDriveStringsW(buffer.len() as _, buffer.as_mut_ptr()) }; - if len == 0 { - return Err(vm.new_last_os_error()); - } - if len as usize >= buffer.len() { - return Err(std::io::Error::from_raw_os_error(ERROR_MORE_DATA as _).to_pyexception(vm)); - } - let drives: Vec<_> = buffer[..(len - 1) as usize] - .split(|&c| c == 0) - .map(|drive| vm.new_pyobj(String::from_utf16_lossy(drive))) + let drives: Vec<_> = host_nt::listdrives() + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|drive| vm.new_pyobj(drive.to_string_lossy().into_owned())) .collect(); Ok(vm.ctx.new_list(drives)) } #[pyfunction] fn listvolumes(vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES; - - let mut result = Vec::new(); - let mut buffer = [0u16; Foundation::MAX_PATH as usize + 1]; - - let find = unsafe { FileSystem::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as _) }; - if find == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - loop { - // Find the null terminator - let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); - let volume = String::from_utf16_lossy(&buffer[..len]); - result.push(vm.new_pyobj(volume)); - - let ret = unsafe { - FileSystem::FindNextVolumeW(find, buffer.as_mut_ptr(), buffer.len() as _) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - unsafe { FileSystem::FindVolumeClose(find) }; - if err.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) { - break; - } - return Err(err.to_pyexception(vm)); - } - } - + let result = host_nt::listvolumes() + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|volume| vm.new_pyobj(volume.to_string_lossy().into_owned())) + .collect(); Ok(vm.ctx.new_list(result)) } #[pyfunction] fn listmounts(volume: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_MORE_DATA; - - let wide = volume.to_wide_cstring(vm)?; - let mut buflen: u32 = Foundation::MAX_PATH + 1; - let mut buffer: Vec = vec![0; buflen as usize]; - - loop { - let success = unsafe { - FileSystem::GetVolumePathNamesForVolumeNameW( - wide.as_ptr(), - buffer.as_mut_ptr(), - buflen, - &mut buflen, - ) - }; - if success != 0 { - break; - } - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(ERROR_MORE_DATA as i32) { - buffer.resize(buflen as usize, 0); - continue; - } - return Err(err.to_pyexception(vm)); - } - - // Parse null-separated strings - let mut result = Vec::new(); - let mut start = 0; - for (i, &c) in buffer.iter().enumerate() { - if c == 0 { - if i > start { - let mount = String::from_utf16_lossy(&buffer[start..i]); - result.push(vm.new_pyobj(mount)); - } - start = i + 1; - if start < buffer.len() && buffer[start] == 0 { - break; // Double null = end - } - } - } - + let _ = volume.to_wide_cstring(vm)?; + let result = host_nt::listmounts(volume.as_ref()) + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|mount| vm.new_pyobj(mount.to_string_lossy().into_owned())) + .collect(); Ok(vm.ctx.new_list(result)) } @@ -1889,182 +1057,29 @@ pub(crate) mod module { #[pyfunction] fn mkdir(args: MkdirArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Foundation::LocalFree; - use windows_sys::Win32::Security::Authorization::{ - ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, - }; - use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; - let [] = args.dir_fd.0; let wide = args.path.to_wide_cstring(vm)?; - - // special case: mode 0o700 sets a protected ACL - let res = if args.mode == 0o700 { - let mut sec_attr = SECURITY_ATTRIBUTES { - nLength: core::mem::size_of::() as u32, - lpSecurityDescriptor: core::ptr::null_mut(), - bInheritHandle: 0, - }; - // Set a discretionary ACL (D) that is protected (P) and includes - // inheritable (OICI) entries that allow (A) full control (FA) to - // SYSTEM (SY), Administrators (BA), and the owner (OW). - let sddl: Vec = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0" - .encode_utf16() - .collect(); - let convert_result = unsafe { - ConvertStringSecurityDescriptorToSecurityDescriptorW( - sddl.as_ptr(), - SDDL_REVISION_1, - &mut sec_attr.lpSecurityDescriptor, - core::ptr::null_mut(), - ) - }; - if convert_result == 0 { - return Err(vm.new_last_os_error()); - } - let res = - unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), &sec_attr as *const _ as _) }; - unsafe { LocalFree(sec_attr.lpSecurityDescriptor) }; - res - } else { - unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), core::ptr::null_mut()) } - }; - - if res == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) - } - - unsafe extern "C" { - fn _umask(mask: i32) -> i32; - } - - /// Close fd and convert error to PyException (PEP 446 cleanup) - #[cold] - fn close_fd_and_raise(fd: i32, err: std::io::Error, vm: &VirtualMachine) -> PyBaseExceptionRef { - let _ = unsafe { crt_fd::Owned::from_raw(fd) }; - err.to_pyexception(vm) + host_nt::mkdir(&wide, args.mode).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn umask(mask: i32, vm: &VirtualMachine) -> PyResult { - let result = unsafe { _umask(mask) }; - if result < 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::umask(mask).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn pipe(vm: &VirtualMachine) -> PyResult<(i32, i32)> { - use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; - use windows_sys::Win32::System::Pipes::CreatePipe; - - let mut attr = SECURITY_ATTRIBUTES { - nLength: core::mem::size_of::() as u32, - lpSecurityDescriptor: core::ptr::null_mut(), - bInheritHandle: 0, - }; - - let (read_handle, write_handle) = unsafe { - let mut read = MaybeUninit::::uninit(); - let mut write = MaybeUninit::::uninit(); - let res = CreatePipe( - read.as_mut_ptr() as *mut _, - write.as_mut_ptr() as *mut _, - &mut attr as *mut _, - 0, - ); - if res == 0 { - return Err(vm.new_last_os_error()); - } - (read.assume_init(), write.assume_init()) - }; - - // Convert handles to file descriptors - // O_NOINHERIT = 0x80 (MSVC CRT) - const O_NOINHERIT: i32 = 0x80; - let read_fd = unsafe { libc::open_osfhandle(read_handle, O_NOINHERIT) }; - let write_fd = unsafe { libc::open_osfhandle(write_handle, libc::O_WRONLY | O_NOINHERIT) }; - - if read_fd == -1 || write_fd == -1 { - unsafe { - Foundation::CloseHandle(read_handle as _); - Foundation::CloseHandle(write_handle as _); - } - return Err(vm.new_last_os_error()); - } - - Ok((read_fd, write_fd)) + host_nt::pipe().map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn getppid() -> u32 { - use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION}; - - type NtQueryInformationProcessFn = unsafe extern "system" fn( - process_handle: isize, - process_information_class: u32, - process_information: *mut core::ffi::c_void, - process_information_length: u32, - return_length: *mut u32, - ) -> i32; - - let ntdll = unsafe { - windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(windows_sys::w!( - "ntdll.dll" - )) - }; - if ntdll.is_null() { - return 0; - } - - let func = unsafe { - windows_sys::Win32::System::LibraryLoader::GetProcAddress( - ntdll, - c"NtQueryInformationProcess".as_ptr() as *const u8, - ) - }; - let Some(func) = func else { - return 0; - }; - let nt_query: NtQueryInformationProcessFn = unsafe { core::mem::transmute(func) }; - - let mut info: PROCESS_BASIC_INFORMATION = unsafe { core::mem::zeroed() }; - - let status = unsafe { - nt_query( - GetCurrentProcess() as isize, - 0, // ProcessBasicInformation - &mut info as *mut _ as *mut core::ffi::c_void, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - ) - }; - - if status >= 0 - && info.InheritedFromUniqueProcessId != 0 - && info.InheritedFromUniqueProcessId < u32::MAX as usize - { - info.InheritedFromUniqueProcessId as u32 - } else { - 0 - } + host_nt::getppid() } #[pyfunction] fn dup(fd: i32, vm: &VirtualMachine) -> PyResult { - let fd2 = unsafe { suppress_iph!(libc::dup(fd)) }; - if fd2 < 0 { - return Err(vm.new_last_errno_error()); - } - let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd2) }; - let handle = crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(fd2, e, vm))?; - raw_set_handle_inheritable(handle.as_raw_handle() as _, false) - .map_err(|e| close_fd_and_raise(fd2, e, vm))?; - Ok(fd2) + host_nt::dup(fd).map_err(|e| e.to_pyexception(vm)) } #[derive(FromArgs)] @@ -2079,152 +1094,26 @@ pub(crate) mod module { #[pyfunction] fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult { - let result = unsafe { suppress_iph!(libc::dup2(args.fd, args.fd2)) }; - if result < 0 { - return Err(vm.new_last_errno_error()); - } - if !args.inheritable { - let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(args.fd2) }; - let handle = - crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; - raw_set_handle_inheritable(handle.as_raw_handle() as _, false) - .map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; - } - Ok(args.fd2) + host_nt::dup2(args.fd, args.fd2, args.inheritable).map_err(|e| e.to_pyexception(vm)) } /// Windows-specific readlink that preserves \\?\ prefix for junctions /// returns the substitute name from reparse data which includes the prefix #[pyfunction] fn readlink(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::CloseHandle; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, - FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, - }; - use windows_sys::Win32::System::IO::DeviceIoControl; - use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; - let mode = path.mode(); - let wide_path = path.as_ref().to_wide_with_nul(); - - // Open the file/directory with reparse point flag - let handle = unsafe { - CreateFileW( - wide_path.as_ptr(), - 0, // No access needed, just reading reparse data - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - core::ptr::null_mut(), - ) - }; - - if handle == INVALID_HANDLE_VALUE { - return Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path, - vm, - )); - } - - // Buffer for reparse data - MAXIMUM_REPARSE_DATA_BUFFER_SIZE is 16384 - const BUFFER_SIZE: usize = 16384; - let mut buffer = vec![0u8; BUFFER_SIZE]; - let mut bytes_returned: u32 = 0; - - let result = unsafe { - DeviceIoControl( - handle, - FSCTL_GET_REPARSE_POINT, - core::ptr::null(), - 0, - buffer.as_mut_ptr() as *mut _, - BUFFER_SIZE as u32, - &mut bytes_returned, - core::ptr::null_mut(), - ) - }; - - unsafe { CloseHandle(handle) }; - - if result == 0 { - return Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path, - vm, - )); - } - - // Parse the reparse data buffer - // REPARSE_DATA_BUFFER structure: - // DWORD ReparseTag - // WORD ReparseDataLength - // WORD Reserved - // For symlinks/junctions (IO_REPARSE_TAG_SYMLINK/MOUNT_POINT): - // WORD SubstituteNameOffset - // WORD SubstituteNameLength - // WORD PrintNameOffset - // WORD PrintNameLength - // (For symlinks only: DWORD Flags) - // PathBuffer... - - let reparse_tag = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); - - // Check if it's a symlink or mount point (junction) - use windows_sys::Win32::System::SystemServices::{ - IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, - }; - - let (substitute_offset, substitute_length, path_buffer_start) = - if reparse_tag == IO_REPARSE_TAG_SYMLINK { - // Symlink has Flags field (4 bytes) before PathBuffer - let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; - let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; - // PathBuffer starts at offset 20 (after Flags at offset 16) - (sub_offset, sub_length, 20usize) - } else if reparse_tag == IO_REPARSE_TAG_MOUNT_POINT { - // Mount point (junction) has no Flags field - let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; - let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; - // PathBuffer starts at offset 16 - (sub_offset, sub_length, 16usize) - } else { - return Err(vm.new_value_error("not a symbolic link")); - }; - - // Extract the substitute name - let path_start = path_buffer_start + substitute_offset; - let path_end = path_start + substitute_length; - - if path_end > buffer.len() { - return Err(vm.new_os_error("Invalid reparse data".to_owned())); - } - - // Convert from UTF-16LE - let path_slice = &buffer[path_start..path_end]; - let wide_chars: Vec = path_slice - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect(); - - let mut wide_chars = wide_chars; - // For mount points (junctions), the substitute name typically starts with \??\ - // Convert this to \\?\ - if wide_chars.len() > 4 - && wide_chars[0] == b'\\' as u16 - && wide_chars[1] == b'?' as u16 - && wide_chars[2] == b'?' as u16 - && wide_chars[3] == b'\\' as u16 - { - wide_chars[1] = b'\\' as u16; + match host_nt::readlink(path.as_ref()) { + Ok(result_path) => Ok(mode.process_path(std::path::PathBuf::from(result_path), vm)), + Err(host_nt::ReadlinkError::Io(err)) => { + Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)) + } + Err(host_nt::ReadlinkError::NotSymbolicLink) => { + Err(vm.new_value_error("not a symbolic link")) + } + Err(host_nt::ReadlinkError::InvalidReparseData) => { + Err(vm.new_os_error("Invalid reparse data".to_owned())) + } } - - let result_path = std::ffi::OsString::from_wide(&wide_chars); - - Ok(mode.process_path(std::path::PathBuf::from(result_path), vm)) } pub(crate) fn support_funcs() -> Vec { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index ea9918b91c5..bcca77528ba 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1,4 +1,5 @@ // spell-checker:disable +#![allow(unreachable_pub)] use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, @@ -178,7 +179,10 @@ pub(super) mod _os { use core::time::Duration; use crossbeam_utils::atomic::AtomicCell; use rustpython_common::wtf8::Wtf8Buf; - use rustpython_host_env::suppress_iph; + #[cfg(windows)] + use rustpython_host_env::nt as host_nt; + #[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))] + use rustpython_host_env::posix as host_posix; use std::{fs, io, path::PathBuf, time::SystemTime}; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -347,9 +351,9 @@ pub(super) mod _os { let c_path = path.clone().into_cstring(vm)?; #[cfg(not(target_os = "redox"))] if let Some(fd) = dir_fd.raw_opt() { - let res = unsafe { libc::mkdirat(fd, c_path.as_ptr(), mode as _) }; - return if res < 0 { - let err = crate::host_env::os::errno_io_error(); + return if let Err(err) = + crate::host_env::posix::make_dir_at(fd, c_path.as_c_str(), mode as u32) + { Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -357,9 +361,7 @@ pub(super) mod _os { } #[cfg(target_os = "redox")] let [] = dir_fd.0; - let res = unsafe { libc::mkdir(c_path.as_ptr(), mode as _) }; - if res < 0 { - let err = crate::host_env::os::errno_io_error(); + if let Err(err) = crate::host_env::posix::make_dir(c_path.as_c_str(), mode as u32) { return Err(OSErrorBuilder::with_filename(&err, path, vm)); } Ok(()) @@ -381,9 +383,7 @@ pub(super) mod _os { #[cfg(not(target_os = "redox"))] if let Some(fd) = dir_fd.raw_opt() { let c_path = path.clone().into_cstring(vm)?; - let res = unsafe { libc::unlinkat(fd, c_path.as_ptr(), libc::AT_REMOVEDIR) }; - return if res < 0 { - let err = crate::host_env::os::errno_io_error(); + return if let Err(err) = crate::host_env::posix::remove_dir_at(fd, c_path.as_c_str()) { Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -439,35 +439,17 @@ pub(super) mod _os { } #[cfg(all(unix, not(target_os = "redox")))] { - use rustpython_host_env::os::ffi::OsStrExt; - use std::os::unix::io::IntoRawFd; - let new_fd = nix::unistd::dup(fno).map_err(|e| e.into_pyexception(vm))?; - let raw_fd = new_fd.into_raw_fd(); - let dir = OwnedDir::from_fd(raw_fd).map_err(|e| { - unsafe { libc::close(raw_fd) }; - e.into_pyexception(vm) - })?; - // OwnedDir::drop calls rewinddir (reset to start) then closedir. + let mut dir = host_posix::FdDirStream::from_fd(fno.into()) + .map_err(|e| e.into_pyexception(vm))?; let mut list = Vec::new(); - loop { - nix::errno::Errno::clear(); - let entry = unsafe { libc::readdir(dir.as_ptr()) }; - if entry.is_null() { - let err = nix::errno::Errno::last(); - if err != nix::errno::Errno::UnknownErrno { - return Err(io::Error::from(err).into_pyexception(vm)); - } - break; - } - let fname = unsafe { core::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) } - .to_bytes(); - match fname { - b"." | b".." => continue, - _ => list.push( - OutputMode::String - .process_path(std::ffi::OsStr::from_bytes(fname), vm), + while let Some(entry) = dir.next_entry().map_err(|e| e.into_pyexception(vm))? { + list.push( + OutputMode::String.process_path( + rustpython_host_env::os::bytes_as_os_str(&entry.name) + .expect("unix dir entry names are arbitrary bytes"), + vm, ), - } + ); } list } @@ -520,7 +502,7 @@ pub(super) mod _os { check_env_var_len(wide.len(), vm)?; // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ - let result = unsafe { suppress_iph!(_wputenv(wide.as_ptr())) }; + let result = unsafe { rustpython_host_env::suppress_iph!(_wputenv(wide.as_ptr())) }; if result != 0 { return Err(vm.new_last_errno_error()); } @@ -567,7 +549,7 @@ pub(super) mod _os { check_env_var_len(wide.len(), vm)?; // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ - let result = unsafe { suppress_iph!(_wputenv(wide.as_ptr())) }; + let result = unsafe { rustpython_host_env::suppress_iph!(_wputenv(wide.as_ptr())) }; if result != 0 { return Err(vm.new_last_errno_error()); } @@ -856,7 +838,7 @@ pub(super) mod _os { #[cfg(windows)] #[pymethod] fn is_junction(&self, _vm: &VirtualMachine) -> bool { - junction::exists(self.pathval.clone()).unwrap_or(false) + host_nt::test_file_type_by_name(&self.pathval, host_nt::TestType::Junction) } #[pymethod] @@ -991,7 +973,7 @@ pub(super) mod _os { let lstat = { let cell = OnceCell::new(); if let Ok(stat_struct) = - crate::windows::win32_xstat(pathval.as_os_str(), false) + host_nt::win32_xstat(pathval.as_os_str(), false) { let stat_obj = StatResultData::from_stat(&stat_struct, vm).to_pyobject(vm); @@ -1031,54 +1013,12 @@ pub(super) mod _os { } } - /// Wrapper around a raw `libc::DIR*` for fd-based scandir. - #[cfg(all(unix, not(target_os = "redox")))] - struct OwnedDir(core::ptr::NonNull); - - #[cfg(all(unix, not(target_os = "redox")))] - impl OwnedDir { - fn from_fd(fd: crt_fd::Raw) -> io::Result { - let ptr = unsafe { libc::fdopendir(fd) }; - core::ptr::NonNull::new(ptr) - .map(OwnedDir) - .ok_or_else(io::Error::last_os_error) - } - - fn as_ptr(&self) -> *mut libc::DIR { - self.0.as_ptr() - } - } - - #[cfg(all(unix, not(target_os = "redox")))] - impl Drop for OwnedDir { - fn drop(&mut self) { - unsafe { - libc::rewinddir(self.0.as_ptr()); - libc::closedir(self.0.as_ptr()); - } - } - } - - #[cfg(all(unix, not(target_os = "redox")))] - impl core::fmt::Debug for OwnedDir { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("OwnedDir").field(&self.0).finish() - } - } - - // Safety: OwnedDir wraps a *mut libc::DIR. All access is synchronized - // through the PyMutex in ScandirIteratorFd. - #[cfg(all(unix, not(target_os = "redox")))] - unsafe impl Send for OwnedDir {} - #[cfg(all(unix, not(target_os = "redox")))] - unsafe impl Sync for OwnedDir {} - #[cfg(all(unix, not(target_os = "redox")))] #[pyattr] #[pyclass(name = "ScandirIter")] #[derive(Debug, PyPayload)] struct ScandirIteratorFd { - dir: crate::common::lock::PyMutex>, + dir: crate::common::lock::PyMutex>, /// The original fd passed to scandir(), stored in DirEntry for fstatat orig_fd: crt_fd::Raw, } @@ -1129,59 +1069,37 @@ pub(super) mod _os { #[cfg(all(unix, not(target_os = "redox")))] impl IterNext for ScandirIteratorFd { fn next(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - use rustpython_host_env::os::ffi::OsStrExt; let mut guard = zelf.dir.lock(); let dir = match guard.as_mut() { None => return Ok(PyIterReturn::StopIteration(None)), Some(dir) => dir, }; - loop { - nix::errno::Errno::clear(); - let entry = unsafe { - let ptr = libc::readdir(dir.as_ptr()); - if ptr.is_null() { - let err = nix::errno::Errno::last(); - if err != nix::errno::Errno::UnknownErrno { - return Err(io::Error::from(err).into_pyexception(vm)); - } - drop(guard.take()); - return Ok(PyIterReturn::StopIteration(None)); - } - &*ptr - }; - let fname = unsafe { core::ffi::CStr::from_ptr(entry.d_name.as_ptr()) }.to_bytes(); - if fname == b"." || fname == b".." { - continue; + let Some(entry) = dir.next_entry().map_err(|e| e.into_pyexception(vm))? else { + drop(guard.take()); + return Ok(PyIterReturn::StopIteration(None)); + }; + let file_name = std::ffi::OsString::from( + rustpython_host_env::os::bytes_as_os_str(&entry.name) + .expect("unix dir entry names are arbitrary bytes"), + ); + let pathval = PathBuf::from(&file_name); + Ok(PyIterReturn::Return( + DirEntry { + file_name, + pathval, + file_type: Err(io::Error::other( + "file_type unavailable for fd-based scandir", + )), + d_type: entry.d_type, + dir_fd: Some(zelf.orig_fd), + mode: OutputMode::String, + lstat: OnceCell::new(), + stat: OnceCell::new(), + ino: AtomicCell::new(entry.ino as _), } - let file_name = std::ffi::OsString::from(std::ffi::OsStr::from_bytes(fname)); - let pathval = PathBuf::from(&file_name); - #[cfg(target_os = "freebsd")] - let ino = entry.d_fileno; - #[cfg(not(target_os = "freebsd"))] - let ino = entry.d_ino; - let d_type = entry.d_type; - return Ok(PyIterReturn::Return( - DirEntry { - file_name, - pathval, - file_type: Err(io::Error::other( - "file_type unavailable for fd-based scandir", - )), - d_type: if d_type == libc::DT_UNKNOWN { - None - } else { - Some(d_type) - }, - dir_fd: Some(zelf.orig_fd), - mode: OutputMode::String, - lstat: OnceCell::new(), - stat: OnceCell::new(), - ino: AtomicCell::new(ino as _), - } - .into_ref(&vm.ctx) - .into(), - )); - } + .into_ref(&vm.ctx) + .into(), + )) } } @@ -1209,15 +1127,8 @@ pub(super) mod _os { } #[cfg(all(unix, not(target_os = "redox")))] { - use std::os::unix::io::IntoRawFd; - // closedir() closes the fd, so duplicate it first - let new_fd = nix::unistd::dup(fno).map_err(|e| e.into_pyexception(vm))?; - let raw_fd = new_fd.into_raw_fd(); - let dir = OwnedDir::from_fd(raw_fd).map_err(|e| { - // fdopendir failed, close the dup'd fd - unsafe { libc::close(raw_fd) }; - e.into_pyexception(vm) - })?; + let dir = host_posix::FdDirStream::from_fd(fno.into()) + .map_err(|e| e.into_pyexception(vm))?; Ok(ScandirIteratorFd { dir: crate::common::lock::PyMutex::new(Some(dir)), orig_fd: fno.as_raw(), @@ -1401,10 +1312,9 @@ pub(super) mod _os { dir_fd: DirFd<'_, { STAT_DIR_FD as usize }>, follow_symlinks: FollowSymlinks, ) -> io::Result> { - // TODO: replicate CPython's win32_xstat let [] = dir_fd.0; match file { - OsPathOrFd::Path(path) => crate::windows::win32_xstat(&path.path, follow_symlinks.0), + OsPathOrFd::Path(path) => host_nt::win32_xstat(&path.path, follow_symlinks.0), OsPathOrFd::Fd(fd) => crate::host_env::fileutils::fstat(fd), } .map(Some) @@ -1416,42 +1326,14 @@ pub(super) mod _os { dir_fd: DirFd<'_, { STAT_DIR_FD as usize }>, follow_symlinks: FollowSymlinks, ) -> io::Result> { - let mut stat = core::mem::MaybeUninit::uninit(); - let ret = match file { - OsPathOrFd::Path(path) => { - use rustpython_host_env::os::ffi::OsStrExt; - let path = path.as_ref().as_os_str().as_bytes(); - let path = match alloc::ffi::CString::new(path) { - Ok(x) => x, - Err(_) => return Ok(None), - }; - - #[cfg(not(target_os = "redox"))] - let fstatat_ret = dir_fd.raw_opt().map(|dir_fd| { - let flags = if follow_symlinks.0 { - 0 - } else { - libc::AT_SYMLINK_NOFOLLOW - }; - unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) } - }); - #[cfg(target_os = "redox")] - let ([], fstatat_ret) = (dir_fd.0, None); - - fstatat_ret.unwrap_or_else(|| { - if follow_symlinks.0 { - unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) } - } else { - unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) } - } - }) - } - OsPathOrFd::Fd(fd) => unsafe { libc::fstat(fd.as_raw(), stat.as_mut_ptr()) }, - }; - if ret < 0 { - return Err(io::Error::last_os_error()); + match file { + OsPathOrFd::Path(path) => host_posix::stat_path( + path.as_ref().as_os_str(), + dir_fd.raw_opt(), + follow_symlinks.0, + ), + OsPathOrFd::Fd(fd) => host_posix::stat_fd(fd).map(Some), } - Ok(Some(unsafe { stat.assume_init() })) } #[pyfunction] @@ -1494,40 +1376,7 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { crate::host_env::os::set_current_dir(&path.path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; - - #[cfg(windows)] - { - // win32_wchdir() - - // On Windows, set the per-drive CWD environment variable (=X:) - // This is required for GetFullPathNameW to work correctly with drive-relative paths - - use std::os::windows::ffi::OsStrExt; - use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; - - if let Ok(cwd) = crate::host_env::os::current_dir() { - let cwd_str = cwd.as_os_str(); - let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); - - // Check for UNC-like paths (\\server\share or //server/share) - // wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0 - let is_unc_like_path = cwd_wide.len() >= 2 - && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) - || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); - - if !is_unc_like_path { - // Create env var name "=X:" where X is the drive letter - let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; - cwd_wide.push(0); // null-terminate the path - unsafe { - SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); - } - } - } - } - - Ok(()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction] @@ -1547,7 +1396,7 @@ pub(super) mod _os { .argument("dst") .try_path(dst, vm)?; - fs::rename(&src.path, &dst.path).map_err(|err| { + crate::host_env::os::rename(&src.path, &dst.path).map_err(|err| { let builder = err.to_os_error_builder(vm); let builder = builder.filename(src.filename(vm)); let builder = builder.filename2(dst.filename(vm)); @@ -1570,7 +1419,7 @@ pub(super) mod _os { #[pyfunction] fn cpu_count(vm: &VirtualMachine) -> PyObjectRef { - let cpu_count = num_cpus::get(); + let cpu_count = crate::host_env::os::cpu_count(); vm.ctx.new_int(cpu_count).into() } @@ -1598,8 +1447,8 @@ pub(super) mod _os { } #[pyfunction] - pub(crate) fn isatty(fd: i32) -> bool { - unsafe { suppress_iph!(libc::isatty(fd)) != 0 } + pub fn isatty(fd: i32) -> bool { + crate::host_env::os::isatty(fd) } #[pyfunction] @@ -1609,32 +1458,7 @@ pub(super) mod _os { how: i32, vm: &VirtualMachine, ) -> PyResult { - #[cfg(not(windows))] - let res = unsafe { suppress_iph!(libc::lseek(fd.as_raw(), position, how)) }; - #[cfg(windows)] - let res = unsafe { - use std::os::windows::io::AsRawHandle; - use windows_sys::Win32::Storage::FileSystem; - let handle = crt_fd::as_handle(fd).map_err(|e| e.into_pyexception(vm))?; - let mut distance_to_move: [i32; 2] = core::mem::transmute(position); - let ret = FileSystem::SetFilePointer( - handle.as_raw_handle(), - distance_to_move[0], - &mut distance_to_move[1], - how as _, - ); - if ret == FileSystem::INVALID_SET_FILE_POINTER { - -1 - } else { - distance_to_move[0] = ret as _; - core::mem::transmute::<[i32; 2], i64>(distance_to_move) - } - }; - if res < 0 { - Err(vm.new_last_os_error()) - } else { - Ok(res) - } + crate::host_env::os::seek_fd(fd, position, how).map_err(|e| e.into_pyexception(vm)) } #[derive(FromArgs)] @@ -1664,20 +1488,9 @@ pub(super) mod _os { .map_err(|_| vm.new_value_error("embedded null byte"))?; let follow = follow_symlinks.into_option().unwrap_or(true); - let flags = if follow { libc::AT_SYMLINK_FOLLOW } else { 0 }; - - let ret = unsafe { - libc::linkat( - libc::AT_FDCWD, - src_cstr.as_ptr(), - libc::AT_FDCWD, - dst_cstr.as_ptr(), - flags, - ) - }; - - if ret != 0 { - let err = std::io::Error::last_os_error(); + if let Err(err) = + crate::host_env::posix::link_paths(src_cstr.as_c_str(), dst_cstr.as_c_str(), follow) + { let builder = err.to_os_error_builder(vm); let builder = builder.filename(src.filename(vm)); let builder = builder.filename2(dst.filename(vm)); @@ -1714,7 +1527,7 @@ pub(super) mod _os { #[pyfunction] fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult { let cstr = command.to_cstring(vm)?; - let x = unsafe { libc::system(cstr.as_ptr()) }; + let x = crate::host_env::os::system(cstr.as_c_str()); Ok(x) } @@ -1798,31 +1611,14 @@ pub(super) mod _os { { let path_for_err = path.clone(); let path = path.into_cstring(vm)?; - - let ts = |d: Duration| libc::timespec { - tv_sec: d.as_secs() as _, - tv_nsec: d.subsec_nanos() as _, - }; - let times = [ts(acc), ts(modif)]; - - let ret = unsafe { - libc::utimensat( - dir_fd.get().as_raw(), - path.as_ptr(), - times.as_ptr(), - if _follow_symlinks.0 { - 0 - } else { - libc::AT_SYMLINK_NOFOLLOW - }, - ) - }; - if ret < 0 { - Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path_for_err, - vm, - )) + if let Err(err) = crate::host_env::posix::set_file_times_at( + dir_fd.get().as_raw(), + path.as_c_str(), + acc, + modif, + _follow_symlinks.0, + ) { + Err(OSErrorBuilder::with_filename(&err, path_for_err, vm)) } else { Ok(()) } @@ -1830,21 +1626,12 @@ pub(super) mod _os { #[cfg(target_os = "redox")] { let [] = dir_fd.0; - - let tv = |d: Duration| libc::timeval { - tv_sec: d.as_secs() as _, - tv_usec: d.as_micros() as _, - }; - nix::sys::stat::utimes(path.as_ref(), &tv(acc).into(), &tv(modif).into()) + rustpython_host_env::posix::utimes(path.as_ref(), acc, modif) .map_err(|err| err.into_pyexception(vm)) } } #[cfg(windows)] { - use std::os::windows::prelude::*; - type DWORD = u32; - use windows_sys::Win32::{Foundation::FILETIME, Storage::FileSystem}; - let [] = dir_fd.0; if !_follow_symlinks.0 { @@ -1853,37 +1640,8 @@ pub(super) mod _os { )); } - let ft = |d: Duration| { - let intervals = ((d.as_secs() as i64 + 11644473600) * 10_000_000) - + (d.subsec_nanos() as i64 / 100); - FILETIME { - dwLowDateTime: intervals as DWORD, - dwHighDateTime: (intervals >> 32) as DWORD, - } - }; - - let acc = ft(acc); - let modif = ft(modif); - - let f = crate::host_env::fs::open_write_with_custom_flags( - &path, - windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, - ) - .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; - - let ret = unsafe { - FileSystem::SetFileTime(f.as_raw_handle() as _, core::ptr::null(), &acc, &modif) - }; - - if ret == 0 { - Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path, - vm, - )) - } else { - Ok(()) - } + crate::host_env::os::set_file_times(&path, acc, modif) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } } @@ -1916,32 +1674,12 @@ pub(super) mod _os { fn times(vm: &VirtualMachine) -> PyResult { #[cfg(windows)] { - use core::mem::MaybeUninit; - use windows_sys::Win32::{Foundation::FILETIME, System::Threading}; - - let mut _create = MaybeUninit::::uninit(); - let mut _exit = MaybeUninit::::uninit(); - let mut kernel = MaybeUninit::::uninit(); - let mut user = MaybeUninit::::uninit(); - - unsafe { - let h_proc = Threading::GetCurrentProcess(); - Threading::GetProcessTimes( - h_proc, - _create.as_mut_ptr(), - _exit.as_mut_ptr(), - kernel.as_mut_ptr(), - user.as_mut_ptr(), - ); - } - - let kernel = unsafe { kernel.assume_init() }; - let user = unsafe { user.assume_init() }; + let times = crate::host_env::time::get_process_times_100ns() + .ok_or_else(|| vm.new_last_os_error())?; let times_result = TimesResultData { - user: user.dwHighDateTime as f64 * 429.4967296 + user.dwLowDateTime as f64 * 1e-7, - system: kernel.dwHighDateTime as f64 * 429.4967296 - + kernel.dwLowDateTime as f64 * 1e-7, + user: times.user as f64 * 1e-7, + system: times.system as f64 * 1e-7, children_user: 0.0, children_system: 0.0, elapsed: 0.0, @@ -1951,27 +1689,15 @@ pub(super) mod _os { } #[cfg(unix)] { - let mut t = libc::tms { - tms_utime: 0, - tms_stime: 0, - tms_cutime: 0, - tms_cstime: 0, - }; - - let tick_for_second = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as f64; - let c = unsafe { libc::times(&mut t as *mut _) }; - - // XXX: The signedness of `clock_t` varies from platform to platform. - if c == (-1i8) as libc::clock_t { - return Err(vm.new_os_error("Fail to get times".to_string())); - } + let times = crate::host_env::time::process_times() + .map_err(|_| vm.new_os_error("Fail to get times".to_string()))?; let times_result = TimesResultData { - user: t.tms_utime as f64 / tick_for_second, - system: t.tms_stime as f64 / tick_for_second, - children_user: t.tms_cutime as f64 / tick_for_second, - children_system: t.tms_cstime as f64 / tick_for_second, - elapsed: c as f64 / tick_for_second, + user: times.user, + system: times.system, + children_user: times.children_user, + children_system: times.children_system, + elapsed: times.elapsed, }; Ok(times_result.to_pyobject(vm)) @@ -1995,45 +1721,25 @@ pub(super) mod _os { #[cfg(target_os = "linux")] #[pyfunction] - fn copy_file_range(args: CopyFileRangeArgs<'_>, vm: &VirtualMachine) -> PyResult { - #[allow(clippy::unnecessary_option_map_or_else)] - let p_offset_src = args.offset_src.as_ref().map_or_else(core::ptr::null, |x| x); - #[allow(clippy::unnecessary_option_map_or_else)] - let p_offset_dst = args.offset_dst.as_ref().map_or_else(core::ptr::null, |x| x); + fn copy_file_range(mut args: CopyFileRangeArgs<'_>, vm: &VirtualMachine) -> PyResult { let count: usize = args .count .try_into() .map_err(|_| vm.new_value_error("count should >= 0"))?; - // The flags argument is provided to allow - // for future extensions and currently must be to 0. - let flags = 0u32; - - // Safety: p_offset_src and p_offset_dst is a unique pointer for offset_src and offset_dst respectively, - // and will only be freed after this function ends. - // - // Why not use `libc::copy_file_range`: On `musl-libc`, `libc::copy_file_range` is not provided. Therefore - // we use syscalls directly instead. - let ret = unsafe { - libc::syscall( - libc::SYS_copy_file_range, - args.src, - p_offset_src as *mut i64, - args.dst, - p_offset_dst as *mut i64, - count, - flags, - ) - }; - - usize::try_from(ret).map_err(|_| vm.new_last_errno_error()) + crate::host_env::os::copy_file_range( + args.src, + args.offset_src.as_mut(), + args.dst, + args.offset_dst.as_mut(), + count, + ) + .map_err(|_| vm.new_last_errno_error()) } #[pyfunction] fn strerror(e: i32) -> String { - unsafe { core::ffi::CStr::from_ptr(libc::strerror(e)) } - .to_string_lossy() - .into_owned() + crate::host_env::time::strerror(e) } #[pyfunction] @@ -2072,16 +1778,8 @@ pub(super) mod _os { #[cfg(all(unix, not(any(target_os = "redox", target_os = "android"))))] #[pyfunction] fn getloadavg(vm: &VirtualMachine) -> PyResult<(f64, f64, f64)> { - let mut loadavg = [0f64; 3]; - - // Safety: loadavg is on stack and only write by `getloadavg` and are freed - // after this function ends. - unsafe { - if libc::getloadavg(&mut loadavg[0] as *mut f64, 3) != 3 { - return Err(vm.new_os_error("Load averages are unobtainable".to_string())); - } - } - + let loadavg = crate::host_env::time::getloadavg() + .map_err(|_| vm.new_os_error("Load averages are unobtainable".to_string()))?; Ok((loadavg[0], loadavg[1], loadavg[2])) } @@ -2091,16 +1789,12 @@ pub(super) mod _os { let status = u32::try_from(status) .map_err(|_| vm.new_value_error(format!("invalid WEXITSTATUS: {status}")))?; - let status = status as libc::c_int; - if libc::WIFEXITED(status) { - return Ok(libc::WEXITSTATUS(status)); - } - - if libc::WIFSIGNALED(status) { - return Ok(-libc::WTERMSIG(status)); + if let Some(exitcode) = crate::host_env::time::waitstatus_to_exitcode(status as libc::c_int) + { + return Ok(exitcode); } - Err(vm.new_value_error(format!("Invalid wait status: {status}"))) + Err(vm.new_value_error(format!("Invalid wait status: {}", status as libc::c_int))) } #[cfg(windows)] @@ -2119,31 +1813,7 @@ pub(super) mod _os { return None; } - cfg_select! { - any(target_os = "android", target_os = "redox") => { - Some("UTF-8".to_owned()) - } - windows => { - use windows_sys::Win32::System::Console; - let cp = match fd { - 0 => unsafe { Console::GetConsoleCP() }, - 1 | 2 => unsafe { Console::GetConsoleOutputCP() }, - _ => 0, - }; - - Some(format!("cp{cp}")) - } - _ => { - Some(unsafe { - let encoding = libc::nl_langinfo(libc::CODESET); - if encoding.is_null() || encoding.read() == b'\0' as libc::c_char { - "UTF-8".to_owned() - } else { - core::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() - } - }) - } - } + rustpython_host_env::os::device_encoding(fd) } #[pystruct_sequence_data] @@ -2214,27 +1884,7 @@ pub(super) mod _os { #[cfg(all(unix, not(target_os = "redox")))] impl StatvfsResultData { - fn from_statvfs(st: libc::statvfs) -> Self { - // f_fsid is a struct on some platforms (e.g., Linux fsid_t) and a scalar on others. - // We extract raw bytes and interpret as a native-endian integer. - // Note: The value may differ across architectures due to endianness. - let f_fsid = { - let ptr = core::ptr::addr_of!(st.f_fsid) as *const u8; - let size = core::mem::size_of_val(&st.f_fsid); - if size >= 8 { - let bytes = unsafe { core::slice::from_raw_parts(ptr, 8) }; - u64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - bytes[7], - ]) as libc::c_ulong - } else if size >= 4 { - let bytes = unsafe { core::slice::from_raw_parts(ptr, 4) }; - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as libc::c_ulong - } else { - 0 - } - }; - + fn from_statvfs(st: crate::host_env::posix::StatVfsInfo) -> Self { Self { f_bsize: st.f_bsize, f_frsize: st.f_frsize, @@ -2246,7 +1896,7 @@ pub(super) mod _os { f_favail: st.f_favail, f_flag: st.f_flag, f_namemax: st.f_namemax, - f_fsid, + f_fsid: st.f_fsid, } } } @@ -2256,22 +1906,17 @@ pub(super) mod _os { #[pyfunction] #[pyfunction(name = "fstatvfs")] fn statvfs(path: OsPathOrFd<'_>, vm: &VirtualMachine) -> PyResult { - let mut st: libc::statvfs = unsafe { core::mem::zeroed() }; - let ret = match &path { + let st = match &path { OsPathOrFd::Path(p) => { let cpath = p.clone().into_cstring(vm)?; - unsafe { libc::statvfs(cpath.as_ptr(), &mut st) } + crate::host_env::posix::statvfs_path(cpath.as_c_str()) } - OsPathOrFd::Fd(fd) => unsafe { libc::fstatvfs(fd.as_raw(), &mut st) }, + OsPathOrFd::Fd(fd) => crate::host_env::posix::statvfs_fd(fd.as_raw()), }; - if ret != 0 { - return Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path, - vm, - )); + if let Err(err) = st { + return Err(OSErrorBuilder::with_filename(&err, path, vm)); } - Ok(StatvfsResultData::from_statvfs(st).to_pyobject(vm)) + Ok(StatvfsResultData::from_statvfs(st.unwrap()).to_pyobject(vm)) } pub(super) fn support_funcs() -> Vec { @@ -2309,7 +1954,7 @@ pub(super) mod _os { supports } } -pub(crate) use _os::{ftruncate, isatty, lseek}; +pub(crate) use _os::ftruncate; pub(crate) struct SupportFunc { name: &'static str, diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index ca3a9a2dc48..9b27dad778a 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -35,17 +35,11 @@ pub mod module { ))] use crate::{builtins::PyUtf8StrRef, utils::ToCString}; use alloc::ffi::CString; - use bitflags::bitflags; use core::ffi::CStr; - use nix::{ - errno::Errno, - fcntl, - unistd::{self, Gid, Pid, Uid}, - }; use rustpython_host_env::os::ffi::OsStringExt; use std::{ fs, io, - os::fd::{AsFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, + os::fd::{BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, }; use strum::IntoEnumIterator; use strum_macros::{EnumIter, EnumString}; @@ -374,127 +368,27 @@ pub mod module { } } - // Flags for os_access - bitflags! { - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub struct AccessFlags: u8 { - const F_OK = _os::F_OK; - const R_OK = _os::R_OK; - const W_OK = _os::W_OK; - const X_OK = _os::X_OK; - } - } - - struct Permissions { - is_readable: bool, - is_writable: bool, - is_executable: bool, - } - - const fn get_permissions(mode: u32) -> Permissions { - Permissions { - is_readable: mode & 4 != 0, - is_writable: mode & 2 != 0, - is_executable: mode & 1 != 0, - } - } - - fn get_right_permission( - mode: u32, - file_owner: Uid, - file_group: Gid, - ) -> nix::Result { - let owner_mode = (mode & 0o700) >> 6; - let owner_permissions = get_permissions(owner_mode); - - let group_mode = (mode & 0o070) >> 3; - let group_permissions = get_permissions(group_mode); - - let others_mode = mode & 0o007; - let others_permissions = get_permissions(others_mode); - - let user_id = nix::unistd::getuid(); - let groups_ids = getgroups_impl()?; - - if file_owner == user_id { - Ok(owner_permissions) - } else if groups_ids.contains(&file_group) { - Ok(group_permissions) - } else { - Ok(others_permissions) - } - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - fn getgroups_impl() -> nix::Result> { - use core::ptr; - use libc::{c_int, gid_t}; - - let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; - let mut groups = Vec::::with_capacity(Errno::result(ret)? as usize); - let ret = unsafe { - libc::getgroups( - groups.capacity() as c_int, - groups.as_mut_ptr() as *mut gid_t, - ) - }; - - Errno::result(ret).map(|s| { - unsafe { groups.set_len(s as usize) }; - groups - }) - } - - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] - use nix::unistd::getgroups as getgroups_impl; - - #[cfg(target_os = "redox")] - fn getgroups_impl() -> nix::Result> { - Err(nix::Error::EOPNOTSUPP) - } - #[pyfunction] fn getgroups(vm: &VirtualMachine) -> PyResult> { - let group_ids = getgroups_impl().map_err(|e| e.into_pyexception(vm))?; + let group_ids = + rustpython_host_env::posix::getgroups().map_err(|e| e.into_pyexception(vm))?; Ok(group_ids .into_iter() - .map(|gid| vm.ctx.new_int(gid.as_raw()).into()) + .map(|gid| vm.ctx.new_int(gid).into()) .collect()) } #[pyfunction] pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult { - use std::os::unix::fs::MetadataExt; - - let flags = AccessFlags::from_bits(mode).ok_or_else(|| { - vm.new_value_error( - "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK", - ) - })?; - - let metadata = match crate::host_env::fs::metadata(&path.path) { - Ok(m) => m, - // If the file doesn't exist, return False for any access check - Err(_) => return Ok(false), - }; - - // if it's only checking for F_OK - if flags == AccessFlags::F_OK { - return Ok(true); // File exists + match rustpython_host_env::posix::check_access(path.as_ref(), mode) { + Ok(ok) => Ok(ok), + Err(rustpython_host_env::posix::AccessError::InvalidMode) => Err(vm.new_value_error( + "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK", + )), + Err(rustpython_host_env::posix::AccessError::Os(err)) => { + Err(io::Error::from_raw_os_error(err).into_pyexception(vm)) + } } - - let user_id = metadata.uid(); - let group_id = metadata.gid(); - let mode = metadata.mode(); - - let perm = get_right_permission(mode, Uid::from_raw(user_id), Gid::from_raw(group_id)) - .map_err(|err| err.into_pyexception(vm))?; - - let r_ok = !flags.contains(AccessFlags::R_OK) || perm.is_readable; - let w_ok = !flags.contains(AccessFlags::W_OK) || perm.is_writable; - let x_ok = !flags.contains(AccessFlags::X_OK) || perm.is_executable; - - Ok(r_ok && w_ok && x_ok) } #[pyattr] @@ -536,18 +430,13 @@ pub mod module { let dst = args.dst.into_cstring(vm)?; #[cfg(not(target_os = "redox"))] { - nix::unistd::symlinkat(&*src, args.dir_fd.get(), &*dst) + rustpython_host_env::posix::symlinkat(&src, args.dir_fd.get().into(), &dst) .map_err(|err| err.into_pyexception(vm)) } #[cfg(target_os = "redox")] { let [] = args.dir_fd.0; - let res = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }; - if res < 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + rustpython_host_env::posix::symlink(&src, &dst).map_err(|err| err.into_pyexception(vm)) } } @@ -561,13 +450,8 @@ pub mod module { #[cfg(not(target_os = "redox"))] if let Some(fd) = dir_fd.raw_opt() { let c_path = path.clone().into_cstring(vm)?; - let res = unsafe { libc::unlinkat(fd, c_path.as_ptr(), 0) }; - return if res < 0 { - let err = crate::host_env::os::errno_io_error(); - Err(OSErrorBuilder::with_filename(&err, path, vm)) - } else { - Ok(()) - }; + return rustpython_host_env::posix::unlinkat(fd, &c_path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)); } #[cfg(target_os = "redox")] let [] = dir_fd.0; @@ -580,12 +464,7 @@ pub mod module { fn fchdir(fd: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { warn_if_bool_fd(&fd, vm)?; let fd = i32::try_from_object(vm, fd)?; - let ret = unsafe { libc::fchdir(fd) }; - if ret == 0 { - Ok(()) - } else { - Err(io::Error::last_os_error().into_pyexception(vm)) - } + rustpython_host_env::posix::fchdir(fd).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] @@ -593,11 +472,8 @@ pub mod module { fn chroot(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { use crate::exceptions::OSErrorBuilder; - nix::unistd::chroot(&*path.path).map_err(|err| { - // Use `From for io::Error` when it is available - let io_err: io::Error = err.into(); - OSErrorBuilder::with_filename(&io_err, path, vm) - }) + rustpython_host_env::posix::chroot(std::path::Path::new(&path.path)) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } // As of now, redox does not seems to support chown command (cf. https://gitlab.redox-os.org/redox-os/coreutils , last checked on 05/07/2020) @@ -612,7 +488,7 @@ pub mod module { vm: &VirtualMachine, ) -> PyResult<()> { let uid = if uid >= 0 { - Some(nix::unistd::Uid::from_raw(uid as u32)) + Some(uid as u32) } else if uid == -1 { None } else { @@ -620,30 +496,24 @@ pub mod module { }; let gid = if gid >= 0 { - Some(nix::unistd::Gid::from_raw(gid as u32)) + Some(gid as u32) } else if gid == -1 { None } else { return Err(vm.new_os_error("Specified gid is not valid.")); }; - let flag = if follow_symlinks.0 { - nix::fcntl::AtFlags::empty() - } else { - nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW - }; - match path { - OsPathOrFd::Path(ref p) => { - nix::unistd::fchownat(dir_fd.get(), p.path.as_os_str(), uid, gid, flag) - } - OsPathOrFd::Fd(fd) => nix::unistd::fchown(fd, uid, gid), + OsPathOrFd::Path(ref p) => rustpython_host_env::posix::fchownat( + dir_fd.get().into(), + p.path.as_os_str(), + uid, + gid, + follow_symlinks.0, + ), + OsPathOrFd::Fd(fd) => rustpython_host_env::posix::fchown(fd.into(), uid, gid), } - .map_err(|err| { - // Use `From for io::Error` when it is available - let err = io::Error::from_raw_os_error(err as i32); - OSErrorBuilder::with_filename(&err, path, vm) - }) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[cfg(not(target_os = "redox"))] @@ -904,7 +774,7 @@ pub mod module { }; if num_threads > 1 { - let pid = unsafe { libc::getpid() }; + let pid = rustpython_host_env::posix::getpid(); let msg = format!( "This process (pid={pid}) is multi-threaded, use of {name}() may lead to deadlocks in the child." ); @@ -936,24 +806,23 @@ pub mod module { .call(("os.fork",), vm)?; py_os_before_fork(vm); + let pid = rustpython_host_env::posix::fork(); - let pid = unsafe { libc::fork() }; - // Save errno immediately — AfterFork callbacks may clobber it. - let saved_errno = nix::Error::last_raw(); - if pid == 0 { - py_os_after_fork_child(vm); - } else { - // Match CPython timing: capture this before parent after-fork hooks - // in case those hooks start threads. - let num_os_threads = get_number_of_os_threads(); - py_os_after_fork_parent(vm); - // Match CPython timing: warn only after parent callback path resumes world. - warn_if_multi_threaded("fork", num_os_threads, vm); - } - if pid == -1 { - Err(nix::Error::from_raw(saved_errno).into_pyexception(vm)) - } else { - Ok(pid) + match pid { + Ok(0) => { + py_os_after_fork_child(vm); + Ok(0) + } + Ok(pid) => { + // Match CPython timing: capture this before parent after-fork hooks + // in case those hooks start threads. + let num_os_threads = get_number_of_os_threads(); + py_os_after_fork_parent(vm); + // Match CPython timing: warn only after parent callback path resumes world. + warn_if_multi_threaded("fork", num_os_threads, vm); + Ok(pid) + } + Err(err) => Err(err.into_pyexception(vm)), } } @@ -975,45 +844,27 @@ pub mod module { #[cfg(not(target_os = "redox"))] impl MknodArgs<'_> { - fn _mknod(self, vm: &VirtualMachine) -> PyResult { - Ok(unsafe { - libc::mknod( - self.path.clone().into_cstring(vm)?.as_ptr(), - self.mode, - self.device, - ) - }) - } - #[cfg(not(target_vendor = "apple"))] fn mknod(self, vm: &VirtualMachine) -> PyResult<()> { - let ret = match self.dir_fd.raw_opt() { - None => self._mknod(vm)?, - Some(non_default_fd) => unsafe { - libc::mknodat( - non_default_fd, - self.path.clone().into_cstring(vm)?.as_ptr(), - self.mode, - self.device, - ) - }, - }; - if ret != 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) + let c_path = self.path.clone().into_cstring(vm)?; + match self.dir_fd.raw_opt() { + None => rustpython_host_env::posix::mknod(&c_path, self.mode, self.device), + Some(non_default_fd) => rustpython_host_env::posix::mknodat( + non_default_fd, + &c_path, + self.mode, + self.device, + ), } + .map_err(|err| err.into_pyexception(vm)) } #[cfg(target_vendor = "apple")] fn mknod(self, vm: &VirtualMachine) -> PyResult<()> { let [] = self.dir_fd.0; - let ret = self._mknod(vm)?; - if ret != 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + let c_path = self.path.clone().into_cstring(vm)?; + rustpython_host_env::posix::mknod(&c_path, self.mode, self.device) + .map_err(|err| err.into_pyexception(vm)) } } @@ -1026,49 +877,31 @@ pub mod module { #[cfg(not(target_os = "redox"))] #[pyfunction] fn nice(increment: i32, vm: &VirtualMachine) -> PyResult { - Errno::clear(); - let res = unsafe { libc::nice(increment) }; - if res == -1 && Errno::last_raw() != 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(res) - } + rustpython_host_env::posix::nice(increment).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] fn sched_get_priority_max(policy: i32, vm: &VirtualMachine) -> PyResult { - let max = unsafe { libc::sched_get_priority_max(policy) }; - if max == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(max) - } + rustpython_host_env::posix::sched_get_priority_max(policy) + .map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] fn sched_get_priority_min(policy: i32, vm: &VirtualMachine) -> PyResult { - let min = unsafe { libc::sched_get_priority_min(policy) }; - if min == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(min) - } + rustpython_host_env::posix::sched_get_priority_min(policy) + .map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn sched_yield(vm: &VirtualMachine) -> PyResult<()> { - nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm)) + rustpython_host_env::posix::sched_yield().map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn get_inheritable(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult { - let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD); - match flags { - Ok(ret) => Ok((ret & libc::FD_CLOEXEC) == 0), - Err(err) => Err(err.into_pyexception(vm)), - } + rustpython_host_env::fcntl::get_inheritable(fd).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] @@ -1078,36 +911,18 @@ pub mod module { #[pyfunction] fn get_blocking(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult { - let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL); - match flags { - Ok(ret) => Ok((ret & libc::O_NONBLOCK) == 0), - Err(err) => Err(err.into_pyexception(vm)), - } + rustpython_host_env::fcntl::get_blocking(fd).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn set_blocking(fd: BorrowedFd<'_>, blocking: bool, vm: &VirtualMachine) -> PyResult<()> { - let _set_flag = || { - use nix::fcntl::{FcntlArg, OFlag, fcntl}; - - let flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?); - let mut new_flags = flags; - new_flags.set(OFlag::from_bits_truncate(libc::O_NONBLOCK), !blocking); - if flags != new_flags { - fcntl(fd, FcntlArg::F_SETFL(new_flags))?; - } - Ok(()) - }; - _set_flag().map_err(|err: nix::Error| err.into_pyexception(vm)) + rustpython_host_env::fcntl::set_blocking(fd, blocking) + .map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn pipe(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> { - use nix::unistd::pipe; - let (rfd, wfd) = pipe().map_err(|err| err.into_pyexception(vm))?; - set_inheritable(rfd.as_fd(), false, vm)?; - set_inheritable(wfd.as_fd(), false, vm)?; - Ok((rfd, wfd)) + rustpython_host_env::posix::pipe().map_err(|err| err.into_pyexception(vm)) } // cfg from nix @@ -1122,8 +937,7 @@ pub mod module { ))] #[pyfunction] fn pipe2(flags: libc::c_int, vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> { - let oflags = fcntl::OFlag::from_bits_truncate(flags); - nix::unistd::pipe2(oflags).map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::pipe2(flags).map_err(|err| err.into_pyexception(vm)) } fn _chmod( @@ -1147,11 +961,7 @@ pub mod module { #[cfg(not(target_os = "redox"))] fn _fchmod(fd: BorrowedFd<'_>, mode: u32, vm: &VirtualMachine) -> PyResult<()> { - nix::sys::stat::fchmod( - fd, - nix::sys::stat::Mode::from_bits_truncate(mode as libc::mode_t), - ) - .map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::fchmod(fd, mode).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] @@ -1228,9 +1038,7 @@ pub mod module { return Err(vm.new_value_error("execv() arg 2 first element cannot be empty")); } - unistd::execv(&path, &argv) - .map(|_ok| ()) - .map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::execv(&path, &argv).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] @@ -1278,90 +1086,86 @@ pub mod module { let env: Vec<&CStr> = env.iter().map(|entry| entry.as_c_str()).collect(); - unistd::execve(&path, &argv, &env).map_err(|err| err.into_pyexception(vm))?; + rustpython_host_env::posix::execve(&path, &argv, &env) + .map_err(|err| err.into_pyexception(vm))?; Ok(()) } #[pyfunction] fn getppid(vm: &VirtualMachine) -> PyObjectRef { - let ppid = unistd::getppid().as_raw(); + let ppid = rustpython_host_env::posix::getppid(); vm.ctx.new_int(ppid).into() } #[pyfunction] fn getgid(vm: &VirtualMachine) -> PyObjectRef { - let gid = unistd::getgid().as_raw(); + let gid = rustpython_host_env::posix::getgid(); vm.ctx.new_int(gid).into() } #[pyfunction] fn getegid(vm: &VirtualMachine) -> PyObjectRef { - let egid = unistd::getegid().as_raw(); + let egid = rustpython_host_env::posix::getegid(); vm.ctx.new_int(egid).into() } #[pyfunction] fn getpgid(pid: u32, vm: &VirtualMachine) -> PyResult { - let pgid = - unistd::getpgid(Some(Pid::from_raw(pid as i32))).map_err(|e| e.into_pyexception(vm))?; - Ok(vm.new_pyobj(pgid.as_raw())) + let pgid = rustpython_host_env::posix::getpgid(pid).map_err(|e| e.into_pyexception(vm))?; + Ok(vm.new_pyobj(pgid)) } #[pyfunction] fn getpgrp(vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.new_int(unistd::getpgrp().as_raw()).into() + vm.ctx.new_int(rustpython_host_env::posix::getpgrp()).into() } #[cfg(not(target_os = "redox"))] #[pyfunction] fn getsid(pid: u32, vm: &VirtualMachine) -> PyResult { - let sid = - unistd::getsid(Some(Pid::from_raw(pid as i32))).map_err(|e| e.into_pyexception(vm))?; - Ok(vm.new_pyobj(sid.as_raw())) + let sid = rustpython_host_env::posix::getsid(pid).map_err(|e| e.into_pyexception(vm))?; + Ok(vm.new_pyobj(sid)) } #[pyfunction] fn getuid(vm: &VirtualMachine) -> PyObjectRef { - let uid = unistd::getuid().as_raw(); + let uid = rustpython_host_env::posix::getuid(); vm.ctx.new_int(uid).into() } #[pyfunction] fn geteuid(vm: &VirtualMachine) -> PyObjectRef { - let euid = unistd::geteuid().as_raw(); + let euid = rustpython_host_env::posix::geteuid(); vm.ctx.new_int(euid).into() } #[cfg(not(any(target_os = "wasi", target_os = "android")))] #[pyfunction] - fn setgid(gid: Gid, vm: &VirtualMachine) -> PyResult<()> { - unistd::setgid(gid).map_err(|err| err.into_pyexception(vm)) + fn setgid(gid: RawGid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setgid(gid.0).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] #[pyfunction] - fn setegid(egid: Gid, vm: &VirtualMachine) -> PyResult<()> { - unistd::setegid(egid).map_err(|err| err.into_pyexception(vm)) + fn setegid(egid: RawGid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setegid(egid.0).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn setpgid(pid: u32, pgid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setpgid(Pid::from_raw(pid as i32), Pid::from_raw(pgid as i32)) - .map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::setpgid(pid, pgid).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn setpgrp(vm: &VirtualMachine) -> PyResult<()> { // setpgrp() is equivalent to setpgid(0, 0) - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::setpgrp().map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "redox")))] #[pyfunction] fn setsid(vm: &VirtualMachine) -> PyResult<()> { - unistd::setsid() - .map(|_ok| ()) - .map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::setsid().map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "redox")))] @@ -1369,9 +1173,7 @@ pub mod module { fn tcgetpgrp(fd: i32, vm: &VirtualMachine) -> PyResult { use std::os::fd::BorrowedFd; let fd = unsafe { BorrowedFd::borrow_raw(fd) }; - unistd::tcgetpgrp(fd) - .map(|pid| pid.as_raw()) - .map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::tcgetpgrp(fd).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "redox")))] @@ -1379,7 +1181,7 @@ pub mod module { fn tcsetpgrp(fd: i32, pgid: libc::pid_t, vm: &VirtualMachine) -> PyResult<()> { use std::os::fd::BorrowedFd; let fd = unsafe { BorrowedFd::borrow_raw(fd) }; - unistd::tcsetpgrp(fd, Pid::from_raw(pgid)).map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::tcsetpgrp(fd, pgid).map_err(|err| err.into_pyexception(vm)) } fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult { @@ -1408,35 +1210,40 @@ pub mod module { } } - impl TryFromObject for Uid { + #[derive(Clone, Copy)] + struct RawUid(u32); + + #[derive(Clone, Copy)] + struct RawGid(u32); + + impl TryFromObject for RawUid { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - try_from_id(vm, obj, "uid").map(Self::from_raw) + try_from_id(vm, obj, "uid").map(Self) } } - impl TryFromObject for Gid { + impl TryFromObject for RawGid { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - try_from_id(vm, obj, "gid").map(Self::from_raw) + try_from_id(vm, obj, "gid").map(Self) } } #[cfg(not(any(target_os = "wasi", target_os = "android")))] #[pyfunction] - fn setuid(uid: Uid) -> nix::Result<()> { - unistd::setuid(uid) + fn setuid(uid: RawUid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setuid(uid.0).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] #[pyfunction] - fn seteuid(euid: Uid) -> nix::Result<()> { - unistd::seteuid(euid) + fn seteuid(euid: RawUid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::seteuid(euid.0).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] #[pyfunction] - fn setreuid(ruid: Uid, euid: Uid) -> nix::Result<()> { - let ret = unsafe { libc::setreuid(ruid.as_raw(), euid.as_raw()) }; - nix::Error::result(ret).map(drop) + fn setreuid(ruid: RawUid, euid: RawUid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setreuid(ruid.0, euid.0).map_err(|err| err.into_pyexception(vm)) } // cfg from nix @@ -1447,107 +1254,96 @@ pub mod module { target_os = "openbsd" ))] #[pyfunction] - fn setresuid(ruid: Uid, euid: Uid, suid: Uid) -> nix::Result<()> { - unistd::setresuid(ruid, euid, suid) + fn setresuid(ruid: RawUid, euid: RawUid, suid: RawUid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setresuid(ruid.0, euid.0, suid.0) + .map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] fn openpty(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> { - let r = nix::pty::openpty(None, None).map_err(|err| err.into_pyexception(vm))?; - for fd in [&r.master, &r.slave] { - super::set_inheritable(fd.as_fd(), false).map_err(|e| e.into_pyexception(vm))?; - } - Ok((r.master, r.slave)) + rustpython_host_env::posix::openpty().map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn ttyname(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult { - let name = unistd::ttyname(fd).map_err(|e| e.into_pyexception(vm))?; - let name = name.into_os_string().into_string().unwrap(); + let name = rustpython_host_env::posix::ttyname(fd).map_err(|e| e.into_pyexception(vm))?; + let name = name.into_string().unwrap(); Ok(vm.ctx.new_str(name).into()) } #[pyfunction] fn umask(mask: libc::mode_t) -> libc::mode_t { - unsafe { libc::umask(mask) } + rustpython_host_env::posix::umask(mask) } #[pyfunction] - fn uname() -> _os::UnameResultData { - let info = rustix::system::uname(); - _os::UnameResultData { - sysname: info.sysname().to_string_lossy().into(), - nodename: info.nodename().to_string_lossy().into(), - release: info.release().to_string_lossy().into(), - version: info.version().to_string_lossy().into(), - machine: info.machine().to_string_lossy().into(), - } + fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResultData> { + let info = + rustpython_host_env::posix::uname_info().map_err(|err| err.into_pyexception(vm))?; + Ok(_os::UnameResultData { + sysname: info.sysname, + nodename: info.nodename, + release: info.release, + version: info.version, + machine: info.machine, + }) } #[pyfunction] fn sync() { #[cfg(not(any(target_os = "redox", target_os = "android")))] - unsafe { - libc::sync(); - } + rustpython_host_env::posix::sync(); } // cfg from nix #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn getresuid() -> nix::Result<(u32, u32, u32)> { - let ret = unistd::getresuid()?; - Ok(( - ret.real.as_raw(), - ret.effective.as_raw(), - ret.saved.as_raw(), - )) + fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { + rustpython_host_env::posix::getresuid().map_err(|err| err.into_pyexception(vm)) } // cfg from nix #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn getresgid() -> nix::Result<(u32, u32, u32)> { - let ret = unistd::getresgid()?; - Ok(( - ret.real.as_raw(), - ret.effective.as_raw(), - ret.saved.as_raw(), - )) + fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { + rustpython_host_env::posix::getresgid().map_err(|err| err.into_pyexception(vm)) } // cfg from nix #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn setresgid(rgid: Gid, egid: Gid, sgid: Gid, vm: &VirtualMachine) -> PyResult<()> { - unistd::setresgid(rgid, egid, sgid).map_err(|err| err.into_pyexception(vm)) + fn setresgid(rgid: RawGid, egid: RawGid, sgid: RawGid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setresgid(rgid.0, egid.0, sgid.0) + .map_err(|err| err.into_pyexception(vm)) } #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))] #[pyfunction] - fn setregid(rgid: Gid, egid: Gid) -> nix::Result<()> { - let ret = unsafe { libc::setregid(rgid.as_raw(), egid.as_raw()) }; - nix::Error::result(ret).map(drop) + fn setregid(rgid: RawGid, egid: RawGid, vm: &VirtualMachine) -> PyResult<()> { + rustpython_host_env::posix::setregid(rgid.0, egid.0).map_err(|err| err.into_pyexception(vm)) } // cfg from nix #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn initgroups(user_name: PyUtf8StrRef, gid: Gid, vm: &VirtualMachine) -> PyResult<()> { + fn initgroups(user_name: PyUtf8StrRef, gid: RawGid, vm: &VirtualMachine) -> PyResult<()> { let user = user_name.to_cstring(vm)?; - unistd::initgroups(&user, gid).map_err(|err| err.into_pyexception(vm)) + rustpython_host_env::posix::initgroups(&user, gid.0).map_err(|err| err.into_pyexception(vm)) } // cfg from nix #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] #[pyfunction] fn setgroups( - group_ids: crate::function::ArgIterable, + group_ids: crate::function::ArgIterable, vm: &VirtualMachine, ) -> PyResult<()> { - let gids = group_ids.iter(vm)?.collect::, _>>()?; - unistd::setgroups(&gids).map_err(|err| err.into_pyexception(vm)) + let gids = group_ids + .iter(vm)? + .map(|gid| gid.map(|gid| gid.0)) + .collect::, _>>()?; + rustpython_host_env::posix::setgroups_raw(&gids).map_err(|err| err.into_pyexception(vm)) } #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] @@ -1637,8 +1433,6 @@ pub mod module { #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] impl PosixSpawnArgs { fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult { - use nix::sys::signal; - use crate::TryFromBorrowedObject; let path = self @@ -1647,8 +1441,7 @@ pub mod module { .into_cstring(vm) .map_err(|_| vm.new_value_error("path should not have nul bytes"))?; - let mut file_actions = - nix::spawn::PosixSpawnFileActions::init().map_err(|e| e.into_pyexception(vm))?; + let mut file_actions = Vec::new(); if let Some(it) = self.file_actions { for action in it.iter(vm)? { let action = action?; @@ -1659,7 +1452,7 @@ pub mod module { let id = PosixSpawnFileActionIdentifier::try_from(id) .map_err(|_| vm.new_type_error("Unknown file_actions identifier"))?; let args: crate::function::FuncArgs = args.to_vec().into(); - let ret = match id { + let parsed = match id { PosixSpawnFileActionIdentifier::Open => { let (fd, path, oflag, mode): (_, OsPath, _, _) = args.bind(vm)?; let path = CString::new(path.into_bytes()).map_err(|_| { @@ -1667,90 +1460,40 @@ pub mod module { "POSIX_SPAWN_OPEN path should not have nul bytes", ) })?; - let oflag = nix::fcntl::OFlag::from_bits_retain(oflag); - let mode = nix::sys::stat::Mode::from_bits_retain(mode); - file_actions.add_open(fd, &*path, oflag, mode) + rustpython_host_env::posix::PosixSpawnFileAction::Open { + fd, + path, + oflag, + mode, + } } PosixSpawnFileActionIdentifier::Close => { let (fd,) = args.bind(vm)?; - file_actions.add_close(fd) + rustpython_host_env::posix::PosixSpawnFileAction::Close { fd } } PosixSpawnFileActionIdentifier::Dup2 => { let (fd, newfd) = args.bind(vm)?; - file_actions.add_dup2(fd, newfd) + rustpython_host_env::posix::PosixSpawnFileAction::Dup2 { fd, newfd } } }; - if let Err(err) = ret { - let err = err.into(); - return Err(OSErrorBuilder::with_filename(&err, self.path, vm)); - } - } - } - - let mut attrp = - nix::spawn::PosixSpawnAttr::init().map_err(|e| e.into_pyexception(vm))?; - let mut flags = nix::spawn::PosixSpawnFlags::empty(); - - if let Some(sigs) = self.setsigdef { - let mut set = signal::SigSet::empty(); - for sig in sigs.iter(vm)? { - let sig = sig?; - let sig = signal::Signal::try_from(sig).map_err(|_| { - vm.new_value_error(format!("signal number {sig} out of range")) - })?; - set.add(sig); + file_actions.push(parsed); } - attrp - .set_sigdefault(&set) - .map_err(|e| e.into_pyexception(vm))?; - flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGDEF); - } - - if let Some(pgid) = self.setpgroup { - attrp - .set_pgroup(nix::unistd::Pid::from_raw(pgid)) - .map_err(|e| e.into_pyexception(vm))?; - flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETPGROUP); - } - - if self.resetids { - flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_RESETIDS); } - if self.setsid { - // Note: POSIX_SPAWN_SETSID may not be available on all platforms - cfg_select! { - any( - target_os = "linux", - target_os = "haiku", - target_os = "solaris", - target_os = "illumos", - target_os = "hurd", - ) => { - flags.insert(nix::spawn::PosixSpawnFlags::from_bits_retain(libc::POSIX_SPAWN_SETSID)); - } - _ => { - return Err(vm.new_not_implemented_error( - "setsid parameter is not supported on this platform", - )); + let setsigdef = self + .setsigdef + .map(|sigs| { + let sigs = sigs.iter(vm)?.collect::>>()?; + for &sig in &sigs { + if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) { + return Err( + vm.new_value_error(format!("signal number {sig} out of range")) + ); + } } - } - } - - if let Some(sigs) = self.setsigmask { - let mut set = signal::SigSet::empty(); - for sig in sigs.iter(vm)? { - let sig = sig?; - let sig = signal::Signal::try_from(sig).map_err(|_| { - vm.new_value_error(format!("signal number {sig} out of range")) - })?; - set.add(sig); - } - attrp - .set_sigmask(&set) - .map_err(|e| e.into_pyexception(vm))?; - flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGMASK); - } + Ok(sigs) + }) + .transpose()?; if let Some(_scheduler) = self.scheduler { // TODO: Implement scheduler parameter handling @@ -1760,10 +1503,27 @@ pub mod module { ); } - if !flags.is_empty() { - attrp.set_flags(flags).map_err(|e| e.into_pyexception(vm))?; + if self.setsid && !rustpython_host_env::posix::supports_posix_spawn_setsid() { + return Err(vm.new_not_implemented_error( + "setsid parameter is not supported on this platform", + )); } + let setsigmask = self + .setsigmask + .map(|sigs| { + let sigs = sigs.iter(vm)?.collect::>>()?; + for &sig in &sigs { + if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) { + return Err( + vm.new_value_error(format!("signal number {sig} out of range")) + ); + } + } + Ok(sigs) + }) + .transpose()?; + let args: Vec = self .args .iter(vm)? @@ -1789,13 +1549,19 @@ pub mod module { .collect::>>()? }; - let ret = if spawnp { - nix::spawn::posix_spawnp(&path, &file_actions, &attrp, &args, &env) - } else { - nix::spawn::posix_spawn(&*path, &file_actions, &attrp, &args, &env) - }; - ret.map(Into::into) - .map_err(|err| OSErrorBuilder::with_filename(&err.into(), self.path, vm)) + rustpython_host_env::posix::posix_spawn(rustpython_host_env::posix::PosixSpawnConfig { + path: &path, + args: &args, + env: &env, + file_actions: &file_actions, + setsigdef: setsigdef.as_deref(), + setpgroup: self.setpgroup, + resetids: self.resetids, + setsid: self.setsid, + setsigmask: setsigmask.as_deref(), + spawnp, + }) + .map_err(|err| OSErrorBuilder::with_filename(&err, self.path, vm)) } } @@ -1813,42 +1579,42 @@ pub mod module { #[pyfunction(name = "WCOREDUMP")] fn wcoredump(status: i32) -> bool { - libc::WCOREDUMP(status) + rustpython_host_env::posix::wcoredump(status) } #[pyfunction(name = "WIFCONTINUED")] fn wifcontinued(status: i32) -> bool { - libc::WIFCONTINUED(status) + rustpython_host_env::posix::wifcontinued(status) } #[pyfunction(name = "WIFSTOPPED")] fn wifstopped(status: i32) -> bool { - libc::WIFSTOPPED(status) + rustpython_host_env::posix::wifstopped(status) } #[pyfunction(name = "WIFSIGNALED")] fn wifsignaled(status: i32) -> bool { - libc::WIFSIGNALED(status) + rustpython_host_env::posix::wifsignaled(status) } #[pyfunction(name = "WIFEXITED")] fn wifexited(status: i32) -> bool { - libc::WIFEXITED(status) + rustpython_host_env::posix::wifexited(status) } #[pyfunction(name = "WEXITSTATUS")] fn wexitstatus(status: i32) -> i32 { - libc::WEXITSTATUS(status) + rustpython_host_env::posix::wexitstatus(status) } #[pyfunction(name = "WSTOPSIG")] fn wstopsig(status: i32) -> i32 { - libc::WSTOPSIG(status) + rustpython_host_env::posix::wstopsig(status) } #[pyfunction(name = "WTERMSIG")] fn wtermsig(status: i32) -> i32 { - libc::WTERMSIG(status) + rustpython_host_env::posix::wtermsig(status) } #[cfg(target_os = "linux")] @@ -1859,33 +1625,23 @@ pub mod module { vm: &VirtualMachine, ) -> PyResult { let flags = flags.unwrap_or(0); - let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) as libc::c_long }; - if fd == -1 { - Err(vm.new_last_errno_error()) - } else { - // Safety: syscall returns a new owned file descriptor. - Ok(unsafe { OwnedFd::from_raw_fd(fd as libc::c_int) }) - } + rustpython_host_env::posix::pidfd_open(pid, flags).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { let mut status = 0; loop { - // Capture errno inside the closure: attach_thread (called by - // allow_threads on return) can clobber errno via syscalls. - let (res, err) = vm.allow_threads(|| { - let r = unsafe { libc::waitpid(pid, &mut status, opt) }; - (r, nix::Error::last_raw()) - }); - if res == -1 { - if err == libc::EINTR { + let res = + vm.allow_threads(|| rustpython_host_env::posix::waitpid(pid, &mut status, opt)); + match res { + Err(err) if err.raw_os_error() == Some(libc::EINTR) => { vm.check_signals()?; continue; } - return Err(nix::Error::from_raw(err).into_pyexception(vm)); + Err(err) => return Err(err.into_pyexception(vm)), + Ok(res) => return Ok((res, status)), } - return Ok((res, status)); } } @@ -1896,14 +1652,7 @@ pub mod module { #[pyfunction] fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { - { - let ret = unsafe { libc::kill(pid, sig as i32) }; - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } - } + rustpython_host_env::posix::kill(pid, sig as i32).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] @@ -1911,18 +1660,10 @@ pub mod module { fd: OptionalArg, vm: &VirtualMachine, ) -> PyResult<_os::TerminalSizeData> { - let (columns, lines) = { - nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize); - let mut w = libc::winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - unsafe { winsz(fd.unwrap_or(libc::STDOUT_FILENO), &mut w) } + let (columns, lines) = + rustpython_host_env::posix::get_terminal_size(fd.unwrap_or(libc::STDOUT_FILENO)) + .map(|(columns, lines)| (columns.into(), lines.into())) .map_err(|err| err.into_pyexception(vm))?; - (w.ws_col.into(), w.ws_row.into()) - }; Ok(_os::TerminalSizeData { columns, lines }) } @@ -1951,10 +1692,7 @@ pub mod module { #[pyfunction] fn dup(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult { - let fd = nix::unistd::dup(fd).map_err(|e| e.into_pyexception(vm))?; - super::set_inheritable(fd.as_fd(), false) - .map(|()| fd) - .map_err(|e| e.into_pyexception(vm)) + rustpython_host_env::posix::dup_noninheritable(fd).map_err(|e| e.into_pyexception(vm)) } #[derive(FromArgs)] @@ -1969,13 +1707,8 @@ pub mod module { #[pyfunction] fn dup2(args: Dup2Args<'_>, vm: &VirtualMachine) -> PyResult { - let mut fd2 = core::mem::ManuallyDrop::new(args.fd2); - nix::unistd::dup2(args.fd, &mut fd2).map_err(|e| e.into_pyexception(vm))?; - let fd2 = core::mem::ManuallyDrop::into_inner(fd2); - if !args.inheritable { - super::set_inheritable(fd2.as_fd(), false).map_err(|e| e.into_pyexception(vm))? - } - Ok(fd2) + rustpython_host_env::posix::dup2(args.fd, args.fd2, args.inheritable) + .map_err(|e| e.into_pyexception(vm)) } pub(crate) fn support_funcs() -> Vec { @@ -2013,12 +1746,10 @@ pub mod module { // Get a pointer to the login name string. The string is statically // allocated and might be overwritten on subsequent calls to this // function or to `cuserid()`. See man getlogin(3) for more information. - let ptr = unsafe { libc::getlogin() }; - if ptr.is_null() { + let Some(login) = rustpython_host_env::posix::getlogin() else { return Err(vm.new_os_error("unable to determine login name")); - } - let slice = unsafe { CStr::from_ptr(ptr) }; - slice + }; + login .to_str() .map(|s| s.to_owned()) .map_err(|e| vm.new_unicode_decode_error(format!("unable to decode login name: {e}"))) @@ -2038,56 +1769,33 @@ pub mod module { vm: &VirtualMachine, ) -> PyResult> { let user = user.to_cstring(vm)?; - let gid = Gid::from_raw(group); - let group_ids = unistd::getgrouplist(&user, gid).map_err(|err| err.into_pyexception(vm))?; - Ok(group_ids - .into_iter() - .map(|gid| vm.new_pyobj(gid.as_raw())) - .collect()) + let group_ids = rustpython_host_env::posix::getgrouplist(&user, group) + .map_err(|err| err.into_pyexception(vm))?; + Ok(group_ids.into_iter().map(|gid| vm.new_pyobj(gid)).collect()) } - #[cfg(not(target_os = "redox"))] - type PriorityWhichType = cfg_select! { - all(target_os = "linux", target_env = "gnu") => libc::__priority_which_t, - _ => libc::c_int, - }; - - #[cfg(not(target_os = "redox"))] - type PriorityWhoType = cfg_select! { - target_os = "freebsd" => i32, - _ => u32, - }; - #[cfg(not(target_os = "redox"))] #[pyfunction] fn getpriority( - which: PriorityWhichType, - who: PriorityWhoType, + which: rustpython_host_env::posix::PriorityWhichType, + who: rustpython_host_env::posix::PriorityWhoType, vm: &VirtualMachine, ) -> PyResult { - Errno::clear(); - let retval = unsafe { libc::getpriority(which, who) }; - if Errno::last_raw() != 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(vm.ctx.new_int(retval).into()) - } + rustpython_host_env::posix::getpriority(which, who) + .map(|retval| vm.ctx.new_int(retval).into()) + .map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] fn setpriority( - which: PriorityWhichType, - who: PriorityWhoType, + which: rustpython_host_env::posix::PriorityWhichType, + who: rustpython_host_env::posix::PriorityWhoType, priority: i32, vm: &VirtualMachine, ) -> PyResult<()> { - let retval = unsafe { libc::setpriority(which, who, priority) }; - if retval == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + rustpython_host_env::posix::setpriority(which, who, priority) + .map_err(|err| err.into_pyexception(vm)) } struct PathconfName(i32); @@ -2110,8 +1818,7 @@ pub mod module { } } - // Copy from [nix::unistd::PathconfVar](https://docs.rs/nix/0.21.0/nix/unistd/enum.PathconfVar.html) - // Change enum name to fit python doc + // Mirror the libc pathconf constants as Python-facing names. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)] #[repr(i32)] #[allow(non_camel_case_types)] @@ -2284,28 +1991,14 @@ pub mod module { PathconfName(name): PathconfName, vm: &VirtualMachine, ) -> PyResult> { - Errno::clear(); - debug_assert_eq!(Errno::last_raw(), 0); - let raw = match &path { + match &path { OsPathOrFd::Path(path) => { - let path = path.clone().into_cstring(vm)?; - unsafe { libc::pathconf(path.as_ptr(), name) } + let c_path = path.clone().into_cstring(vm)?; + rustpython_host_env::posix::pathconf(&c_path, name) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm)) } - OsPathOrFd::Fd(fd) => unsafe { libc::fpathconf(fd.as_raw(), name) }, - }; - - if raw == -1 { - if Errno::last_raw() == 0 { - Ok(None) - } else { - Err(OSErrorBuilder::with_filename( - &io::Error::from(Errno::last()), - path, - vm, - )) - } - } else { - Ok(Some(raw)) + OsPathOrFd::Fd(fd) => rustpython_host_env::posix::fpathconf(fd.as_raw(), name) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)), } } @@ -2511,12 +2204,7 @@ pub mod module { #[pyfunction] fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult { - crate::host_env::os::set_errno(0); - let r = unsafe { libc::sysconf(name.0) }; - if r == -1 && crate::host_env::os::get_errno() != 0 { - return Err(vm.new_last_errno_error()); - } - Ok(r) + rustpython_host_env::posix::sysconf(name.0).map_err(|err| err.into_pyexception(vm)) } #[pyattr] @@ -2559,10 +2247,10 @@ pub mod module { fn sendfile(args: SendFileArgs<'_>, vm: &VirtualMachine) -> PyResult { let mut file_offset = args.offset; - let res = nix::sys::sendfile::sendfile( + let res = rustpython_host_env::posix::sendfile( args.out_fd, args.in_fd, - Some(&mut file_offset), + &mut file_offset, args.count as usize, ) .map_err(|err| err.into_pyexception(vm))?; @@ -2609,11 +2297,11 @@ pub mod module { .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); let trailers = trailers.as_deref(); - let (res, written) = nix::sys::sendfile::sendfile( + let (res, written) = rustpython_host_env::posix::sendfile( args.in_fd, args.out_fd, args.offset, - Some(count), + count, headers, trailers, ); @@ -2628,11 +2316,6 @@ pub mod module { Ok(vm.ctx.new_int(written as u64).into()) } - #[cfg(target_os = "linux")] - unsafe fn sys_getrandom(buf: *mut libc::c_void, buflen: usize, flags: u32) -> isize { - unsafe { libc::syscall(libc::SYS_getrandom, buf, buflen, flags as usize) as _ } - } - #[cfg(target_os = "linux")] #[pyfunction] fn getrandom(size: isize, flags: OptionalArg, vm: &VirtualMachine) -> PyResult> { @@ -2640,12 +2323,11 @@ pub mod module { .map_err(|_| vm.new_os_error(format!("Invalid argument for size: {size}")))?; let mut buf = Vec::with_capacity(size); unsafe { - let len = sys_getrandom( + let len = rustpython_host_env::posix::getrandom( buf.as_mut_ptr() as *mut libc::c_void, size, flags.unwrap_or(0), ) - .try_into() .map_err(|_| vm.new_last_os_error())?; buf.set_len(len); } @@ -2671,8 +2353,11 @@ pub mod module { #[pymodule(sub)] mod posix_sched { use crate::{ - AsObject, Py, PyObjectRef, PyResult, VirtualMachine, builtins::PyTupleRef, - convert::ToPyObject, function::FuncArgs, types::PyStructSequence, + AsObject, Py, PyObjectRef, PyResult, VirtualMachine, + builtins::PyTupleRef, + convert::{IntoPyException, ToPyObject}, + function::FuncArgs, + types::PyStructSequence, }; #[derive(FromArgs)] @@ -2750,12 +2435,7 @@ mod posix_sched { #[pyfunction] fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult { - let policy = unsafe { libc::sched_getscheduler(pid) }; - if policy == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(policy) - } + rustpython_host_env::posix::sched_getscheduler(pid).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_env = "musl"))] @@ -2773,23 +2453,14 @@ mod posix_sched { #[pyfunction] fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult { let libc_sched_param = convert_sched_param(&args.sched_param, vm)?; - let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) }; - if policy == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(policy) - } + rustpython_host_env::posix::sched_setscheduler(args.pid, args.policy, &libc_sched_param) + .map_err(|err| err.into_pyexception(vm)) } #[pyfunction] fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult { - let param = unsafe { - let mut param = core::mem::MaybeUninit::uninit(); - if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) { - return Err(vm.new_last_errno_error()); - } - param.assume_init() - }; + let param = rustpython_host_env::posix::sched_getparam(pid) + .map_err(|err| err.into_pyexception(vm))?; Ok(PySchedParam::from_data( SchedParamData { sched_priority: param.sched_priority.to_pyobject(vm), @@ -2811,11 +2482,7 @@ mod posix_sched { #[pyfunction] fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult { let libc_sched_param = convert_sched_param(&args.sched_param, vm)?; - let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) }; - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(ret) - } + rustpython_host_env::posix::sched_setparam(args.pid, &libc_sched_param) + .map_err(|err| err.into_pyexception(vm)) } } diff --git a/crates/vm/src/stdlib/pwd.rs b/crates/vm/src/stdlib/pwd.rs index b898625906f..e2f987ce019 100644 --- a/crates/vm/src/stdlib/pwd.rs +++ b/crates/vm/src/stdlib/pwd.rs @@ -11,7 +11,7 @@ mod pwd { exceptions, types::PyStructSequence, }; - use nix::unistd::{self, User}; + use rustpython_host_env::pwd as host_pwd; #[cfg(not(target_os = "android"))] use crate::{PyObjectRef, convert::ToPyObject}; @@ -34,26 +34,16 @@ mod pwd { #[pyclass(with(PyStructSequence))] impl PyPasswd {} - impl From for PasswdData { - fn from(user: User) -> Self { - // this is just a pain... - let cstr_lossy = |s: alloc::ffi::CString| { - s.into_string() - .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) - }; - let pathbuf_lossy = |p: std::path::PathBuf| { - p.into_os_string() - .into_string() - .unwrap_or_else(|s| s.to_string_lossy().into_owned()) - }; + impl From for PasswdData { + fn from(user: host_pwd::Passwd) -> Self { Self { pw_name: user.name, - pw_passwd: cstr_lossy(user.passwd), - pw_uid: user.uid.as_raw(), - pw_gid: user.gid.as_raw(), - pw_gecos: cstr_lossy(user.gecos), - pw_dir: pathbuf_lossy(user.dir), - pw_shell: pathbuf_lossy(user.shell), + pw_passwd: user.passwd, + pw_uid: user.uid, + pw_gid: user.gid, + pw_gecos: user.gecos, + pw_dir: user.dir, + pw_shell: user.shell, } } } @@ -64,7 +54,7 @@ mod pwd { if pw_name.contains('\0') { return Err(exceptions::cstring_error(vm)); } - let user = User::from_name(name.as_str()).ok().flatten(); + let user = host_pwd::getpwnam(name.as_str()); let user = user.ok_or_else(|| { vm.new_key_error( vm.ctx @@ -77,11 +67,9 @@ mod pwd { #[pyfunction] fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult { - let uid_t = libc::uid_t::try_from(uid.as_bigint()) - .map(unistd::Uid::from_raw) - .ok(); + let uid_t = libc::uid_t::try_from(uid.as_bigint()).ok(); let user = uid_t - .map(User::from_uid) + .map(host_pwd::getpwuid) .transpose() .map_err(|err| err.into_pyexception(vm))? .flatten(); @@ -99,19 +87,10 @@ mod pwd { #[cfg(not(target_os = "android"))] #[pyfunction] fn getpwall(vm: &VirtualMachine) -> Vec { - // setpwent, getpwent, etc are not thread safe. Could use fgetpwent_r, but this is easier - static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); - let _guard = GETPWALL.lock(); - let mut list = Vec::new(); - - unsafe { libc::setpwent() }; - while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) { - let user = User::from(unsafe { ptr.as_ref() }); - let passwd = PasswdData::from(user).to_pyobject(vm); - list.push(passwd); - } - unsafe { libc::endpwent() }; - - list + host_pwd::getpwall() + .into_iter() + .map(PasswdData::from) + .map(|passwd| passwd.to_pyobject(vm)) + .collect() } } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 02beec87bc5..72fa1f6432b 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -57,15 +57,6 @@ pub mod sys { io::{IsTerminal, Read, Write}, }; - #[cfg(windows)] - use windows_sys::Win32::{ - Foundation::MAX_PATH, - Storage::FileSystem::{ - GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FIXEDFILEINFO, VerQueryValueW, - }, - System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW}, - }; - // Rust target triple (e.g., "x86_64-unknown-linux-gnu") pub(crate) const RUST_MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE"); @@ -1082,119 +1073,22 @@ pub mod sys { vm.trace_func.borrow().clone() } - #[cfg(windows)] - fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> { - use crate::host_env::windows::ToWideString; - unsafe { - // Create a wide string for "kernel32.dll" - let module_name: Vec = std::ffi::OsStr::new("kernel32.dll").to_wide_with_nul(); - let h_kernel32 = GetModuleHandleW(module_name.as_ptr()); - if h_kernel32.is_null() { - return Err(std::io::Error::last_os_error()); - } - - // Prepare a buffer for the module file path - let mut kernel32_path = [0u16; MAX_PATH as usize]; - let len = GetModuleFileNameW( - h_kernel32, - kernel32_path.as_mut_ptr(), - kernel32_path.len() as u32, - ); - if len == 0 { - return Err(std::io::Error::last_os_error()); - } - - // Get the size of the version information block - let ver_block_size = - GetFileVersionInfoSizeW(kernel32_path.as_ptr(), core::ptr::null_mut()); - if ver_block_size == 0 { - return Err(std::io::Error::last_os_error()); - } - - // Allocate a buffer to hold the version information - let mut ver_block = vec![0u8; ver_block_size as usize]; - if GetFileVersionInfoW( - kernel32_path.as_ptr(), - 0, - ver_block_size, - ver_block.as_mut_ptr() as *mut _, - ) == 0 - { - return Err(std::io::Error::last_os_error()); - } - - // Prepare an empty sub-block string (L"") as required by VerQueryValueW - let sub_block: Vec = std::ffi::OsStr::new("").to_wide_with_nul(); - - let mut ffi_ptr: *mut VS_FIXEDFILEINFO = core::ptr::null_mut(); - let mut ffi_len: u32 = 0; - if VerQueryValueW( - ver_block.as_ptr() as *const _, - sub_block.as_ptr(), - &mut ffi_ptr as *mut *mut VS_FIXEDFILEINFO as *mut *mut _, - &mut ffi_len as *mut u32, - ) == 0 - || ffi_ptr.is_null() - { - return Err(std::io::Error::last_os_error()); - } - - // Extract the version numbers from the VS_FIXEDFILEINFO structure. - let ffi = *ffi_ptr; - let real_major = (ffi.dwProductVersionMS >> 16) & 0xFFFF; - let real_minor = ffi.dwProductVersionMS & 0xFFFF; - let real_build = (ffi.dwProductVersionLS >> 16) & 0xFFFF; - - Ok((real_major, real_minor, real_build)) - } - } - #[cfg(windows)] #[pyfunction] fn getwindowsversion(vm: &VirtualMachine) -> PyResult { - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; - use windows_sys::Win32::System::SystemInformation::{ - GetVersionExW, OSVERSIONINFOEXW, OSVERSIONINFOW, - }; - - let mut version: OSVERSIONINFOEXW = unsafe { core::mem::zeroed() }; - version.dwOSVersionInfoSize = core::mem::size_of::() as u32; - let result = unsafe { - let os_vi = &mut version as *mut OSVERSIONINFOEXW as *mut OSVERSIONINFOW; - // SAFETY: GetVersionExW accepts a pointer of OSVERSIONINFOW, but windows-sys crate's type currently doesn't allow to do so. - // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw#parameters - GetVersionExW(os_vi) - }; - - if result == 0 { - return Err(vm.new_os_error("failed to get windows version".to_owned())); - } - - let service_pack = { - let (last, _) = version - .szCSDVersion - .iter() - .take_while(|&x| x != &0) - .enumerate() - .last() - .unwrap_or((0, &0)); - let sp = OsString::from_wide(&version.szCSDVersion[..last]); - sp.into_string() - .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))? - }; - let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?; + let version = crate::host_env::windows::get_windows_version() + .map_err(|e| vm.new_os_error(e.to_string()))?; let winver = WindowsVersionData { - major: real_version.0, - minor: real_version.1, - build: real_version.2, - platform: version.dwPlatformId, - service_pack, - service_pack_major: version.wServicePackMajor, - service_pack_minor: version.wServicePackMinor, - suite_mask: version.wSuiteMask, - product_type: version.wProductType, - platform_version: (real_version.0, real_version.1, real_version.2), // TODO Provide accurate version, like CPython impl + major: version.major, + minor: version.minor, + build: version.build, + platform: version.platform, + service_pack: version.service_pack, + service_pack_major: version.service_pack_major, + service_pack_minor: version.service_pack_minor, + suite_mask: version.suite_mask, + product_type: version.product_type, + platform_version: (version.major, version.minor, version.build), // TODO Provide accurate version, like CPython impl }; Ok(PyWindowsVersion::from_data(winver, vm)) } diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 31e41a89b08..80a832d37f1 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -24,17 +24,19 @@ unsafe extern "C" { } #[pymodule(name = "time", with(#[cfg(any(unix, windows))] platform))] -pub mod decl { +mod decl { + #![allow(unreachable_pub)] + + #[cfg(any(unix, windows))] + use crate::builtins::PyBaseExceptionRef; use crate::{ AsObject, Py, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyStrRef, PyTypeRef}, + builtins::{PyStrRef, PyTypeRef}, function::{Either, FuncArgs, OptionalArg}, types::{PyStructSequence, struct_sequence_new}, }; #[cfg(any(unix, windows))] use crate::{common::wtf8::Wtf8Buf, convert::ToPyObject}; - #[cfg(unix)] - use alloc::ffi::CString; #[cfg(not(any(unix, windows)))] use chrono::{ DateTime, Datelike, TimeZone, Timelike, @@ -44,19 +46,6 @@ pub mod decl { #[cfg(any(unix, windows))] use rustpython_host_env::time::asctime_from_tm; use rustpython_host_env::time::{self as host_time}; - #[cfg(target_env = "msvc")] - #[cfg(not(target_arch = "wasm32"))] - use windows_sys::Win32::System::Time::TIME_ZONE_INFORMATION; - - #[cfg(windows)] - unsafe extern "C" { - fn wcsftime( - s: *mut libc::wchar_t, - max: libc::size_t, - format: *const libc::wchar_t, - tm: *const libc::tm, - ) -> libc::size_t; - } #[allow(dead_code)] pub(super) const SEC_TO_MS: i64 = host_time::SEC_TO_MS; @@ -125,18 +114,17 @@ pub mod decl { if remaining.is_zero() { break; } - let ts = nix::sys::time::TimeSpec::from(remaining); - let (res, err) = vm.allow_threads(|| { - let r = unsafe { libc::nanosleep(ts.as_ref(), core::ptr::null_mut()) }; - (r, nix::Error::last_raw()) - }); - if res == 0 { - break; - } - if err != libc::EINTR { - return Err( - vm.new_os_error(format!("nanosleep: {}", nix::Error::from_raw(err))) - ); + let sleep_result = vm.allow_threads(|| host_time::nanosleep(remaining)); + match sleep_result { + Ok(()) => break, + Err(err) if err.raw_os_error() == Some(libc::EINTR) => {} + Err(err) => { + let errno = err.raw_os_error().unwrap_or(0); + return Err(vm.new_os_error(format!( + "nanosleep: {}", + host_time::nix_errno_display(errno) + ))); + } } // EINTR: run signal handlers, then retry with remaining time vm.check_signals()?; @@ -217,7 +205,7 @@ pub mod decl { #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] - pub(super) fn get_tz_info() -> TIME_ZONE_INFORMATION { + pub(super) fn get_tz_info() -> host_time::WindowsTimeZoneInfo { host_time::get_tz_info() } @@ -240,7 +228,7 @@ pub mod decl { fn altzone(_vm: &VirtualMachine) -> i32 { let info = get_tz_info(); // https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3 - (info.Bias + info.StandardBias) * 60 - 3600 + (info.bias + info.standard_bias) * 60 - 3600 } #[cfg(not(target_env = "msvc"))] @@ -256,7 +244,7 @@ pub mod decl { fn timezone(_vm: &VirtualMachine) -> i32 { let info = get_tz_info(); // https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3 - (info.Bias + info.StandardBias) * 60 + (info.bias + info.standard_bias) * 60 } #[cfg(not(target_os = "freebsd"))] @@ -273,7 +261,7 @@ pub mod decl { fn daylight(_vm: &VirtualMachine) -> i32 { let info = get_tz_info(); // https://users.rust-lang.org/t/accessing-tzname-and-similar-constants-in-windows/125771/3 - (info.StandardBias != info.DaylightBias) as i32 + (info.standard_bias != info.daylight_bias) as i32 } #[cfg(not(target_env = "msvc"))] @@ -296,13 +284,7 @@ pub mod decl { fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef { use crate::builtins::tuple::IntoPyTuple; let info = get_tz_info(); - let standard = widestring::decode_utf16_lossy(info.StandardName) - .take_while(|&c| c != '\0') - .collect::(); - let daylight = widestring::decode_utf16_lossy(info.DaylightName) - .take_while(|&c| c != '\0') - .collect::(); - let tz_name = (&*standard, &*daylight); + let tz_name = (&*info.standard_name, &*info.daylight_name); tz_name.into_pytuple(vm) } @@ -337,19 +319,12 @@ pub mod decl { } } - #[cfg(any(unix, windows))] - struct CheckedTm { - tm: libc::tm, - #[cfg(unix)] - zone: Option, - } - #[cfg(any(unix, windows))] fn checked_tm_from_struct_time( t: &StructTimeData, vm: &VirtualMachine, func_name: &'static str, - ) -> PyResult { + ) -> PyResult { let invalid_tuple = || vm.new_type_error(format!("{func_name}(): illegal time tuple argument")); let classify_err = |e: PyBaseExceptionRef| { @@ -400,45 +375,6 @@ pub mod decl { .try_into_value(vm) .map_err(classify_err)?; - let mut tm: libc::tm = unsafe { core::mem::zeroed() }; - tm.tm_year = year - 1900; - tm.tm_mon = tm_mon; - tm.tm_mday = tm_mday; - tm.tm_hour = tm_hour; - tm.tm_min = tm_min; - tm.tm_sec = tm_sec; - tm.tm_wday = tm_wday; - tm.tm_yday = tm_yday; - tm.tm_isdst = tm_isdst; - - if tm.tm_mon == -1 { - tm.tm_mon = 0; - } else if tm.tm_mon < 0 || tm.tm_mon > 11 { - return Err(vm.new_value_error("month out of range")); - } - if tm.tm_mday == 0 { - tm.tm_mday = 1; - } else if tm.tm_mday < 0 || tm.tm_mday > 31 { - return Err(vm.new_value_error("day of month out of range")); - } - if tm.tm_hour < 0 || tm.tm_hour > 23 { - return Err(vm.new_value_error("hour out of range")); - } - if tm.tm_min < 0 || tm.tm_min > 59 { - return Err(vm.new_value_error("minute out of range")); - } - if tm.tm_sec < 0 || tm.tm_sec > 61 { - return Err(vm.new_value_error("seconds out of range")); - } - if tm.tm_wday < 0 { - return Err(vm.new_value_error("day of week out of range")); - } - if tm.tm_yday == -1 { - tm.tm_yday = 0; - } else if tm.tm_yday < 0 || tm.tm_yday > 365 { - return Err(vm.new_value_error("day of year out of range")); - } - #[cfg(unix)] { use crate::builtins::PyUtf8StrRef; @@ -450,28 +386,75 @@ pub mod decl { .clone() .try_into_value(vm) .map_err(|_| invalid_tuple())?; + Some(zone.as_str().to_owned()) + }; + let gmtoff = if t.tm_gmtoff.is(&vm.ctx.none) { + None + } else { Some( - CString::new(zone.as_str()) - .map_err(|_| vm.new_value_error("embedded null character"))?, + t.tm_gmtoff + .clone() + .try_into_value::(vm) + .map_err(classify_err)?, ) }; - if let Some(zone) = &zone { - tm.tm_zone = zone.as_ptr().cast_mut(); - } - if !t.tm_gmtoff.is(&vm.ctx.none) { - let gmtoff: i64 = t - .tm_gmtoff - .clone() - .try_into_value(vm) - .map_err(classify_err)?; - tm.tm_gmtoff = gmtoff as _; - } - - Ok(CheckedTm { tm, zone }) + host_time::checked_tm_from_parts(host_time::CheckedTmParts { + year: year.into(), + tm_mon, + tm_mday, + tm_hour, + tm_min, + tm_sec, + tm_wday, + tm_yday, + tm_isdst, + zone, + gmtoff, + }) + .map_err(|err| map_checked_tm_error(vm, err)) } #[cfg(windows)] { - Ok(CheckedTm { tm }) + host_time::checked_tm_from_parts(host_time::CheckedTmParts { + year: year.into(), + tm_mon, + tm_mday, + tm_hour, + tm_min, + tm_sec, + tm_wday, + tm_yday, + tm_isdst, + }) + .map_err(|err| map_checked_tm_error(vm, err)) + } + } + + #[cfg(any(unix, windows))] + fn map_checked_tm_error( + vm: &VirtualMachine, + err: host_time::CheckedTmError, + ) -> PyBaseExceptionRef { + match err { + host_time::CheckedTmError::YearOutOfRange => vm.new_overflow_error("year out of range"), + host_time::CheckedTmError::MonthOutOfRange => vm.new_value_error("month out of range"), + host_time::CheckedTmError::DayOfMonthOutOfRange => { + vm.new_value_error("day of month out of range") + } + host_time::CheckedTmError::HourOutOfRange => vm.new_value_error("hour out of range"), + host_time::CheckedTmError::MinuteOutOfRange => { + vm.new_value_error("minute out of range") + } + host_time::CheckedTmError::SecondsOutOfRange => { + vm.new_value_error("seconds out of range") + } + host_time::CheckedTmError::DayOfWeekOutOfRange => { + vm.new_value_error("day of week out of range") + } + host_time::CheckedTmError::DayOfYearOutOfRange => { + vm.new_value_error("day of year out of range") + } + host_time::CheckedTmError::EmbeddedNul => vm.new_value_error("embedded null character"), } } @@ -605,7 +588,11 @@ pub mod decl { } #[cfg(any(unix, windows))] - fn strftime_crt(format: &PyStrRef, checked_tm: CheckedTm, vm: &VirtualMachine) -> PyResult { + fn strftime_crt( + format: &PyStrRef, + checked_tm: host_time::CheckedTm, + vm: &VirtualMachine, + ) -> PyResult { #[cfg(unix)] let _keep_zone_alive = &checked_tm.zone; let mut tm = checked_tm.tm; @@ -620,62 +607,14 @@ pub mod decl { } } - #[cfg(unix)] - fn strftime_ascii(fmt: &str, tm: &libc::tm, vm: &VirtualMachine) -> PyResult { - let fmt_c = - CString::new(fmt).map_err(|_| vm.new_value_error("embedded null character"))?; - let mut size = 1024usize; - let max_scale = 256usize.saturating_mul(fmt.len().max(1)); - loop { - let mut out = vec![0u8; size]; - let written = unsafe { - libc::strftime( - out.as_mut_ptr().cast(), - out.len(), - fmt_c.as_ptr(), - tm as *const libc::tm, - ) - }; - if written > 0 || size >= max_scale { - return Ok(String::from_utf8_lossy(&out[..written]).into_owned()); - } - size = size.saturating_mul(2); - } - } - - #[cfg(windows)] - fn strftime_ascii(fmt: &str, tm: &libc::tm, vm: &VirtualMachine) -> PyResult { - if fmt.contains('\0') { - return Err(vm.new_value_error("embedded null character")); - } - // Use wcsftime for proper Unicode output (e.g. %Z timezone names) - let fmt_wide: Vec = fmt.encode_utf16().chain(core::iter::once(0)).collect(); - let mut size = 1024usize; - let max_scale = 256usize.saturating_mul(fmt.len().max(1)); - loop { - let mut out = vec![0u16; size]; - let written = unsafe { - rustpython_host_env::suppress_iph!(wcsftime( - out.as_mut_ptr(), - out.len(), - fmt_wide.as_ptr(), - tm as *const libc::tm, - )) - }; - if written > 0 || size >= max_scale { - return Ok(String::from_utf16_lossy(&out[..written])); - } - size = size.saturating_mul(2); - } - } - let mut out = Wtf8Buf::new(); let mut ascii = String::new(); for codepoint in format.as_wtf8().code_points() { if codepoint.to_u32() == 0 { if !ascii.is_empty() { - let part = strftime_ascii(&ascii, &tm, vm)?; + let part = host_time::strftime_ascii(&ascii, &tm) + .map_err(|_| vm.new_value_error("embedded null character"))?; out.extend(part.chars()); ascii.clear(); } @@ -690,14 +629,16 @@ pub mod decl { } if !ascii.is_empty() { - let part = strftime_ascii(&ascii, &tm, vm)?; + let part = host_time::strftime_ascii(&ascii, &tm) + .map_err(|_| vm.new_value_error("embedded null character"))?; out.extend(part.chars()); ascii.clear(); } out.push(codepoint); } if !ascii.is_empty() { - let part = strftime_ascii(&ascii, &tm, vm)?; + let part = host_time::strftime_ascii(&ascii, &tm) + .map_err(|_| vm.new_value_error("embedded null character"))?; out.extend(part.chars()); } Ok(out.to_pyobject(vm)) @@ -786,18 +727,9 @@ pub mod decl { #[cfg(all(target_arch = "wasm32", target_os = "emscripten"))] fn get_process_time(vm: &VirtualMachine) -> PyResult { - let t: libc::tms = unsafe { - let mut t = core::mem::MaybeUninit::uninit(); - if libc::times(t.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - t.assume_init() - }; - let freq = unsafe { libc::sysconf(libc::_SC_CLK_TCK) }; - - Ok(Duration::from_nanos( - time_muldiv(t.tms_utime, SEC_TO_NS, freq) + time_muldiv(t.tms_stime, SEC_TO_NS, freq), - )) + let times = host_time::process_times() + .map_err(|_| vm.new_os_error("Failed to get clock time".to_owned()))?; + Ok(Duration::from_secs_f64(times.user + times.system)) } #[cfg(not(any( @@ -943,31 +875,32 @@ pub mod decl { return Err(vm.new_overflow_error("year out of range")); } - let mut tm: libc::tm = unsafe { core::mem::zeroed() }; - tm.tm_sec = t.tm_sec.clone().try_into_value(vm).map_err(classify_err)?; - tm.tm_min = t.tm_min.clone().try_into_value(vm).map_err(classify_err)?; - tm.tm_hour = t.tm_hour.clone().try_into_value(vm).map_err(classify_err)?; - tm.tm_mday = t.tm_mday.clone().try_into_value(vm).map_err(classify_err)?; - tm.tm_mon = t - .tm_mon - .clone() - .try_into_value::(vm) - .map_err(classify_err)? - - 1; - tm.tm_year = year - 1900; - tm.tm_wday = -1; - tm.tm_yday = t - .tm_yday - .clone() - .try_into_value::(vm) - .map_err(classify_err)? - - 1; - tm.tm_isdst = t - .tm_isdst - .clone() - .try_into_value(vm) - .map_err(classify_err)?; - Ok(tm) + host_time::mktime_tm_from_parts(host_time::MktimeTmParts { + year, + tm_sec: t.tm_sec.clone().try_into_value(vm).map_err(classify_err)?, + tm_min: t.tm_min.clone().try_into_value(vm).map_err(classify_err)?, + tm_hour: t.tm_hour.clone().try_into_value(vm).map_err(classify_err)?, + tm_mday: t.tm_mday.clone().try_into_value(vm).map_err(classify_err)?, + tm_mon: t + .tm_mon + .clone() + .try_into_value::(vm) + .map_err(classify_err)?, + tm_yday: t + .tm_yday + .clone() + .try_into_value::(vm) + .map_err(classify_err)?, + tm_isdst: t + .tm_isdst + .clone() + .try_into_value(vm) + .map_err(classify_err)?, + }) + .map_err(|err| match err { + host_time::CheckedTmError::YearOutOfRange => vm.new_overflow_error("year out of range"), + _ => vm.new_type_error("mktime(): illegal time tuple argument"), + }) } #[cfg(any(unix, windows))] @@ -1030,9 +963,14 @@ mod platform { convert::IntoPyException, }; use core::time::Duration; - #[cfg_attr(target_env = "musl", allow(deprecated))] - use libc::time_t; - use nix::{sys::time::TimeSpec, time::ClockId}; + #[cfg(any( + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + ))] + use rustpython_host_env::resource as host_resource; + use rustpython_host_env::time::{self as host_time, ClockId}; #[cfg(target_os = "solaris")] #[pyattr] @@ -1095,40 +1033,33 @@ mod platform { } } - #[cfg_attr(target_env = "musl", allow(deprecated))] - pub(super) fn current_time_t() -> time_t { - unsafe { libc::time(core::ptr::null_mut()) } + pub(super) fn current_time_t() -> host_time::TimeT { + host_time::current_time_t() } - #[cfg_attr(target_env = "musl", allow(deprecated))] pub(super) fn gmtime_from_timestamp( - when: time_t, + when: host_time::TimeT, vm: &VirtualMachine, ) -> PyResult { - let mut out = core::mem::MaybeUninit::::uninit(); - let ret = unsafe { libc::gmtime_r(&when, out.as_mut_ptr()) }; - if ret.is_null() { + let Some(tm) = host_time::gmtime_from_timestamp(when) else { return Err(vm.new_overflow_error("timestamp out of range for platform time_t")); - } - Ok(struct_time_from_tm(vm, unsafe { out.assume_init() })) + }; + Ok(struct_time_from_tm(vm, tm)) } - #[cfg_attr(target_env = "musl", allow(deprecated))] pub(super) fn localtime_from_timestamp( - when: time_t, + when: host_time::TimeT, vm: &VirtualMachine, ) -> PyResult { - let mut out = core::mem::MaybeUninit::::uninit(); - let ret = unsafe { libc::localtime_r(&when, out.as_mut_ptr()) }; - if ret.is_null() { + let Some(tm) = host_time::localtime_from_timestamp(when) else { return Err(vm.new_overflow_error("timestamp out of range for platform time_t")); - } - Ok(struct_time_from_tm(vm, unsafe { out.assume_init() })) + }; + Ok(struct_time_from_tm(vm, tm)) } pub(super) fn unix_mktime(t: &StructTimeData, vm: &VirtualMachine) -> PyResult { let mut tm = super::decl::tm_from_struct_time(t, vm)?; - let timestamp = unsafe { libc::mktime(&mut tm) }; + let timestamp = host_time::mktime(&mut tm); if timestamp == -1 && tm.tm_wday == -1 { return Err(vm.new_overflow_error("mktime argument out of range")); } @@ -1136,8 +1067,7 @@ mod platform { } fn get_clock_time(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { - let ts = nix::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm))?; - Ok(ts.into()) + rustpython_host_env::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] @@ -1153,23 +1083,8 @@ mod platform { #[cfg(not(target_os = "redox"))] #[pyfunction] fn clock_getres(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { - let ts = nix::time::clock_getres(clk_id).map_err(|e| e.into_pyexception(vm))?; - Ok(Duration::from(ts).as_secs_f64()) - } - - #[cfg(not(target_os = "redox"))] - #[cfg(not(target_vendor = "apple"))] - fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> { - nix::time::clock_settime(clk_id, timespec).map_err(|e| e.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[cfg(target_os = "macos")] - fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> { - // idk why nix disables clock_settime on macos - let ret = unsafe { libc::clock_settime(clk_id.as_raw(), timespec.as_ref()) }; - nix::Error::result(ret) - .map(drop) + rustpython_host_env::time::clock_getres(clk_id) + .map(|d| d.as_secs_f64()) .map_err(|e| e.into_pyexception(vm)) } @@ -1177,7 +1092,7 @@ mod platform { #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))] #[pyfunction] fn clock_settime(clk_id: ClockId, time: Duration, vm: &VirtualMachine) -> PyResult<()> { - set_clock_time(clk_id, time.into(), vm) + rustpython_host_env::time::clock_settime(clk_id, time).map_err(|e| e.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] @@ -1185,8 +1100,8 @@ mod platform { #[cfg_attr(target_env = "musl", allow(deprecated))] #[pyfunction] fn clock_settime_ns(clk_id: ClockId, time: libc::time_t, vm: &VirtualMachine) -> PyResult<()> { - let ts = Duration::from_nanos(time as _).into(); - set_clock_time(clk_id, ts, vm) + rustpython_host_env::time::clock_settime(clk_id, Duration::from_nanos(time as _)) + .map_err(|e| e.into_pyexception(vm)) } // Requires all CLOCK constants available and clock_getres @@ -1271,7 +1186,8 @@ mod platform { #[cfg(target_os = "solaris")] pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult { - Ok(Duration::from_nanos(unsafe { libc::gethrvtime() })) + let _ = vm; + Ok(host_time::gethrvtime_duration()) } #[cfg(not(any( @@ -1291,7 +1207,6 @@ mod platform { target_os = "openbsd", ))] pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - use nix::sys::resource::{UsageWho, getrusage}; fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult { (|tv: libc::timeval| { let t = tv.tv_sec.checked_mul(SEC_TO_NS)?; @@ -1300,9 +1215,9 @@ mod platform { })(tv) .ok_or_else(|| vm.new_overflow_error("timestamp too large to convert to i64")) } - let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?; - let utime = from_timeval(ru.user_time().into(), vm)?; - let stime = from_timeval(ru.system_time().into(), vm)?; + let ru = host_resource::getrusage(libc::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?; + let utime = from_timeval(ru.ru_utime, vm)?; + let stime = from_timeval(ru.ru_stime, vm)?; Ok(Duration::from_nanos((utime + stime) as u64)) } @@ -1317,19 +1232,7 @@ mod platform { builtins::{PyNamespace, PyUtf8StrRef}, }; use core::time::Duration; - use windows_sys::Win32::{ - Foundation::FILETIME, - System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency}, - System::SystemInformation::{GetSystemTimeAdjustment, GetTickCount64}, - System::Threading::{GetCurrentProcess, GetCurrentThread, GetProcessTimes, GetThreadTimes}, - }; - - unsafe extern "C" { - fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int; - fn _localtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int; - #[link_name = "_mktime64"] - fn c_mktime(tm: *mut libc::tm) -> libc::time_t; - } + use rustpython_host_env::time as host_time; fn struct_time_from_tm( vm: &VirtualMachine, @@ -1352,80 +1255,51 @@ mod platform { } } - #[cfg_attr(target_env = "musl", allow(deprecated))] - pub(super) fn current_time_t() -> libc::time_t { - unsafe { libc::time(core::ptr::null_mut()) } + pub(super) fn current_time_t() -> host_time::TimeT { + host_time::current_time_t() } - #[cfg_attr(target_env = "musl", allow(deprecated))] pub(super) fn gmtime_from_timestamp( - when: libc::time_t, + when: host_time::TimeT, vm: &VirtualMachine, ) -> PyResult { - let mut out = core::mem::MaybeUninit::::uninit(); - let err = unsafe { _gmtime64_s(out.as_mut_ptr(), &when) }; - if err != 0 { - return Err(vm.new_overflow_error("timestamp out of range for platform time_t")); - } - Ok(struct_time_from_tm( - vm, - unsafe { out.assume_init() }, - "UTC", - 0, - )) + let tm = host_time::gmtime_from_timestamp(when) + .ok_or_else(|| vm.new_overflow_error("timestamp out of range for platform time_t"))?; + Ok(struct_time_from_tm(vm, tm, "UTC", 0)) } - #[cfg_attr(target_env = "musl", allow(deprecated))] pub(super) fn localtime_from_timestamp( - when: libc::time_t, + when: host_time::TimeT, vm: &VirtualMachine, ) -> PyResult { - let mut out = core::mem::MaybeUninit::::uninit(); - let err = unsafe { _localtime64_s(out.as_mut_ptr(), &when) }; - if err != 0 { - return Err(vm.new_overflow_error("timestamp out of range for platform time_t")); - } - let tm = unsafe { out.assume_init() }; + let tm = host_time::localtime_from_timestamp(when) + .ok_or_else(|| vm.new_overflow_error("timestamp out of range for platform time_t"))?; // Get timezone info from Windows API let info = get_tz_info(); let (bias, name) = if tm.tm_isdst > 0 { - (info.DaylightBias, &info.DaylightName) + (info.daylight_bias, &info.daylight_name) } else { - (info.StandardBias, &info.StandardName) + (info.standard_bias, &info.standard_name) }; - let zone = widestring::decode_utf16_lossy(name.iter().copied()) - .take_while(|&c| c != '\0') - .collect::(); - #[allow(clippy::unnecessary_cast, reason = "info.Bias is not always i32")] - let gmtoff = -((info.Bias + bias) as i32) * 60; + let gmtoff = -(info.bias + bias) * 60; - Ok(struct_time_from_tm(vm, tm, &zone, gmtoff)) + Ok(struct_time_from_tm(vm, tm, name, gmtoff)) } pub(super) fn win_mktime(t: &StructTimeData, vm: &VirtualMachine) -> PyResult { let mut tm = super::decl::tm_from_struct_time(t, vm)?; - let timestamp = unsafe { rustpython_host_env::suppress_iph!(c_mktime(&mut tm)) }; + let timestamp = host_time::mktime(&mut tm); if timestamp == -1 && tm.tm_wday == -1 { return Err(vm.new_overflow_error("mktime argument out of range")); } Ok(timestamp as f64) } - fn u64_from_filetime(time: FILETIME) -> u64 { - let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime]; - unsafe { core::mem::transmute(large) } - } - fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult { - let frequency = unsafe { - let mut freq = core::mem::MaybeUninit::uninit(); - if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 { - return Err(vm.new_last_os_error()); - } - freq.assume_init() - }; + let frequency = + host_time::query_performance_frequency().ok_or_else(|| vm.new_last_os_error())?; if frequency < 1 { Err(vm.new_runtime_error("invalid QueryPerformanceFrequency")) @@ -1446,11 +1320,7 @@ mod platform { } pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult { - let ticks = unsafe { - let mut performance_count = core::mem::MaybeUninit::uninit(); - QueryPerformanceCounter(performance_count.as_mut_ptr()); - performance_count.assume_init() - }; + let ticks = host_time::query_performance_counter(); Ok(Duration::from_nanos(time_muldiv( ticks, @@ -1460,25 +1330,11 @@ mod platform { } fn get_system_time_adjustment(vm: &VirtualMachine) -> PyResult { - let mut _time_adjustment = core::mem::MaybeUninit::uninit(); - let mut time_increment = core::mem::MaybeUninit::uninit(); - let mut _is_time_adjustment_disabled = core::mem::MaybeUninit::uninit(); - let time_increment = unsafe { - if GetSystemTimeAdjustment( - _time_adjustment.as_mut_ptr(), - time_increment.as_mut_ptr(), - _is_time_adjustment_disabled.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_last_os_error()); - } - time_increment.assume_init() - }; - Ok(time_increment) + host_time::get_system_time_adjustment().ok_or_else(|| vm.new_last_os_error()) } pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult { - let ticks = unsafe { GetTickCount64() }; + let ticks = host_time::tick_count64(); Ok(Duration::from_nanos( (ticks as i64) @@ -1523,52 +1379,14 @@ mod platform { } pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult { - let (kernel_time, user_time) = unsafe { - let mut _creation_time = core::mem::MaybeUninit::uninit(); - let mut _exit_time = core::mem::MaybeUninit::uninit(); - let mut kernel_time = core::mem::MaybeUninit::uninit(); - let mut user_time = core::mem::MaybeUninit::uninit(); - - let thread = GetCurrentThread(); - if GetThreadTimes( - thread, - _creation_time.as_mut_ptr(), - _exit_time.as_mut_ptr(), - kernel_time.as_mut_ptr(), - user_time.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - (kernel_time.assume_init(), user_time.assume_init()) - }; - let k_time = u64_from_filetime(kernel_time); - let u_time = u64_from_filetime(user_time); - Ok(Duration::from_nanos((k_time + u_time) * 100)) + let total = host_time::get_thread_time_100ns() + .ok_or_else(|| vm.new_os_error("Failed to get clock time".to_owned()))?; + Ok(Duration::from_nanos(total * 100)) } pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - let (kernel_time, user_time) = unsafe { - let mut _creation_time = core::mem::MaybeUninit::uninit(); - let mut _exit_time = core::mem::MaybeUninit::uninit(); - let mut kernel_time = core::mem::MaybeUninit::uninit(); - let mut user_time = core::mem::MaybeUninit::uninit(); - - let process = GetCurrentProcess(); - if GetProcessTimes( - process, - _creation_time.as_mut_ptr(), - _exit_time.as_mut_ptr(), - kernel_time.as_mut_ptr(), - user_time.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - (kernel_time.assume_init(), user_time.assume_init()) - }; - let k_time = u64_from_filetime(kernel_time); - let u_time = u64_from_filetime(user_time); - Ok(Duration::from_nanos((k_time + u_time) * 100)) + let total = host_time::get_process_time_100ns() + .ok_or_else(|| vm.new_os_error("Failed to get clock time".to_owned()))?; + Ok(Duration::from_nanos(total * 100)) } } diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index 4e0725141d8..fcaaf927a72 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -18,24 +18,10 @@ mod winreg { use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::Sign; use num_traits::ToPrimitive; - use windows_sys::Win32::Foundation::{self, ERROR_MORE_DATA}; - use windows_sys::Win32::System::Registry; + use rustpython_host_env::winreg as host_winreg; /// Atomic HKEY handle type for lock-free thread-safe access - type AtomicHKEY = AtomicCell; - - /// Convert byte slice to UTF-16 slice (zero-copy when aligned) - fn bytes_as_wide_slice(bytes: &[u8]) -> &[u16] { - // SAFETY: Windows Registry API returns properly aligned UTF-16 data. - // align_to handles any edge cases safely by returning empty prefix/suffix - // if alignment doesn't match. - let (prefix, u16_slice, suffix) = unsafe { bytes.align_to::() }; - debug_assert!( - prefix.is_empty() && suffix.is_empty(), - "Registry data should be u16-aligned" - ); - u16_slice - } + type AtomicHKEY = AtomicCell; fn os_error_from_windows_code( vm: &VirtualMachine, @@ -46,7 +32,7 @@ mod winreg { } /// Wrapper type for HKEY that can be created from PyHkey or int - struct HKEYArg(Registry::HKEY); + struct HKEYArg(host_winreg::HKEY); impl TryFromObject for HKEYArg { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { @@ -56,21 +42,20 @@ mod winreg { } // Then try int let handle = usize::try_from_object(vm, obj)?; - Ok(Self(handle as Registry::HKEY)) + Ok(Self(handle as host_winreg::HKEY)) } } // access rights #[pyattr] - pub(super) use windows_sys::Win32::System::Registry::{ + pub(super) use host_winreg::{ KEY_ALL_ACCESS, KEY_CREATE_LINK, KEY_CREATE_SUB_KEY, KEY_ENUMERATE_SUB_KEYS, KEY_EXECUTE, KEY_NOTIFY, KEY_QUERY_VALUE, KEY_READ, KEY_SET_VALUE, KEY_WOW64_32KEY, KEY_WOW64_64KEY, KEY_WRITE, }; - // value types #[pyattr] - pub(super) use windows_sys::Win32::System::Registry::{ + pub(super) use host_winreg::{ REG_BINARY, REG_CREATED_NEW_KEY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_DWORD_LITTLE_ENDIAN, REG_EXPAND_SZ, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE, REG_NOTIFY_CHANGE_ATTRIBUTES, REG_NOTIFY_CHANGE_LAST_SET, REG_NOTIFY_CHANGE_NAME, @@ -80,25 +65,14 @@ mod winreg { REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ, REG_WHOLE_HIVE_VOLATILE, }; - // Additional constants not in windows-sys #[pyattr] - const REG_REFRESH_HIVE: u32 = 0x00000002; + const REG_REFRESH_HIVE: u32 = host_winreg::REG_REFRESH_HIVE; #[pyattr] - const REG_NO_LAZY_FLUSH: u32 = 0x00000004; - // REG_LEGAL_OPTION is a mask of all option flags + const REG_NO_LAZY_FLUSH: u32 = host_winreg::REG_NO_LAZY_FLUSH; #[pyattr] - const REG_LEGAL_OPTION: u32 = Registry::REG_OPTION_RESERVED - | Registry::REG_OPTION_NON_VOLATILE - | Registry::REG_OPTION_VOLATILE - | Registry::REG_OPTION_CREATE_LINK - | Registry::REG_OPTION_BACKUP_RESTORE - | Registry::REG_OPTION_OPEN_LINK; - // REG_LEGAL_CHANGE_FILTER is a mask of all notify flags + const REG_LEGAL_OPTION: u32 = host_winreg::REG_LEGAL_OPTION; #[pyattr] - const REG_LEGAL_CHANGE_FILTER: u32 = Registry::REG_NOTIFY_CHANGE_NAME - | Registry::REG_NOTIFY_CHANGE_ATTRIBUTES - | Registry::REG_NOTIFY_CHANGE_LAST_SET - | Registry::REG_NOTIFY_CHANGE_SECURITY; + const REG_LEGAL_CHANGE_FILTER: u32 = host_winreg::REG_LEGAL_CHANGE_FILTER; // error is an alias for OSError (for backwards compatibility) #[pyattr] @@ -108,37 +82,37 @@ mod winreg { #[pyattr(once)] fn HKEY_CLASSES_ROOT(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_CLASSES_ROOT).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_CLASSES_ROOT).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_CURRENT_USER(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_CURRENT_USER).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_CURRENT_USER).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_LOCAL_MACHINE(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_LOCAL_MACHINE).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_LOCAL_MACHINE).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_USERS(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_USERS).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_USERS).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_PERFORMANCE_DATA(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_PERFORMANCE_DATA).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_PERFORMANCE_DATA).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_CURRENT_CONFIG(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_CURRENT_CONFIG).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_CURRENT_CONFIG).into_ref(&vm.ctx) } #[pyattr(once)] fn HKEY_DYN_DATA(vm: &VirtualMachine) -> PyRef { - PyHkey::new(Registry::HKEY_DYN_DATA).into_ref(&vm.ctx) + PyHkey::new(host_winreg::HKEY_DYN_DATA).into_ref(&vm.ctx) } #[pyattr] @@ -152,7 +126,7 @@ mod winreg { unsafe impl Sync for PyHkey {} impl PyHkey { - fn new(hkey: Registry::HKEY) -> Self { + fn new(hkey: host_winreg::HKEY) -> Self { Self { hkey: AtomicHKEY::new(hkey), } @@ -186,7 +160,7 @@ mod winreg { if old_hkey.is_null() { return Ok(()); } - let res = unsafe { Registry::RegCloseKey(old_hkey) }; + let res = host_winreg::close_key(old_hkey); if res == 0 { Ok(()) } else { @@ -225,7 +199,7 @@ mod winreg { fn drop(&mut self) { let hkey = self.hkey.swap(core::ptr::null_mut()); if !hkey.is_null() { - unsafe { Registry::RegCloseKey(hkey) }; + host_winreg::close_key(hkey); } } } @@ -237,7 +211,7 @@ mod winreg { } } - pub(super) const HKEY_ERR_MSG: &str = "bad operand type"; + const HKEY_ERR_MSG: &str = "bad operand type"; impl AsNumber for PyHkey { fn as_number() -> &'static PyNumberMethods { @@ -286,7 +260,7 @@ mod winreg { let mut ret_key = core::ptr::null_mut(); let wide_computer_name = computer_name.to_wide_with_nul(); let res = unsafe { - Registry::RegConnectRegistryW( + host_winreg::connect_registry( wide_computer_name.as_ptr(), key.hkey.load(), &mut ret_key, @@ -300,7 +274,7 @@ mod winreg { } else { let mut ret_key = core::ptr::null_mut(); let res = unsafe { - Registry::RegConnectRegistryW(core::ptr::null_mut(), key.hkey.load(), &mut ret_key) + host_winreg::connect_registry(core::ptr::null_mut(), key.hkey.load(), &mut ret_key) }; if res == 0 { Ok(PyHkey::new(ret_key)) @@ -315,7 +289,7 @@ mod winreg { let wide_sub_key = sub_key.to_wide_with_nul(); let mut out_key = core::ptr::null_mut(); let res = unsafe { - Registry::RegCreateKeyW(key.hkey.load(), wide_sub_key.as_ptr(), &mut out_key) + host_winreg::create_key(key.hkey.load(), wide_sub_key.as_ptr(), &mut out_key) }; if res == 0 { Ok(PyHkey::new(out_key)) @@ -332,22 +306,22 @@ mod winreg { sub_key: String, #[pyarg(any, default = 0)] reserved: u32, - #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WRITE)] + #[pyarg(any, default = host_winreg::KEY_WRITE)] access: u32, } #[pyfunction] fn CreateKeyEx(args: CreateKeyExArgs, vm: &VirtualMachine) -> PyResult { let wide_sub_key = args.sub_key.to_wide_with_nul(); - let mut res: Registry::HKEY = core::ptr::null_mut(); + let mut res: host_winreg::HKEY = core::ptr::null_mut(); let err = unsafe { let key = args.key.hkey.load(); - Registry::RegCreateKeyExW( + host_winreg::create_key_ex( key, wide_sub_key.as_ptr(), args.reserved, - core::ptr::null(), - Registry::REG_OPTION_NON_VOLATILE, + core::ptr::null_mut(), + host_winreg::REG_OPTION_NON_VOLATILE, args.access, core::ptr::null(), &mut res, @@ -372,7 +346,7 @@ mod winreg { #[pyfunction] fn DeleteKey(key: PyRef, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { let wide_sub_key = sub_key.to_wide_with_nul(); - let res = unsafe { Registry::RegDeleteKeyW(key.hkey.load(), wide_sub_key.as_ptr()) }; + let res = unsafe { host_winreg::delete_key(key.hkey.load(), wide_sub_key.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -386,7 +360,7 @@ mod winreg { let value_ptr = wide_value .as_ref() .map_or(core::ptr::null(), |v| v.as_ptr()); - let res = unsafe { Registry::RegDeleteValueW(key.hkey.load(), value_ptr) }; + let res = unsafe { host_winreg::delete_value(key.hkey.load(), value_ptr) }; if res == 0 { Ok(()) } else { @@ -400,7 +374,7 @@ mod winreg { key: PyRef, #[pyarg(any)] sub_key: String, - #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WOW64_64KEY)] + #[pyarg(any, default = host_winreg::KEY_WOW64_64KEY)] access: u32, #[pyarg(any, default = 0)] reserved: u32, @@ -410,7 +384,7 @@ mod winreg { fn DeleteKeyEx(args: DeleteKeyExArgs, vm: &VirtualMachine) -> PyResult<()> { let wide_sub_key = args.sub_key.to_wide_with_nul(); let res = unsafe { - Registry::RegDeleteKeyExW( + host_winreg::delete_key_ex( args.key.hkey.load(), wide_sub_key.as_ptr(), args.access, @@ -435,16 +409,7 @@ mod winreg { let mut tmpbuf = [0u16; 257]; let mut len = tmpbuf.len() as u32; let res = unsafe { - Registry::RegEnumKeyExW( - key.hkey.load(), - index as u32, - tmpbuf.as_mut_ptr(), - &mut len, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - ) + host_winreg::enum_key_ex(key.hkey.load(), index as u32, tmpbuf.as_mut_ptr(), &mut len) }; if res != 0 { return Err(os_error_from_windows_code(vm, res as i32)); @@ -458,21 +423,14 @@ mod winreg { // Query registry for the required buffer sizes. let mut ret_value_size: u32 = 0; let mut ret_data_size: u32 = 0; - let hkey: Registry::HKEY = hkey.hkey.load(); + let hkey: host_winreg::HKEY = hkey.hkey.load(); let rc = unsafe { - Registry::RegQueryInfoKeyW( + host_winreg::query_info_key( hkey, ptr::null_mut(), ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), &mut ret_value_size as *mut u32, &mut ret_data_size as *mut u32, - ptr::null_mut(), - ptr::null_mut(), ) }; if rc != 0 { @@ -495,18 +453,17 @@ mod winreg { let mut current_data_size = ret_data_size; let mut reg_type: u32 = 0; let rc = unsafe { - Registry::RegEnumValueW( + host_winreg::enum_value( hkey, index, ret_value_buf.as_mut_ptr(), &mut current_value_size as *mut u32, - ptr::null_mut(), &mut reg_type as *mut u32, ret_data_buf.as_mut_ptr(), &mut current_data_size as *mut u32, ) }; - if rc == ERROR_MORE_DATA { + if rc == host_winreg::ERROR_MORE_DATA { // Double the buffer sizes. buf_data_size *= 2; buf_value_size *= 2; @@ -547,7 +504,7 @@ mod winreg { #[pyfunction] fn FlushKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegFlushKey(key.hkey.load()) }; + let res = host_winreg::flush_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -565,7 +522,7 @@ mod winreg { let sub_key = sub_key.to_wide_with_nul(); let file_name = file_name.to_wide_with_nul(); let res = - unsafe { Registry::RegLoadKeyW(key.hkey.load(), sub_key.as_ptr(), file_name.as_ptr()) }; + unsafe { host_winreg::load_key(key.hkey.load(), sub_key.as_ptr(), file_name.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -581,7 +538,7 @@ mod winreg { sub_key: String, #[pyarg(any, default = 0)] reserved: u32, - #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_READ)] + #[pyarg(any, default = host_winreg::KEY_READ)] access: u32, } @@ -589,10 +546,10 @@ mod winreg { #[pyfunction(name = "OpenKeyEx")] fn OpenKey(args: OpenKeyArgs, vm: &VirtualMachine) -> PyResult { let wide_sub_key = args.sub_key.to_wide_with_nul(); - let mut res: Registry::HKEY = core::ptr::null_mut(); + let mut res: host_winreg::HKEY = core::ptr::null_mut(); let err = unsafe { let key = args.key.hkey.load(); - Registry::RegOpenKeyExW( + host_winreg::open_key_ex( key, wide_sub_key.as_ptr(), args.reserved, @@ -613,35 +570,12 @@ mod winreg { #[pyfunction] fn QueryInfoKey(key: HKEYArg, vm: &VirtualMachine) -> PyResult> { let key = key.0; - let mut lpcsubkeys: u32 = 0; - let mut lpcvalues: u32 = 0; - let mut lpftlastwritetime: Foundation::FILETIME = unsafe { core::mem::zeroed() }; - let err = unsafe { - Registry::RegQueryInfoKeyW( - key, - core::ptr::null_mut(), - core::ptr::null_mut(), - 0 as _, - &mut lpcsubkeys, - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut lpcvalues, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut lpftlastwritetime, - ) - }; - - if err != 0 { - return Err(vm.new_os_error(format!("error code: {err}"))); - } - let l: u64 = (lpftlastwritetime.dwHighDateTime as u64) << 32 - | lpftlastwritetime.dwLowDateTime as u64; + let info = host_winreg::query_info_key_full(key) + .map_err(|err| vm.new_os_error(format!("error code: {err}")))?; let tup: Vec = vec![ - vm.ctx.new_int(lpcsubkeys).into(), - vm.ctx.new_int(lpcvalues).into(), - vm.ctx.new_int(l).into(), + vm.ctx.new_int(info.sub_keys).into(), + vm.ctx.new_int(info.values).into(), + vm.ctx.new_int(info.last_write_time).into(), ]; Ok(vm.ctx.new_tuple(tup)) } @@ -650,147 +584,30 @@ mod winreg { fn QueryValue(key: HKEYArg, sub_key: Option, vm: &VirtualMachine) -> PyResult { let hkey = key.0; - if hkey == Registry::HKEY_PERFORMANCE_DATA { + if hkey == host_winreg::HKEY_PERFORMANCE_DATA { return Err(os_error_from_windows_code( vm, - Foundation::ERROR_INVALID_HANDLE as i32, + host_winreg::ERROR_INVALID_HANDLE as i32, )); } - // Open subkey if provided and non-empty - let child_key = if let Some(ref sk) = sub_key { - if !sk.is_empty() { - let wide_sub_key = sk.to_wide_with_nul(); - let mut out_key = core::ptr::null_mut(); - let res = unsafe { - Registry::RegOpenKeyExW( - hkey, - wide_sub_key.as_ptr(), - 0, - Registry::KEY_QUERY_VALUE, - &mut out_key, - ) - }; - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); + host_winreg::query_default_value(hkey, sub_key.as_deref().map(std::ffi::OsStr::new)) + .map_err(|err| match err { + host_winreg::QueryStringError::Code(err) => { + os_error_from_windows_code(vm, err as i32) } - Some(out_key) - } else { - None - } - } else { - None - }; - - let target_key = child_key.unwrap_or(hkey); - let mut buf_size: u32 = 256; - let mut buffer: Vec = vec![0; buf_size as usize]; - let mut reg_type: u32 = 0; - - // Loop to handle ERROR_MORE_DATA - let result = loop { - let mut size = buf_size; - let res = unsafe { - Registry::RegQueryValueExW( - target_key, - core::ptr::null(), // NULL value name for default value - core::ptr::null_mut(), - &mut reg_type, - buffer.as_mut_ptr(), - &mut size, - ) - }; - if res == ERROR_MORE_DATA { - buf_size *= 2; - buffer.resize(buf_size as usize, 0); - continue; - } - if res == Foundation::ERROR_FILE_NOT_FOUND { - // Return empty string if there's no default value - break Ok(String::new()); - } - if res != 0 { - break Err(os_error_from_windows_code(vm, res as i32)); - } - if reg_type != Registry::REG_SZ { - break Err(os_error_from_windows_code( - vm, - Foundation::ERROR_INVALID_DATA as i32, - )); - } - - // Convert UTF-16 to String - let u16_slice = bytes_as_wide_slice(&buffer[..size as usize]); - let len = u16_slice - .iter() - .position(|&c| c == 0) - .unwrap_or(u16_slice.len()); - break String::from_utf16(&u16_slice[..len]) - .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))); - }; - - // Close child key if we opened one - if let Some(ck) = child_key { - unsafe { Registry::RegCloseKey(ck) }; - } - - result + host_winreg::QueryStringError::Utf16(e) => { + vm.new_value_error(format!("UTF16 error: {e}")) + } + }) } #[pyfunction] fn QueryValueEx(key: HKEYArg, name: String, vm: &VirtualMachine) -> PyResult> { let hkey = key.0; - let wide_name = name.to_wide_with_nul(); - let mut buf_size: u32 = 0; - let res = unsafe { - Registry::RegQueryValueExW( - hkey, - wide_name.as_ptr(), - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut buf_size, - ) - }; - // Handle ERROR_MORE_DATA by using a default buffer size - if res == ERROR_MORE_DATA || buf_size == 0 { - buf_size = 256; - } else if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - - let mut ret_buf = vec![0u8; buf_size as usize]; - let mut typ = 0; - let mut ret_size: u32; - - // Loop to handle ERROR_MORE_DATA - loop { - ret_size = buf_size; - let res = unsafe { - Registry::RegQueryValueExW( - hkey, - wide_name.as_ptr(), - core::ptr::null_mut(), - &mut typ, - ret_buf.as_mut_ptr(), - &mut ret_size, - ) - }; - - if res != ERROR_MORE_DATA { - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - break; - } - - // Double buffer size and retry - buf_size *= 2; - ret_buf.resize(buf_size as usize, 0); - } - - // Only pass the bytes actually returned by the API - let obj = reg_to_py(vm, &ret_buf[..ret_size as usize], typ)?; + let (ret_buf, typ) = host_winreg::query_value_bytes(hkey, std::ffi::OsStr::new(&name)) + .map_err(|err| os_error_from_windows_code(vm, err as i32))?; + let obj = reg_to_py(vm, &ret_buf, typ)?; // Return tuple (value, type) Ok(vm.ctx.new_tuple(vec![obj, vm.ctx.new_int(typ).into()])) } @@ -798,9 +615,7 @@ mod winreg { #[pyfunction] fn SaveKey(key: PyRef, file_name: String, vm: &VirtualMachine) -> PyResult<()> { let file_name = file_name.to_wide_with_nul(); - let res = unsafe { - Registry::RegSaveKeyW(key.hkey.load(), file_name.as_ptr(), core::ptr::null_mut()) - }; + let res = unsafe { host_winreg::save_key(key.hkey.load(), file_name.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -816,61 +631,24 @@ mod winreg { value: String, vm: &VirtualMachine, ) -> PyResult<()> { - if typ != Registry::REG_SZ { + if typ != host_winreg::REG_SZ { return Err(vm.new_type_error("type must be winreg.REG_SZ")); } let hkey = key.hkey.load(); - if hkey == Registry::HKEY_PERFORMANCE_DATA { + if hkey == host_winreg::HKEY_PERFORMANCE_DATA { return Err(os_error_from_windows_code( vm, - Foundation::ERROR_INVALID_HANDLE as i32, + host_winreg::ERROR_INVALID_HANDLE as i32, )); } - // Create subkey if sub_key is non-empty - let child_key = if !sub_key.is_empty() { - let wide_sub_key = sub_key.to_wide_with_nul(); - let mut out_key = core::ptr::null_mut(); - let res = unsafe { - Registry::RegCreateKeyExW( - hkey, - wide_sub_key.as_ptr(), - 0, - core::ptr::null(), - 0, - Registry::KEY_SET_VALUE, - core::ptr::null(), - &mut out_key, - core::ptr::null_mut(), - ) - }; - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - Some(out_key) - } else { - None - }; - - let target_key = child_key.unwrap_or(hkey); - // Convert value to UTF-16 for Wide API - let wide_value = value.to_wide_with_nul(); - let res = unsafe { - Registry::RegSetValueExW( - target_key, - core::ptr::null(), // value name is NULL - 0, - typ, - wide_value.as_ptr() as *const u8, - (wide_value.len() * 2) as u32, // byte count - ) - }; - - // Close child key if we created one - if let Some(ck) = child_key { - unsafe { Registry::RegCloseKey(ck) }; - } + let res = host_winreg::set_default_value( + hkey, + std::ffi::OsStr::new(&sub_key), + typ, + std::ffi::OsStr::new(&value), + ); if res == 0 { Ok(()) @@ -897,7 +675,7 @@ mod winreg { Ok(vm.ctx.new_int(val).into()) } REG_SZ | REG_EXPAND_SZ => { - let u16_slice = bytes_as_wide_slice(ret_data); + let u16_slice = host_winreg::bytes_as_wide_slice(ret_data); // Only use characters up to the first NUL. let len = u16_slice .iter() @@ -911,7 +689,7 @@ mod winreg { if ret_data.is_empty() { Ok(vm.ctx.new_list(vec![]).into()) } else { - let u16_slice = bytes_as_wide_slice(ret_data); + let u16_slice = host_winreg::bytes_as_wide_slice(ret_data); let u16_count = u16_slice.len(); // Remove trailing null if present (like countStrings) @@ -1057,7 +835,7 @@ mod winreg { None => (core::ptr::null(), 0), }; let res = - unsafe { Registry::RegSetValueExW(key.hkey.load(), value_name_ptr, 0, typ, ptr, len) }; + unsafe { host_winreg::set_value_ex(key.hkey.load(), value_name_ptr, typ, ptr, len) }; if res != 0 { return Err(os_error_from_windows_code(vm, res as i32)); } @@ -1066,7 +844,7 @@ mod winreg { #[pyfunction] fn DisableReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegDisableReflectionKey(key.hkey.load()) }; + let res = host_winreg::disable_reflection_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -1076,7 +854,7 @@ mod winreg { #[pyfunction] fn EnableReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegEnableReflectionKey(key.hkey.load()) }; + let res = host_winreg::enable_reflection_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -1087,7 +865,7 @@ mod winreg { #[pyfunction] fn QueryReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult { let mut result: i32 = 0; - let res = unsafe { Registry::RegQueryReflectionKey(key.hkey.load(), &mut result) }; + let res = unsafe { host_winreg::query_reflection_key(key.hkey.load(), &mut result) }; if res == 0 { Ok(result != 0) } else { @@ -1097,34 +875,13 @@ mod winreg { #[pyfunction] fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult { - let wide_input = i.to_wide_with_nul(); - - // First call with size=0 to get required buffer size - let required_size = unsafe { - windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( - wide_input.as_ptr(), - core::ptr::null_mut(), - 0, - ) - }; - if required_size == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); - } - - // Allocate buffer with exact size and expand - let mut out = vec![0u16; required_size as usize]; - let r = unsafe { - windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( - wide_input.as_ptr(), - out.as_mut_ptr(), - required_size, - ) - }; - if r == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); - } - - let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); - String::from_utf16(&out[..len]).map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))) + host_winreg::expand_environment_strings(std::ffi::OsStr::new(&i)).map_err(|err| match err { + host_winreg::ExpandEnvironmentStringsError::Os => { + vm.new_os_error("ExpandEnvironmentStringsW failed".to_string()) + } + host_winreg::ExpandEnvironmentStringsError::Utf16(e) => { + vm.new_value_error(format!("UTF16 error: {e}")) + } + }) } } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 44842a866a0..5e82c1b6d0c 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -48,11 +48,6 @@ use core::{ sync::atomic::{AtomicBool, AtomicU64, Ordering}, }; use crossbeam_utils::atomic::AtomicCell; -#[cfg(unix)] -use nix::{ - sys::signal::{SaFlags, SigAction, SigSet, Signal::SIGINT, kill, sigaction}, - unistd::getpid, -}; use std::{ collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, @@ -1446,19 +1441,7 @@ impl VirtualMachine { /// Returns (base, top) where base is the lowest address and top is the highest. #[cfg(all(not(miri), not(target_env = "musl"), windows))] fn get_stack_bounds() -> (usize, usize) { - use windows_sys::Win32::System::Threading::{ - GetCurrentThreadStackLimits, SetThreadStackGuarantee, - }; - let mut low: usize = 0; - let mut high: usize = 0; - unsafe { - GetCurrentThreadStackLimits(&mut low as *mut usize, &mut high as *mut usize); - // Add the guaranteed stack space (reserved for exception handling) - let mut guarantee: u32 = 0; - SetThreadStackGuarantee(&mut guarantee); - low += guarantee as usize; - } - (low, high) + crate::host_env::windows::current_thread_stack_bounds() } /// Get stack boundaries on non-Windows platforms. @@ -2167,15 +2150,10 @@ impl VirtualMachine { self.print_exception(exc); cfg_select! { unix => { - let action = SigAction::new( - nix::sys::signal::SigHandler::SigDfl, - SaFlags::SA_ONSTACK, - SigSet::empty(), - ); - let result = unsafe { sigaction(SIGINT, &action) }; - if result.is_ok() { + if crate::host_env::signal::set_sigint_default_onstack().is_ok() { self.flush_std(); - kill(getpid(), SIGINT).expect("Expect to be killed."); + crate::host_env::signal::send_sigint_to_self() + .expect("Expect to be killed."); } (libc::SIGINT as u32) + 128 diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 1d858310ff9..aed2b3fd689 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -1,18 +1,12 @@ -use crate::host_env::fileutils::{ - StatStruct, - windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name}, -}; use crate::{ PyObjectRef, PyResult, TryFromObject, VirtualMachine, convert::{ToPyObject, ToPyResult}, }; -use rustpython_host_env::windows::ToWideString; -use std::ffi::OsStr; -use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; +use rustpython_host_env::nt as host_nt; /// Windows HANDLE wrapper for Python interop #[derive(Clone, Copy)] -pub struct WinHandle(pub HANDLE); +pub struct WinHandle(pub host_nt::Handle); pub(crate) trait WindowsSysResultValue { type Ok: ToPyObject; @@ -22,11 +16,11 @@ pub(crate) trait WindowsSysResultValue { fn into_ok(self) -> Self::Ok; } -impl WindowsSysResultValue for HANDLE { +impl WindowsSysResultValue for host_nt::Handle { type Ok = WinHandle; fn is_err(&self) -> bool { - *self == INVALID_HANDLE_VALUE + host_nt::is_invalid_handle(*self) } fn into_ok(self) -> Self::Ok { @@ -73,7 +67,7 @@ type HandleInt = isize; impl TryFromObject for WinHandle { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { let handle = HandleInt::try_from_object(vm, obj)?; - Ok(Self(handle as HANDLE)) + Ok(Self(handle as host_nt::Handle)) } } @@ -82,465 +76,3 @@ impl ToPyObject for WinHandle { (self.0 as HandleInt).to_pyobject(vm) } } - -pub fn init_winsock() { - static WSA_INIT: parking_lot::Once = parking_lot::Once::new(); - WSA_INIT.call_once(|| unsafe { - let mut wsa_data = core::mem::MaybeUninit::uninit(); - let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr()); - }) -} - -// win32_xstat in cpython -pub fn win32_xstat(path: &OsStr, traverse: bool) -> std::io::Result { - let mut result = win32_xstat_impl(path, traverse)?; - // ctime is only deprecated from 3.12, so we copy birthtime across - result.st_ctime = result.st_birthtime; - result.st_ctime_nsec = result.st_birthtime_nsec; - Ok(result) -} - -fn is_reparse_tag_name_surrogate(tag: u32) -> bool { - (tag & 0x20000000) > 0 -} - -// Constants -const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; -const S_IFMT: u16 = libc::S_IFMT as u16; -const S_IFDIR: u16 = libc::S_IFDIR as u16; -const S_IFREG: u16 = libc::S_IFREG as u16; -const S_IFCHR: u16 = libc::S_IFCHR as u16; -const S_IFLNK: u16 = crate::host_env::fileutils::windows::S_IFLNK as u16; -const S_IFIFO: u16 = crate::host_env::fileutils::windows::S_IFIFO as u16; - -/// FILE_ATTRIBUTE_TAG_INFO structure for GetFileInformationByHandleEx -#[repr(C)] -#[derive(Default)] -struct FileAttributeTagInfo { - file_attributes: u32, - reparse_tag: u32, -} - -/// Ported from attributes_to_mode (fileutils.c) -fn attributes_to_mode(attr: u32) -> u16 { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, - }; - let mut m: u16 = 0; - if attr & FILE_ATTRIBUTE_DIRECTORY != 0 { - m |= S_IFDIR | 0o111; // IFEXEC for user,group,other - } else { - m |= S_IFREG; - } - if attr & FILE_ATTRIBUTE_READONLY != 0 { - m |= 0o444; - } else { - m |= 0o666; - } - m -} - -/// Ported from _Py_attribute_data_to_stat (fileutils.c) -/// Converts BY_HANDLE_FILE_INFORMATION to StatStruct -fn attribute_data_to_stat( - info: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, - reparse_tag: u32, - basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, - id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, -) -> StatStruct { - use crate::host_env::fileutils::windows::SECS_BETWEEN_EPOCHS; - use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; - - let mut st_mode = attributes_to_mode(info.dwFileAttributes); - let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64); - let st_dev = id_info.map_or(info.dwVolumeSerialNumber, |id| id.VolumeSerialNumber as u32); - let st_nlink = info.nNumberOfLinks as i32; - - // Convert FILETIME/LARGE_INTEGER to (time_t, nsec) - let filetime_to_time = |ft_low: u32, ft_high: u32| -> (libc::time_t, i32) { - let ticks = ((ft_high as i64) << 32) | (ft_low as i64); - let nsec = ((ticks % 10_000_000) * 100) as i32; - let sec = (ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; - (sec, nsec) - }; - - let large_integer_to_time = |li: i64| -> (libc::time_t, i32) { - let nsec = ((li % 10_000_000) * 100) as i32; - let sec = (li / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; - (sec, nsec) - }; - - let (st_birthtime, st_birthtime_nsec); - let (st_mtime, st_mtime_nsec); - let (st_atime, st_atime_nsec); - - if let Some(bi) = basic_info { - (st_birthtime, st_birthtime_nsec) = large_integer_to_time(bi.CreationTime); - (st_mtime, st_mtime_nsec) = large_integer_to_time(bi.LastWriteTime); - (st_atime, st_atime_nsec) = large_integer_to_time(bi.LastAccessTime); - } else { - (st_birthtime, st_birthtime_nsec) = filetime_to_time( - info.ftCreationTime.dwLowDateTime, - info.ftCreationTime.dwHighDateTime, - ); - (st_mtime, st_mtime_nsec) = filetime_to_time( - info.ftLastWriteTime.dwLowDateTime, - info.ftLastWriteTime.dwHighDateTime, - ); - (st_atime, st_atime_nsec) = filetime_to_time( - info.ftLastAccessTime.dwLowDateTime, - info.ftLastAccessTime.dwHighDateTime, - ); - } - - // Get file ID from id_info or fallback to file index - let (st_ino, st_ino_high) = if let Some(id) = id_info { - // FILE_ID_INFO.FileId is FILE_ID_128 which is [u8; 16] - let bytes = id.FileId.Identifier; - let low = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); - let high = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); - (low, high) - } else { - let ino = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64); - (ino, 0u64) - }; - - // Set symlink mode if applicable - if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 - && reparse_tag == IO_REPARSE_TAG_SYMLINK - { - st_mode = (st_mode & !S_IFMT) | S_IFLNK; - } - - StatStruct { - st_dev, - st_ino, - st_ino_high, - st_mode, - st_nlink, - st_uid: 0, - st_gid: 0, - st_rdev: 0, - st_size, - st_atime, - st_atime_nsec, - st_mtime, - st_mtime_nsec, - st_ctime: 0, // Will be set by caller - st_ctime_nsec: 0, - st_birthtime, - st_birthtime_nsec, - st_file_attributes: info.dwFileAttributes, - st_reparse_tag: reparse_tag, - } -} - -/// Get file info using FindFirstFileW (fallback when CreateFileW fails) -/// Ported from attributes_from_dir -fn attributes_from_dir( - path: &OsStr, -) -> std::io::Result<( - windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, - u32, -)> { - use windows_sys::Win32::Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW, - WIN32_FIND_DATAW, - }; - - let wide: Vec = path.to_wide_with_nul(); - let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; - - let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; - if handle == INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error()); - } - unsafe { FindClose(handle) }; - - let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; - info.dwFileAttributes = find_data.dwFileAttributes; - info.ftCreationTime = find_data.ftCreationTime; - info.ftLastAccessTime = find_data.ftLastAccessTime; - info.ftLastWriteTime = find_data.ftLastWriteTime; - info.nFileSizeHigh = find_data.nFileSizeHigh; - info.nFileSizeLow = find_data.nFileSizeLow; - - let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { - find_data.dwReserved0 - } else { - 0 - }; - - Ok((info, reparse_tag)) -} - -/// Ported from win32_xstat_slow_impl -fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result { - use windows_sys::Win32::{ - Foundation::{ - CloseHandle, ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, - ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ, - INVALID_HANDLE_VALUE, - }, - Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY, - FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, - FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, - FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_CHAR, - FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN, FileAttributeTagInfo, FileBasicInfo, - FileIdInfo, GetFileAttributesW, GetFileInformationByHandle, - GetFileInformationByHandleEx, GetFileType, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, - }, - }; - - let wide: Vec = path.to_wide_with_nul(); - - let access = FILE_READ_ATTRIBUTES; - let mut flags = FILE_FLAG_BACKUP_SEMANTICS; - if !traverse { - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - - let mut h_file = unsafe { - CreateFileW( - wide.as_ptr(), - access, - 0, - core::ptr::null(), - OPEN_EXISTING, - flags, - core::ptr::null_mut(), - ) - }; - - let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; - let mut tag_info = FileAttributeTagInfo::default(); - let mut is_unhandled_tag = false; - - if h_file == INVALID_HANDLE_VALUE { - let error = std::io::Error::last_os_error(); - let error_code = error.raw_os_error().unwrap_or(0) as u32; - - match error_code { - ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => { - // Try reading the parent directory using FindFirstFileW - let (info, reparse_tag) = attributes_from_dir(path)?; - file_info = info; - tag_info.reparse_tag = reparse_tag; - - if file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 - && (traverse || !is_reparse_tag_name_surrogate(tag_info.reparse_tag)) - { - return Err(error); - } - // h_file remains INVALID_HANDLE_VALUE, we'll use file_info from FindFirstFileW - } - ERROR_INVALID_PARAMETER => { - // Retry with GENERIC_READ (needed for \\.\con) - h_file = unsafe { - CreateFileW( - wide.as_ptr(), - access | GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - flags, - core::ptr::null_mut(), - ) - }; - if h_file == INVALID_HANDLE_VALUE { - return Err(error); - } - } - ERROR_CANT_ACCESS_FILE if traverse => { - // bpo37834: open unhandled reparse points if traverse fails - is_unhandled_tag = true; - h_file = unsafe { - CreateFileW( - wide.as_ptr(), - access, - 0, - core::ptr::null(), - OPEN_EXISTING, - flags | FILE_FLAG_OPEN_REPARSE_POINT, - core::ptr::null_mut(), - ) - }; - if h_file == INVALID_HANDLE_VALUE { - return Err(error); - } - } - _ => return Err(error), - } - } - - // Scope for handle cleanup - let result = (|| -> std::io::Result { - if h_file != INVALID_HANDLE_VALUE { - // Handle types other than files on disk - let file_type = unsafe { GetFileType(h_file) }; - if file_type != FILE_TYPE_DISK { - if file_type == FILE_TYPE_UNKNOWN { - let err = std::io::Error::last_os_error(); - if err.raw_os_error().unwrap_or(0) != 0 { - return Err(err); - } - } - let file_attributes = unsafe { GetFileAttributesW(wide.as_ptr()) }; - let mut st_mode: u16 = 0; - if file_attributes != INVALID_FILE_ATTRIBUTES - && file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0 - { - st_mode = S_IFDIR; - } else if file_type == FILE_TYPE_CHAR { - st_mode = S_IFCHR; - } else if file_type == FILE_TYPE_PIPE { - st_mode = S_IFIFO; - } - return Ok(StatStruct { - st_mode, - ..Default::default() - }); - } - - // Query the reparse tag - if !traverse || is_unhandled_tag { - let mut local_tag_info: FileAttributeTagInfo = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - h_file, - FileAttributeTagInfo, - &mut local_tag_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - let err_code = - std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; - match err_code { - ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { - local_tag_info.file_attributes = FILE_ATTRIBUTE_NORMAL; - local_tag_info.reparse_tag = 0; - } - _ => return Err(std::io::Error::last_os_error()), - } - } else if local_tag_info.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { - if is_reparse_tag_name_surrogate(local_tag_info.reparse_tag) { - if is_unhandled_tag { - return Err(std::io::Error::from_raw_os_error( - ERROR_CANT_ACCESS_FILE as i32, - )); - } - // This is a symlink, keep the tag info - } else if !is_unhandled_tag { - // Traverse a non-link reparse point - unsafe { CloseHandle(h_file) }; - return win32_xstat_slow_impl(path, true); - } - } - tag_info = local_tag_info; - } - - // Get file information - let ret = unsafe { GetFileInformationByHandle(h_file, &mut file_info) }; - if ret == 0 { - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; - match err_code { - ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { - // Volumes and physical disks are block devices - return Ok(StatStruct { - st_mode: 0x6000, // S_IFBLK - ..Default::default() - }); - } - _ => return Err(std::io::Error::last_os_error()), - } - } - - // Get FILE_BASIC_INFO - let mut basic_info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; - let has_basic_info = unsafe { - GetFileInformationByHandleEx( - h_file, - FileBasicInfo, - &mut basic_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - } != 0; - - // Get FILE_ID_INFO (optional) - let mut id_info: FILE_ID_INFO = unsafe { core::mem::zeroed() }; - let has_id_info = unsafe { - GetFileInformationByHandleEx( - h_file, - FileIdInfo, - &mut id_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - } != 0; - - let mut result = attribute_data_to_stat( - &file_info, - tag_info.reparse_tag, - if has_basic_info { - Some(&basic_info) - } else { - None - }, - if has_id_info { Some(&id_info) } else { None }, - ); - result.update_st_mode_from_path(path, file_info.dwFileAttributes); - Ok(result) - } else { - // We got file_info from attributes_from_dir - let mut result = attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None); - result.update_st_mode_from_path(path, file_info.dwFileAttributes); - Ok(result) - } - })(); - - // Cleanup - if h_file != INVALID_HANDLE_VALUE { - unsafe { CloseHandle(h_file) }; - } - - result -} - -fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result { - use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT}; - - let stat_info = - get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo); - match stat_info { - Ok(stat_info) => { - if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0) - || (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag)) - { - let mut result = - crate::host_env::fileutils::windows::stat_basic_info_to_stat(&stat_info); - // If st_ino is 0, fall through to slow path to get proper file ID - if result.st_ino != 0 || result.st_ino_high != 0 { - result.update_st_mode_from_path(path, stat_info.FileAttributes); - return Ok(result); - } - } - } - Err(e) => { - if let Some(errno) = e.raw_os_error() - && matches!( - errno as u32, - Foundation::ERROR_FILE_NOT_FOUND - | Foundation::ERROR_PATH_NOT_FOUND - | Foundation::ERROR_NOT_READY - | Foundation::ERROR_BAD_NET_NAME - ) - { - return Err(e); - } - } - } - - // Fallback to slow implementation - win32_xstat_slow_impl(path, traverse) -} diff --git a/host_env_proposal.md b/host_env_proposal.md new file mode 100644 index 00000000000..fd32df1abc9 --- /dev/null +++ b/host_env_proposal.md @@ -0,0 +1,494 @@ +# Plan: Create `rustpython-host_env` crate + +## Context + +RustPython controls host OS access via the `host_env` feature flag, enforced by `#[cfg(feature = "host_env")]` scattered across hundreds of locations. If a `cfg` is forgotten, host code leaks into sandbox builds silently. + +By isolating host OS API wrappers into a dedicated crate, **the crate boundary itself becomes the sandbox guarantee**. Key constraint: this crate has **zero Python runtime dependency**. All Python-level bindings must be added by the consumer (vm/stdlib). + +## Current State + +### Already Python-free host abstractions in `crates/common/src/`: +- `os.rs` — errno handling, exit_code, winerror_to_errno, OsStr ffi conversions +- `crt_fd.rs` — CRT file descriptor abstraction (Owned/Borrowed types, open/read/write/close) +- `fileutils.rs` — fstat, fopen, Windows StatStruct +- `windows.rs` — ToWideString, FromWideString traits +- `macros.rs` — `suppress_iph!` macro (MSVC invalid parameter handler suppression) + +### Pure host functions embedded in vm/stdlib modules: + +These files mix Python bindings with pure host API calls. The host parts should be extracted: + +**`vm/src/stdlib/posix.rs`** (2908 lines): +- `set_inheritable(fd, inheritable)` — pure nix fcntl wrapper +- `getgroups_impl()` — pure libc/nix wrapper +- `get_right_permission()`, `get_permissions()` — pure permission logic +- 400+ libc constant re-exports (`#[pyattr] use libc::*`) + +**`vm/src/stdlib/nt.rs`** (2301 lines): +- `win32_hchmod()`, `win32_lchmod()`, `fchmod_impl()` — pure Windows API calls (currently return PyResult, should return io::Result) +- Spawn mode constants, `O_*` flags + +**`vm/src/stdlib/_signal.rs`** (729 lines): +- `timeval_to_double()`, `double_to_timeval()`, `itimerval_to_tuple()` — pure math +- 30+ signal/timer constants + +**`vm/src/stdlib/time.rs`** (1616 lines): +- `asctime_from_tm()` — pure string formatting +- `get_tz_info()` — pure Windows API +- Time unit constants (`SEC_TO_MS`, `MS_TO_US`, etc.) +- `duration_since_system_now()` — host clock access (currently takes vm, can return io::Result instead) + +**`vm/src/stdlib/msvcrt.rs`**: +- `getch()`, `getwch()`, `getche()`, `getwche()`, `kbhit()`, `setmode_binary()` — all pure host +- Locking constants (`LK_UNLCK`, `LK_LOCK`, etc.) + +**`vm/src/stdlib/_winapi.rs`** (2180 lines): +- `GetACP()`, `GetCurrentProcess()`, `GetLastError()`, `GetVersion()` — pure host +- 100+ Windows API constants + +**`vm/src/stdlib/os.rs`** (2395 lines): +- `fs_metadata()` — pure `std::fs` wrapper +- libc flag constants (`O_APPEND`, `O_CREAT`, etc.) + +## Dependency Graph (After) + +``` +rustpython-host_env (NEW — zero Python dep, independent of common) +├── Dependencies: libc, nix (unix), windows-sys (win), widestring (win), rustpython-wtf8 +├── From common: os, crt_fd, fileutils, windows, macros +└── Extracted from vm/stdlib: posix, nt, signal, time, msvcrt, winapi, socket, mmap, ... + +rustpython-common (NO host_env dependency — pure algorithmic code only) +└── cformat, float_ops, hash, int, str, encodings, etc. + +rustpython-vm +├── rustpython-common +├── rustpython-host_env (optional, feature = "host_env") +├── libc (retained for type definitions & constants used inline in #[pyattr]) +└── Python bindings call host_env for actual OS operations + +rustpython-stdlib +├── rustpython-vm, rustpython-common +├── rustpython-host_env (optional, feature = "host_env") +└── libc, nix, socket2, memmap2 (retained for now — future migration target) +``` + +`common` and `host_env` are fully independent — no dependency in either direction. + +## Phase 1: Create the crate and move modules from common + +Create `crates/host_env/`, **move** host modules from common, and update common to re-export. + +### New files: + +**`crates/host_env/Cargo.toml`:** +```toml +[package] +name = "rustpython-host_env" +description = "Host OS API abstractions for RustPython (zero Python dependency)" +version.workspace = true +edition.workspace = true + +[dependencies] +rustpython-wtf8 = { workspace = true } +libc = { workspace = true } +num-traits = { workspace = true } +cfg-if = { workspace = true } + +[target.'cfg(unix)'.dependencies] +nix = { workspace = true } + +[target.'cfg(windows)'.dependencies] +widestring = { workspace = true } +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_Globalization", + "Win32_Networking_WinSock", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Ioctl", + "Win32_System_LibraryLoader", + "Win32_System_SystemServices", + "Win32_System_Time", +] } +``` + +**`crates/host_env/src/lib.rs`:** +```rust +#[macro_use] +mod macros; +pub use macros::*; + +pub mod os; + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod crt_fd; + +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +pub mod fileutils; + +#[cfg(windows)] +pub mod windows; + +// New modules — extracted from vm/stdlib (Phase 2) +#[cfg(unix)] +pub mod posix; +#[cfg(windows)] +pub mod nt; +pub mod signal; +pub mod time; +#[cfg(windows)] +pub mod msvcrt; +#[cfg(windows)] +pub mod winapi; +``` + +**Modules moved from common**: `os.rs`, `crt_fd.rs`, `fileutils.rs`, `windows.rs`, `macros.rs` + +### Modified files: + +**`Cargo.toml` (workspace root):** +- Add `"crates/host_env"` to `[workspace.members]` +- Add `rustpython-host_env = { path = "crates/host_env" }` to `[workspace.dependencies]` + +**`crates/common/Cargo.toml`:** +- Remove `nix`, `windows-sys`, `widestring` from direct dependencies +- Keep `libc` for type definitions (`wchar_t` in `str.rs`) +- No `host_env` feature or dependency — common stays purely algorithmic + +**`crates/common/src/lib.rs`:** +- Remove `pub mod os`, `pub mod crt_fd`, `pub mod fileutils`, `pub mod windows` declarations +- Remove `#[macro_use] mod macros` and `suppress_iph!` macro (moved to host_env) +- Delete the source files: `os.rs`, `crt_fd.rs`, `fileutils.rs`, `windows.rs`, `macros.rs` + +**`crates/vm/Cargo.toml`:** +```toml +[features] +host_env = ["rustpython-host_env"] + +[dependencies] +rustpython-host_env = { workspace = true, optional = true } +``` + +**`crates/stdlib/Cargo.toml`:** +```toml +[features] +host_env = ["rustpython-vm/host_env", "rustpython-host_env"] + +[dependencies] +rustpython-host_env = { workspace = true, optional = true } +``` + +### Verification: +```bash +cargo check -p rustpython-host_env +cargo test +cargo check -p rustpython-vm --no-default-features --features compiler,gc # sandbox build +``` + +## Phase 2: Extract host functions from vm/stdlib modules + +Extract pure host API functions and constants from vm's stdlib modules into new modules within `host_env`. + +### New modules in `crates/host_env/src/`: + +**`posix.rs`** — extracted from `vm/src/stdlib/posix.rs`: +```rust +use std::os::fd::BorrowedFd; + +pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { + use nix::fcntl; + let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); + let mut new_flags = flags; + new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); + if flags != new_flags { + fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; + } + Ok(()) +} + +pub fn getgroups() -> nix::Result> { ... } +pub fn get_right_permission(mode: u32, file_owner: Uid, file_group: Gid) -> nix::Result { ... } +``` + +**`nt.rs`** — extracted from `vm/src/stdlib/nt.rs`: +```rust +pub fn win32_hchmod(handle: HANDLE, mode: u32) -> io::Result<()> { ... } +pub fn win32_lchmod(path: &OsStr, mode: u32) -> io::Result<()> { ... } +``` + +**`signal.rs`** — extracted from `vm/src/stdlib/_signal.rs`: +```rust +pub fn timeval_to_double(tv: &libc::timeval) -> f64 { ... } +pub fn double_to_timeval(val: f64) -> libc::timeval { ... } +pub fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { ... } +``` + +**`time.rs`** — extracted from `vm/src/stdlib/time.rs`: +```rust +pub const SEC_TO_MS: i64 = 1000; +pub const MS_TO_US: i64 = 1000; +// ... + +pub fn asctime_from_tm(tm: &libc::tm) -> String { ... } +pub fn duration_since_system_now() -> io::Result { ... } + +#[cfg(windows)] +pub fn get_tz_info() -> TIME_ZONE_INFORMATION { ... } +``` + +**`msvcrt.rs`** — extracted from `vm/src/stdlib/msvcrt.rs`: +```rust +pub fn getch() -> Vec { ... } +pub fn getwch() -> String { ... } +pub fn kbhit() -> i32 { ... } +pub fn setmode_binary(fd: crt_fd::Borrowed<'_>) { ... } + +pub const LK_UNLCK: i32 = 0; +pub const LK_LOCK: i32 = 1; +// ... +``` + +**`winapi.rs`** — extracted from `vm/src/stdlib/_winapi.rs`: +```rust +pub fn get_acp() -> u32 { ... } +pub fn get_current_process() -> HANDLE { ... } +pub fn get_last_error() -> u32 { ... } +pub fn get_version() -> u32 { ... } +// + Windows API constants +``` + +### Modified vm/stdlib files: + +Each file is updated to call `rustpython_host_env::` instead of inlining the host calls: + +```rust +// BEFORE (vm/src/stdlib/posix.rs) +pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { + use nix::fcntl; + // ... 10 lines of nix API calls +} + +// AFTER (vm/src/stdlib/posix.rs) +pub use rustpython_host_env::posix::set_inheritable; +``` + +## Phase 3: vm/stdlib import migration + +All `common::os`, `common::crt_fd`, `common::fileutils`, `common::windows` imports must be updated to `rustpython_host_env::`. + +### Import migration targets (vm) — ~20 files: + +| File | Current | New | +|------|---------|-----| +| `ospath.rs` | `rustpython_common::crt_fd` | `rustpython_host_env::crt_fd` | +| `stdlib/os.rs` | `common::crt_fd`, `common::os::*` | `rustpython_host_env::` | +| `stdlib/nt.rs` | `common::windows::*`, `common::crt_fd::*` | `rustpython_host_env::` | +| `stdlib/_io.rs` | `common::crt_fd::Offset`, `common::fileutils::fstat` | `rustpython_host_env::` | +| `stdlib/_signal.rs` | `common::crt_fd::*`, `common::fileutils::fstat` | `rustpython_host_env::` | +| `stdlib/posix.rs` | `common::os::*`, `common::crt_fd::Offset` | `rustpython_host_env::` | +| `stdlib/_ctypes/function.rs` | `rustpython_common::os::get_errno` | `rustpython_host_env::os::` | +| `stdlib/_codecs.rs` | `common::windows::ToWideString` | `rustpython_host_env::windows::` | +| `stdlib/sys.rs`, `winreg.rs`, `winsound.rs` | `common::windows::ToWideString` | `rustpython_host_env::windows::` | +| `windows.rs` | `rustpython_common::windows::ToWideString` | `rustpython_host_env::windows::` | +| `exceptions.rs` | `common::os::ErrorExt`, `common::os::winerror_to_errno` | `rustpython_host_env::os::` | + +### Import migration targets (stdlib) — ~7 files: + +| File | Current | New | +|------|---------|-----| +| `socket.rs` | `common::os::ErrorExt`, `common::os::errno_io_error` | `rustpython_host_env::os::` | +| `mmap.rs` | `rustpython_common::crt_fd` | `rustpython_host_env::crt_fd` | +| `faulthandler.rs` | `rustpython_common::os::{get_errno, set_errno}` | `rustpython_host_env::os::` | +| `posixshmem.rs` | `common::os::errno_io_error` | `rustpython_host_env::os::` | +| `termios.rs` | `common::os::ErrorExt` | `rustpython_host_env::os::` | +| `overlapped.rs` | `crate::vm::common::os::winerror_to_errno` | `rustpython_host_env::os::` | +| `openssl.rs` | `rustpython_common::fileutils::fopen` | `rustpython_host_env::fileutils::` | + +### External consumers: + +| File | Current | New | +|------|---------|-----| +| `src/lib.rs` | `rustpython_vm::common::os::exit_code` | `rustpython_host_env::os::exit_code` | +| `examples/*.rs` | `vm::common::os::exit_code` | Keep via re-export | + +## Phase 4 (Future): Extract host functions from stdlib modules + +Same pattern as Phase 2, but for `crates/stdlib/src/` modules. These modules heavily use `libc`, `nix`, `socket2`, `memmap2` directly. Extract the pure host layer into `host_env`. + +**Target modules and what goes into host_env:** + +| stdlib module | host_env module | What to extract | +|---------------|----------------|-----------------| +| `socket.rs` (3498 lines) | `host_env::socket` | Socket creation, bind, connect, address conversion, cmsg helpers, poll wrappers. Re-export `socket2` types. | +| `mmap.rs` (1625 lines) | `host_env::mmap` | mmap/munmap wrappers, madvise, msync. Re-export `memmap2` types. | +| `select.rs` (745 lines) | `host_env::select` | select/poll/epoll/kqueue wrappers via libc/nix. | +| `posixsubprocess.rs` (537 lines) | `host_env::subprocess` | fork_exec, pipe, dup2, close-on-exec logic. | +| `multiprocessing.rs` (1152 lines) | `host_env::multiprocessing` | Semaphore operations (sem_open/wait/post/unlink via libc). | +| `fcntl.rs` (220 lines) | `host_env::fcntl` | fcntl, ioctl, flock wrappers. | +| `faulthandler.rs` (1333 lines) | `host_env::faulthandler` | Signal handler registration, stack dump via libc write. | +| `locale.rs` (332 lines) | `host_env::locale` | strcoll, strxfrm, setlocale wrappers. | +| `resource.rs` (194 lines) | `host_env::resource` | getrusage, getrlimit, setrlimit wrappers. | +| `grp.rs` (103 lines) | `host_env::grp` | getgrent/setgrent/endgrent, Group lookup via nix. | +| `syslog.rs` (148 lines) | `host_env::syslog` | openlog, syslog, closelog, setlogmask wrappers. | +| `posixshmem.rs` (52 lines) | `host_env::shm` | shm_open, shm_unlink wrappers. | +| `termios.rs` (280 lines) | `host_env::termios` | Terminal attribute get/set via termios crate. | + +After this, `nix`, `socket2`, `memmap2`, `rustix` are removed from stdlib's direct dependencies. Only `host_env` provides them. + +## Phase 5: Lint enforcement + +Three layers of enforcement, from strongest to lightest: + +### Layer 1: Crate boundary (compile-time, absolute) + +The strongest guarantee. If a crate doesn't list `rustpython-host_env` in its `[dependencies]`, it physically cannot call any host_env function. This is already enforced by Rust's module system. + +**Pure crates (no host_env dependency allowed):** +- `rustpython-common` +- `rustpython-compiler`, `rustpython-compiler-core`, `rustpython-compiler-source` +- `rustpython-codegen` +- `rustpython-literal` +- `rustpython-sre_engine` +- `rustpython-wtf8` +- `rustpython-derive`, `rustpython-derive-impl` + +CI check: +```bash +# Verify pure crates don't depend on host_env +for crate in common compiler compiler-core compiler-source codegen literal sre_engine wtf8 derive derive-impl; do + if rg 'rustpython-host_env' "crates/$crate/Cargo.toml"; then + echo "ERROR: $crate should not depend on host_env" + exit 1 + fi +done +``` + +### Layer 2: clippy disallowed_methods (compile-time, configurable) + +Block direct host API usage in vm/stdlib. Force all host access through `host_env`. + +**Workspace-level `clippy.toml`** (project root): +```toml +disallowed-methods = [ + # Filesystem + { path = "std::fs::read", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::write", reason = "use rustpython_host_env" }, + { path = "std::fs::read_to_string", reason = "use rustpython_host_env" }, + { path = "std::fs::read_dir", reason = "use rustpython_host_env" }, + { path = "std::fs::create_dir", reason = "use rustpython_host_env" }, + { path = "std::fs::create_dir_all", reason = "use rustpython_host_env" }, + { path = "std::fs::remove_file", reason = "use rustpython_host_env" }, + { path = "std::fs::remove_dir", reason = "use rustpython_host_env" }, + { path = "std::fs::metadata", reason = "use rustpython_host_env" }, + { path = "std::fs::symlink_metadata", reason = "use rustpython_host_env" }, + { path = "std::fs::canonicalize", reason = "use rustpython_host_env" }, + { path = "std::fs::File::open", reason = "use rustpython_host_env" }, + { path = "std::fs::File::create", reason = "use rustpython_host_env" }, + { path = "std::fs::OpenOptions::open", reason = "use rustpython_host_env" }, + + # Environment + { path = "std::env::var", reason = "use rustpython_host_env" }, + { path = "std::env::var_os", reason = "use rustpython_host_env" }, + { path = "std::env::set_var", reason = "use rustpython_host_env" }, + { path = "std::env::remove_var", reason = "use rustpython_host_env" }, + { path = "std::env::vars", reason = "use rustpython_host_env" }, + { path = "std::env::vars_os", reason = "use rustpython_host_env" }, + { path = "std::env::current_dir", reason = "use rustpython_host_env" }, + { path = "std::env::set_current_dir", reason = "use rustpython_host_env" }, + { path = "std::env::temp_dir", reason = "use rustpython_host_env" }, + + # Process + { path = "std::process::Command::new", reason = "use rustpython_host_env" }, + { path = "std::process::exit", reason = "use rustpython_host_env" }, + { path = "std::process::abort", reason = "use rustpython_host_env" }, + { path = "std::process::id", reason = "use rustpython_host_env" }, + + # Network + { path = "std::net::TcpStream::connect", reason = "use rustpython_host_env" }, + { path = "std::net::TcpListener::bind", reason = "use rustpython_host_env" }, + { path = "std::net::UdpSocket::bind", reason = "use rustpython_host_env" }, +] +``` + +**`crates/host_env/clippy.toml`** (overrides — host_env is allowed to use everything): +```toml +disallowed-methods = [] +``` + +Clippy resolves `clippy.toml` by walking up from the crate directory, so `host_env`'s local config takes precedence over the workspace root. + +**Workspace `Cargo.toml`:** +```toml +[workspace.lints.clippy] +disallowed_methods = "deny" +``` + +### Layer 3: Sandbox build verification (CI) + +Build without `host_env` feature to catch any code that accidentally compiles without the feature gate: + +```bash +cargo check -p rustpython-vm --no-default-features --features compiler,gc +cargo check -p rustpython-stdlib --no-default-features --features compiler +``` + +### Layer 4: Whitelist-based module audit (CI script) + +Maintain a whitelist of modules in vm/stdlib that are known to NOT use host_env. Any change that adds a `rustpython_host_env` import to a whitelisted module triggers CI failure. + +```bash +# .ci/host_env_whitelist.txt — modules that must stay host-free +# vm modules: +crates/vm/src/stdlib/_abc.rs +crates/vm/src/stdlib/_collections.rs +crates/vm/src/stdlib/_functools.rs +crates/vm/src/stdlib/_operator.rs +crates/vm/src/stdlib/_sre.rs +crates/vm/src/stdlib/_stat.rs +crates/vm/src/stdlib/_string.rs +crates/vm/src/stdlib/errno.rs +crates/vm/src/stdlib/gc.rs +crates/vm/src/stdlib/itertools.rs +crates/vm/src/stdlib/marshal.rs + +# Check: +while IFS= read -r file; do + if rg 'rustpython_host_env' "$file" 2>/dev/null; then + echo "ERROR: $file is whitelisted as host-free but imports host_env" + exit 1 + fi +done < .ci/host_env_whitelist.txt +``` + +The inverse is also useful — list all files that ARE allowed to use host_env, and reject any new file that uses it without being on the list. This catches accidental host API usage in new modules. + +### Layer 5: `#![no_std]` for pure crates + +After removing host modules from `common`, it could potentially become `#![no_std]` unconditionally (it already has `#![cfg_attr(not(feature = "std"), no_std)]`). This is the strongest possible guarantee — no `std::fs`, `std::env`, `std::net`, `std::process` available at all. + +Candidate crates for unconditional `#![no_std]`: +- `rustpython-literal` +- `rustpython-wtf8` +- `rustpython-compiler-source` + +### Summary of enforcement layers + +| Layer | What it catches | Strength | Cost | +|-------|----------------|----------|------| +| Crate boundary | Missing host_env dependency | Absolute — compile error | Zero — automatic | +| clippy disallowed_methods | Direct std::fs/env/net usage | Strong — clippy deny | Low — clippy.toml config | +| Sandbox build | Missing `#[cfg(feature = "host_env")]` | Strong — compile error | Low — CI job | +| Module whitelist | Unintended host_env usage in pure modules | Medium — CI script | Low — maintain whitelist | +| `#![no_std]` | Any std usage in pure crates | Absolute — compile error | Medium — may need refactoring | + +## Risk Assessment + +| Risk | Level | Mitigation | +|------|-------|------------| +| Target modules have Python type dependencies | **Low** | Verified: only `libc`, `nix`, `windows-sys`, `rustpython-wtf8` | +| Internal cross-references break on move | **Low** | `crt_fd`, `os`, `fileutils`, `windows` all move together; `crate::` paths stay valid | +| `suppress_iph!` macro `$crate` resolution | **Medium** | `$crate` automatically resolves to new crate; `__macro_private` moves alongside | +| Breaking external consumers | **Medium** | Clean break — consumers must update `common::os` to `host_env::os`. No re-export shim. | +| Scope of Phase 2 extraction | **Medium** | Start with clearly pure functions; mixed functions can be migrated incrementally | From ae108b4f2e195bfaa168eaecacc708f655b4a23c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 16 May 2026 12:23:57 +0900 Subject: [PATCH 2/3] Remove orphaned _name_wide assignment in WindowsConsoleIO open --- crates/vm/src/stdlib/_io.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index f178ae85e3a..e25d6b60901 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -6023,8 +6023,6 @@ mod winconsoleio { fd = host_nt::open_console_path_fd(wide.as_ptr(), writable) .map_err(|err| err.to_pyexception(vm))?; - - _name_wide = Some(wide); } else { // When opened by fd, never close the fd (user owns it) zelf.closefd.store(false); From 8e94df65da703d2134ded23fc7dc74b54b79dce2 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 16 May 2026 17:32:04 +0900 Subject: [PATCH 3/3] Inline cmd in ioctl error format string --- crates/stdlib/src/socket.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index f11fa4c8402..4193350776e 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -2163,7 +2163,7 @@ mod _socket { if cmd != c::SIO_KEEPALIVE_VALS { return Err(vm - .new_value_error(format!("invalid ioctl command {}", cmd)) + .new_value_error(format!("invalid ioctl command {cmd}")) .into()); } host_socket::ioctl_keepalive(fd as _, ka).map_err(Into::into)