interprocess-2.2.3/.cargo_vcs_info.json0000644000000001360000000000100135600ustar { "git": { "sha1": "71921a61b842e5051525857b12a550b90b9d2042" }, "path_in_vcs": "" }interprocess-2.2.3/Cargo.lock0000644000000331070000000000100115370ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "backtrace" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "color-eyre" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" dependencies = [ "backtrace", "color-spantrace", "eyre", "indenter", "once_cell", "owo-colors", "tracing-error", ] [[package]] name = "color-spantrace" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", "tracing-core", "tracing-error", ] [[package]] name = "doctest-file" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "interprocess" version = "2.2.3" dependencies = [ "color-eyre", "doctest-file", "futures-core", "libc", "recvmsg", "tokio", "widestring", "windows-sys 0.52.0", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "recvmsg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "socket2" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "syn" version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "tokio" version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-error" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ "tracing", "tracing-subscriber", ] [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "sharded-slab", "thread_local", "tracing-core", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "widestring" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.4", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm 0.52.4", "windows_aarch64_msvc 0.52.4", "windows_i686_gnu 0.52.4", "windows_i686_msvc 0.52.4", "windows_x86_64_gnu 0.52.4", "windows_x86_64_gnullvm 0.52.4", "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" interprocess-2.2.3/Cargo.toml0000644000000077520000000000100115710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75" name = "interprocess" version = "2.2.3" authors = ["Goat "] build = false exclude = [ "/.github/", "/.gitignore", "/.editorconfig", "interprocess.code-workspace", "/Cargo.lock", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Interprocess communication toolkit" readme = "README.md" keywords = [ "ipc", "pipe", ] categories = [ "os", "os::unix-apis", "os::windows-apis", "asynchronous", ] license = "MIT OR Apache-2.0" repository = "https://github.com/kotauskas/interprocess" [package.metadata.docs.rs] features = [ "doc_cfg", "tokio", ] targets = [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "aarch64-apple-darwin", "x86_64-unknown-freebsd", ] [features] async = ["futures-core"] default = [] doc_cfg = [] tokio = [ "dep:tokio", "async", ] [lib] name = "interprocess" path = "src/lib.rs" [[example]] name = "unnamed_pipe_sync" path = "examples/unnamed_pipe/sync/main.rs" [[example]] name = "unnamed_pipe_tokio" path = "examples/unnamed_pipe/tokio/main.rs" [[example]] name = "local_socket_sync_server" path = "examples/local_socket/sync/listener.rs" [[example]] name = "local_socket_sync_client" path = "examples/local_socket/sync/stream.rs" [[example]] name = "local_socket_tokio_server" path = "examples/local_socket/tokio/listener.rs" [[example]] name = "local_socket_tokio_client" path = "examples/local_socket/tokio/stream.rs" [[example]] name = "named_pipe_sync_server" path = "examples/named_pipe/sync/listener.rs" [[example]] name = "named_pipe_sync_client_bytes" path = "examples/named_pipe/sync/stream/bytes.rs" [[example]] name = "named_pipe_sync_client_msg" path = "examples/named_pipe/sync/stream/msg.rs" [[example]] name = "named_pipe_tokio_server" path = "examples/named_pipe/tokio/listener.rs" [[example]] name = "named_pipe_tokio_client_bytes" path = "examples/named_pipe/tokio/stream/bytes.rs" [[example]] name = "named_pipe_tokio_client_msg" path = "examples/named_pipe/tokio/stream/msg.rs" [dependencies.doctest-file] version = "1.0.0" [dependencies.futures-core] version = "0.3.28" optional = true [dependencies.tokio] version = "1.36.0" features = [ "sync", "rt", "net", "time", "io-util", ] optional = true [dev-dependencies.color-eyre] version = "0.6.2" [dev-dependencies.tokio] version = "1.36.0" features = [ "sync", "rt-multi-thread", "io-util", "macros", ] [target."cfg(unix)".dependencies.libc] version = "0.2.137" features = ["extra_traits"] [target."cfg(windows)".dependencies.recvmsg] version = "1.0.0" [target."cfg(windows)".dependencies.tokio] version = "1.36.0" features = [ "sync", "rt-multi-thread", "fs", "net", "time", "io-util", ] optional = true [target."cfg(windows)".dependencies.widestring] version = "1.0.2" [target."cfg(windows)".dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Threading", "Win32_System_Memory", "Win32_System_SystemServices", "Win32_System_LibraryLoader", ] [lints.clippy] as_conversions = "deny" dbg_macro = "warn" exit = "forbid" get_unwrap = "deny" ptr_as_ptr = "forbid" tabs_in_doc_comments = "allow" [lints.rust] dead_code = "allow" unsafe_op_in_unsafe_fn = "forbid" [lints.rust.rust_2018_idioms] level = "deny" priority = -1 interprocess-2.2.3/Cargo.toml.orig000064400000000000000000000064061046102023000152450ustar 00000000000000[package] name = "interprocess" version = "2.2.3" rust-version = "1.75" edition = "2021" authors = ["Goat "] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/kotauskas/interprocess" description = "Interprocess communication toolkit" categories = ["os", "os::unix-apis", "os::windows-apis", "asynchronous"] keywords = ["ipc", "pipe"] autotests = false autoexamples = false exclude = [ "/.github/", "/.gitignore", "/.editorconfig", "interprocess.code-workspace", "/Cargo.lock", ] [[example]] name = "unnamed_pipe_sync" path = "examples/unnamed_pipe/sync/main.rs" [[example]] name = "unnamed_pipe_tokio" path = "examples/unnamed_pipe/tokio/main.rs" [[example]] name = "local_socket_sync_server" path = "examples/local_socket/sync/listener.rs" [[example]] name = "local_socket_sync_client" path = "examples/local_socket/sync/stream.rs" [[example]] name = "local_socket_tokio_server" path = "examples/local_socket/tokio/listener.rs" [[example]] name = "local_socket_tokio_client" path = "examples/local_socket/tokio/stream.rs" [[example]] name = "named_pipe_sync_server" path = "examples/named_pipe/sync/listener.rs" [[example]] name = "named_pipe_sync_client_bytes" path = "examples/named_pipe/sync/stream/bytes.rs" [[example]] name = "named_pipe_sync_client_msg" path = "examples/named_pipe/sync/stream/msg.rs" [[example]] name = "named_pipe_tokio_server" path = "examples/named_pipe/tokio/listener.rs" [[example]] name = "named_pipe_tokio_client_bytes" path = "examples/named_pipe/tokio/stream/bytes.rs" [[example]] name = "named_pipe_tokio_client_msg" path = "examples/named_pipe/tokio/stream/msg.rs" [features] default = [] async = ["futures-core"] tokio = ["dep:tokio", "async"] doc_cfg = [] [dependencies] doctest-file = "1.0.0" tokio = { version = "1.36.0", features = [ "sync", "rt", "net", "time", "io-util", ], optional = true } futures-core = { version = "0.3.28", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = [ "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Threading", "Win32_System_Memory", "Win32_System_SystemServices", "Win32_System_LibraryLoader", ] } tokio = { version = "1.36.0", features = [ "sync", "rt-multi-thread", "fs", "net", "time", "io-util", ], optional = true } recvmsg = "1.0.0" widestring = "1.0.2" [target.'cfg(unix)'.dependencies] libc = { version = "0.2.137", features = ["extra_traits"] } [dev-dependencies] tokio = { version = "1.36.0", features = [ "sync", "rt-multi-thread", "io-util", "macros", ] } color-eyre = "0.6.2" [lints.rust] unsafe_op_in_unsafe_fn = "forbid" rust_2018_idioms = { level = "deny", priority = -1 } dead_code = "allow" [lints.clippy] exit = "forbid" ptr_as_ptr = "forbid" get_unwrap = "deny" as_conversions = "deny" dbg_macro = "warn" tabs_in_doc_comments = "allow" [package.metadata.docs.rs] features = ["doc_cfg", "tokio"] targets = [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "aarch64-apple-darwin", "x86_64-unknown-freebsd", ] interprocess-2.2.3/LICENSE-APACHE000064400000000000000000000222171046102023000143000ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS interprocess-2.2.3/LICENSE-MIT000064400000000000000000000017771046102023000140200ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. interprocess-2.2.3/README.md000064400000000000000000000126351046102023000136360ustar 00000000000000[![Crates.io](https://img.shields.io/crates/v/interprocess)](https://crates.io/crates/interprocess "Interprocess on Crates.io") [![Docs.rs](https://img.shields.io/badge/documentation-docs.rs-informational)](https://docs.rs/interprocess "interprocess on Docs.rs") [![Build status](https://github.com/kotauskas/interprocess/actions/workflows/checks_and_tests.yml/badge.svg)](https://github.com/kotauskas/interprocess/actions/workflows/checks_and_tests.yml) ![maintenance-status](https://img.shields.io/badge/maintenance-actively%20developed-brightgreen) [![Rust version: 1.75+](https://img.shields.io/badge/rust%20version-1.75+-orange)][blogpost] Interprocess communication toolkit for Rust programs that aims to expose as many platform-specific features as possible while maintaining a uniform interface for all platforms and encouraging portable, correct code. ## Interprocess communication primitives Interprocess provides both OS-specific IPC interfaces and cross-platform abstractions for them. ##### Cross-platform IPC APIs - **Local sockets** – similar to TCP sockets, but use filesystem or namespaced paths instead of ports on `localhost`, depending on the OS, bypassing the network stack entirely; implemented using named pipes on Windows and Unix domain sockets on Unix ##### Platform-specific, but present on both Unix-like systems and Windows - **Unnamed pipes** – anonymous file-like objects for communicating privately in one direction, most commonly used to communicate between a child process and its parent ##### Unix-only - **FIFO files** – special type of file which is similar to unnamed pipes but exists on the filesystem, often referred to as "named pipes" but completely different from Windows named pipes - *Unix domain sockets* – Interprocess no longer provides those, as they are present in the standard library; they are, however, exposed as local sockets ##### Windows-only - **Named pipes** – resemble Unix domain sockets, use a separate namespace instead of on-drive paths ## Asynchronous I/O Currently, only Tokio for local sockets, Unix domain sockets and Windows named pipes is supported. Support for `async-std` is planned. ## Platform support Interprocess supports Windows and all generic Unix-like systems. Additionally, platform-specific extensions are supported on select systems. The policy with those extensions is to put them behind `#[cfg]` gates and only expose on the supporting platforms, producing compile errors instead of runtime errors on platforms that have no support for those features. Four levels of support (not called *tiers* to prevent confusion with Rust target tiers, since those work completely differently) are provided by Interprocess. It would be a breaking change for a platform to be demoted, although promotions quite obviously can happen as minor or patch releases. ##### Explicit support *OSes at this level: **Windows**, **Linux**, **macOS*** - Interprocess is guaranteed to compile and succeed in running all tests – it would be a critical bug for it not to - CI, currently provided by GitHub Actions, runs on all of those platforms and displays an ugly red badge if anything is wrong on any of those systems - Certain `#[cfg]`-gated platform-specific features are supported with stable public APIs ##### Explicit support with incomplete CI *OSes at this level: **FreeBSD**, **Android*** - Interprocess is expected to compile and succeed in running all tests – it would be a bug for it not to - GitHub Actions only allows Clippy and Rustdoc to be run for those targets in CI (via cross-compilation) due to a lack of native VMs - Certain `#[cfg]`-gated platform-specific features are supported with stable public APIs ##### Explicit support without CI *OSes at this level: currently none* - Interprocess is expected to compile and succeed in running all tests – it would be a bug for it not to - Manual testing on local VMs is usually done before every release; no CI happens because those targets' standard library `.rlib`s cannot be installed via `rustup target add` - Certain `#[cfg]`-gated platform-specific features are supported with stable public APIs ##### Support by association *OSes at this level: **Dragonfly BSD**, **OpenBSD**, **NetBSD**, **Redox**, **Fuchsia**, **iOS**, **tvOS**, **watchOS*** - Interprocess is expected to compile and succeed in running all tests – it would be a bug for it not to - No manual testing is performed, and CI is unavailable because GitHub Actions does not provide it - Certain `#[cfg]`-gated platform-specific features that originate from other platforms are supported with stable public APIs because they behave here identically to how they do on an OS with a higher support level ##### Assumed support *OSes at this level: POSIX-conformant `#[cfg(unix)]` systems not listed above for which the `libc` crate compiles* - Interprocess is expected to compile and succeed in running all tests – it would be a bug for it not to - Because this level encompasses a practically infinite amount of systems, no manual testing or CI can exist ## Feature gates - **`tokio`**, *off* by default – enables support for Tokio-powered efficient asynchronous IPC. ## License This crate, along with all community contributions made to it, is dual-licensed under [MIT] and [Apache 2.0]. [MIT]: https://choosealicense.com/licenses/mit/ [Apache 2.0]: https://choosealicense.com/licenses/apache-2.0/ [blogpost]: https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html interprocess-2.2.3/examples/local_socket/sync/listener.rs000064400000000000000000000073501046102023000220040ustar 00000000000000//{ fn main() -> std::io::Result<()> { //} use { interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions, Stream}, std::io::{self, prelude::*, BufReader}, }; // Define a function that checks for errors in incoming connections. We'll use this to filter // through connections that fail on initialization for one reason or another. fn handle_error(conn: io::Result) -> Option { match conn { Ok(c) => Some(c), Err(e) => { eprintln!("Incoming connection failed: {e}"); None } } } // Pick a name. let printname = "example.sock"; let name = printname.to_ns_name::()?; // Configure our listener... let opts = ListenerOptions::new().name(name); // ...then create it. let listener = match opts.create_sync() { Err(e) if e.kind() == io::ErrorKind::AddrInUse => { // When a program that uses a file-type socket name terminates its socket server // without deleting the file, a "corpse socket" remains, which can neither be // connected to nor reused by a new listener. Normally, Interprocess takes care of // this on affected platforms by deleting the socket file when the listener is // dropped. (This is vulnerable to all sorts of races and thus can be disabled.) // // There are multiple ways this error can be handled, if it occurs, but when the // listener only comes from Interprocess, it can be assumed that its previous instance // either has crashed or simply hasn't exited yet. In this example, we leave cleanup // up to the user, but in a real application, you usually don't want to do that. eprintln!( "Error: could not start server because the socket file is occupied. Please check if {printname} is in use by another process and try again." ); return Err(e); } x => x?, }; // The syncronization between the server and client, if any is used, goes here. eprintln!("Server running at {printname}"); // Preemptively allocate a sizeable buffer for receiving at a later moment. This size should // be enough and should be easy to find for the allocator. Since we only have one concurrent // client, there's no need to reallocate the buffer repeatedly. let mut buffer = String::with_capacity(128); for conn in listener.incoming().filter_map(handle_error) { // Wrap the connection into a buffered receiver right away // so that we could receive a single line from it. let mut conn = BufReader::new(conn); println!("Incoming connection!"); // Since our client example sends first, the server should receive a line and only then // send a response. Otherwise, because receiving from and sending to a connection cannot // be simultaneous without threads or async, we can deadlock the two processes by having // both sides wait for the send buffer to be emptied by the other. conn.read_line(&mut buffer)?; // Now that the receive has come through and the client is waiting on the server's send, do // it. (`.get_mut()` is to get the sender, `BufReader` doesn't implement a pass-through // `Write`.) conn.get_mut().write_all(b"Hello from server!\n")?; // Print out the result, getting the newline for free! print!("Client answered: {buffer}"); // Clear the buffer so that the next iteration will display new data instead of messages // stacking on top of one another. buffer.clear(); } //{ Ok(()) } //} interprocess-2.2.3/examples/local_socket/sync/stream.rs000064400000000000000000000035361046102023000214540ustar 00000000000000//{ fn main() -> std::io::Result<()> { //} use { interprocess::local_socket::{prelude::*, GenericFilePath, GenericNamespaced, Stream}, std::io::{prelude::*, BufReader}, }; // Pick a name. let name = if GenericNamespaced::is_supported() { "example.sock".to_ns_name::()? } else { "/tmp/example.sock".to_fs_name::()? }; // Preemptively allocate a sizeable buffer for receiving. This size should be enough and // should be easy to find for the allocator. let mut buffer = String::with_capacity(128); // Create our connection. This will block until the server accepts our connection, but will // fail immediately if the server hasn't even started yet; somewhat similar to how happens // with TCP, where connecting to a port that's not bound to any server will send a "connection // refused" response, but that will take twice the ping, the roundtrip time, to reach the // client. let conn = Stream::connect(name)?; // Wrap it into a buffered reader right away so that we could receive a single line out of it. let mut conn = BufReader::new(conn); // Send our message into the stream. This will finish either when the whole message has been // sent or if a send operation returns an error. (`.get_mut()` is to get the sender, // `BufReader` doesn't implement pass-through `Write`.) conn.get_mut().write_all(b"Hello from client!\n")?; // We now employ the buffer we allocated prior and receive a single line, interpreting a // newline character as an end-of-file (because local sockets cannot be portably shut down), // verifying validity of UTF-8 on the fly. conn.read_line(&mut buffer)?; // Print out the result, getting the newline for free! print!("Server answered: {buffer}"); //{ Ok(()) } //} interprocess-2.2.3/examples/local_socket/tokio/listener.rs000064400000000000000000000076061046102023000221610ustar 00000000000000//{ #[cfg(not(feature = "tokio"))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } #[cfg(feature = "tokio")] #[tokio::main] async fn main() -> Result<(), Box> { //} use { interprocess::local_socket::{ tokio::{prelude::*, Stream}, GenericNamespaced, ListenerOptions, }, std::io, tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, try_join, }, }; // Describe the things we do when we've got a connection ready. async fn handle_conn(conn: Stream) -> io::Result<()> { let mut recver = BufReader::new(&conn); let mut sender = &conn; // Allocate a sizeable buffer for receiving. This size should be big enough and easy to // find for the allocator. let mut buffer = String::with_capacity(128); // Describe the send operation as sending our whole message. let send = sender.write_all(b"Hello from server!\n"); // Describe the receive operation as receiving a line into our big buffer. let recv = recver.read_line(&mut buffer); // Run both operations concurrently. try_join!(recv, send)?; // Produce our output! println!("Client answered: {}", buffer.trim()); Ok(()) } // Pick a name. let printname = "example.sock"; let name = printname.to_ns_name::()?; // Configure our listener... let opts = ListenerOptions::new().name(name); // ...and create it. let listener = match opts.create_tokio() { Err(e) if e.kind() == io::ErrorKind::AddrInUse => { // When a program that uses a file-type socket name terminates its socket server // without deleting the file, a "corpse socket" remains, which can neither be // connected to nor reused by a new listener. Normally, Interprocess takes care of // this on affected platforms by deleting the socket file when the listener is // dropped. (This is vulnerable to all sorts of races and thus can be disabled.) // // There are multiple ways this error can be handled, if it occurs, but when the // listener only comes from Interprocess, it can be assumed that its previous instance // either has crashed or simply hasn't exited yet. In this example, we leave cleanup // up to the user, but in a real application, you usually don't want to do that. eprintln!( "Error: could not start server because the socket file is occupied. Please check if {printname} is in use by another process and try again." ); return Err(e.into()); } x => x?, }; // The syncronization between the server and client, if any is used, goes here. eprintln!("Server running at {printname}"); // Set up our loop boilerplate that processes our incoming connections. loop { // Sort out situations when establishing an incoming connection caused an error. let conn = match listener.accept().await { Ok(c) => c, Err(e) => { eprintln!("There was an error with an incoming connection: {e}"); continue; } }; // Spawn new parallel asynchronous tasks onto the Tokio runtime and hand the connection // over to them so that multiple clients could be processed simultaneously in a // lightweight fashion. tokio::spawn(async move { // The outer match processes errors that happen when we're connecting to something. // The inner if-let processes errors that happen during the connection. if let Err(e) = handle_conn(conn).await { eprintln!("Error while handling connection: {e}"); } }); } } // interprocess-2.2.3/examples/local_socket/tokio/stream.rs000064400000000000000000000033731046102023000216240ustar 00000000000000//{ #[cfg(not(feature = "tokio"))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } #[cfg(feature = "tokio")] #[tokio::main] async fn main() -> Result<(), Box> { //} use { interprocess::local_socket::{ tokio::{prelude::*, Stream}, GenericFilePath, GenericNamespaced, }, tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, try_join, }, }; // Pick a name. let name = if GenericNamespaced::is_supported() { "example.sock".to_ns_name::()? } else { "/tmp/example.sock".to_fs_name::()? }; // Await this here since we can't do a whole lot without a connection. let conn = Stream::connect(name).await?; // This consumes our connection and splits it into two halves, so that we can concurrently use // both. let (recver, mut sender) = conn.split(); let mut recver = BufReader::new(recver); // Allocate a sizeable buffer for receiving. This size should be enough and should be easy to // find for the allocator. let mut buffer = String::with_capacity(128); // Describe the send operation as writing our whole string. let send = sender.write_all(b"Hello from client!\n"); // Describe the receive operation as receiving until a newline into our buffer. let recv = recver.read_line(&mut buffer); // Concurrently perform both operations. try_join!(send, recv)?; // Close the connection a bit earlier than you'd think we would. Nice practice! drop((recver, sender)); // Display the results when we're done! println!("Server answered: {}", buffer.trim()); //{ Ok(()) } //} interprocess-2.2.3/examples/named_pipe/sync/listener.rs000064400000000000000000000002111046102023000214300ustar 00000000000000//{ // TODO(2.3.0) #[cfg(not(windows))] fn main() {} #[cfg(windows)] fn main() -> std::io::Result<()> { //} //{ Ok(()) } //} interprocess-2.2.3/examples/named_pipe/sync/stream/bytes.rs000064400000000000000000000033051046102023000222330ustar 00000000000000//{ #[cfg(not(windows))] fn main() {} #[cfg(windows)] fn main() -> std::io::Result<()> { //} use { interprocess::os::windows::named_pipe::*, std::io::{prelude::*, BufReader}, }; // Preemptively allocate a sizeable buffer for receiving. This size should be enough and // should be easy to find for the allocator. let mut buffer = String::with_capacity(128); // Create our connection. This will block until the server accepts our connection, but will // fail immediately if the server hasn't even started yet; somewhat similar to how happens // with TCP, where connecting to a port that's not bound to any server will send a "connection // refused" response, but that will take twice the ping, the roundtrip time, to reach the // client. let conn = DuplexPipeStream::::connect_by_path(r"\\.\pipe\Example")?; // Wrap it into a buffered reader right away so that we could receive a single line out of it. let mut conn = BufReader::new(conn); // Send our message into the stream. This will finish either when the whole message has been // sent or if a send operation returns an error. (`.get_mut()` is to get the sender, // `BufReader` doesn't implement a pass-through `Write`.) conn.get_mut().write_all(b"Hello from client!\n")?; // We now employ the buffer we allocated prior and receive a single line, interpreting a // newline character as an end-of-file (because local sockets cannot be portably shut down), // verifying validity of UTF-8 on the fly. conn.read_line(&mut buffer)?; // Print out the result, getting the newline for free! print!("Server answered: {buffer}"); //{ Ok(()) } //} interprocess-2.2.3/examples/named_pipe/sync/stream/msg.rs000064400000000000000000000035501046102023000216750ustar 00000000000000//{ #[cfg(not(windows))] fn main() {} #[cfg(windows)] fn main() -> std::io::Result<()> { //} use {interprocess::os::windows::named_pipe::*, recvmsg::prelude::*}; // Preemptively allocate a sizeable buffer for receiving. Keep in mind that this will depend // on the specifics of the protocol you're using. let mut buffer = MsgBuf::from(Vec::with_capacity(128)); // Create our connection. This will block until the server accepts our connection, but will // fail immediately if the server hasn't even started yet; somewhat similar to how happens // with TCP, where connecting to a port that's not bound to any server will send a "connection // refused" response, but that will take twice the ping, the roundtrip time, to reach the // client. let mut conn = DuplexPipeStream::::connect_by_path(r"\\.\pipe\Example")?; // Here's our message so that we could check its length later. static MESSAGE: &[u8] = b"Hello from client!"; // Send the message, getting the amount of bytes that was actually sent in return. let sent = conn.send(MESSAGE)?; assert_eq!(sent, MESSAGE.len()); // If it doesn't match, something's seriously wrong. // Use the reliable message receive API, which gets us a `RecvResult` from the // `reliable_recv_msg` module. conn.recv_msg(&mut buffer, None)?; // Convert the data that's been received into a string. This checks for UTF-8 validity, and if // invalid characters are found, a new buffer is allocated to house a modified version of the // received data, where decoding errors are replaced with those diamond-shaped question mark // U+FFFD REPLACEMENT CHARACTER thingies: �. let received_string = String::from_utf8_lossy(buffer.filled_part()); // Print out the result! println!("Server answered: {received_string}"); //{ Ok(()) } //} interprocess-2.2.3/examples/named_pipe/tokio/listener.rs000064400000000000000000000062051046102023000216120ustar 00000000000000//{ #[cfg(not(all(windows, feature = "tokio")))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } #[cfg(all(windows, feature = "tokio"))] #[tokio::main] async fn main() -> Result<(), Box> { //} use { interprocess::os::windows::named_pipe::{pipe_mode, tokio::*, PipeListenerOptions}, std::{io, path::Path}, tokio::{ io::{AsyncReadExt, AsyncWriteExt}, try_join, }, }; // Describe the things we do when we've got a connection ready. async fn handle_conn(conn: DuplexPipeStream) -> io::Result<()> { // Split the connection into two halves to process received and sent data concurrently. let (mut recver, mut sender) = conn.split(); // Allocate a sizeable buffer for receiving. This size should be enough and should be easy // to find for the allocator. let mut buffer = String::with_capacity(128); // Describe the send operation as first sending our whole message, and then shutting down // the send half to send an EOF to help the other side determine the end of the // transmission. let send = async { sender.write_all(b"Hello from server!").await?; sender.shutdown().await?; Ok(()) }; // Describe the receive operation as receiving into our big buffer. let recv = recver.read_to_string(&mut buffer); // Run both the send-and-invoke-EOF operation and the receive operation concurrently. try_join!(recv, send)?; // Dispose of our connection right now and not a moment later because I want to! drop((recver, sender)); // Produce our output! println!("Client answered: {}", buffer.trim()); Ok(()) } static PIPE_NAME: &str = "Example"; // Create our listener. let listener = PipeListenerOptions::new() .path(Path::new(PIPE_NAME)) .create_tokio_duplex::()?; // The syncronization between the server and client, if any is used, goes here. eprintln!(r"Server running at \\.\pipe\{PIPE_NAME}"); // Set up our loop boilerplate that processes our incoming connections. loop { // Sort out situations when establishing an incoming connection caused an error. let conn = match listener.accept().await { Ok(c) => c, Err(e) => { eprintln!("There was an error with an incoming connection: {e}"); continue; } }; // Spawn new parallel asynchronous tasks onto the Tokio runtime and hand the connection // over to them so that multiple clients could be processed simultaneously in a // lightweight fashion. tokio::spawn(async move { // The outer match processes errors that happen when we're connecting to something. // The inner if-let processes errors that happen during the connection. if let Err(e) = handle_conn(conn).await { eprintln!("error while handling connection: {e}"); } }); } } // interprocess-2.2.3/examples/named_pipe/tokio/stream/bytes.rs000064400000000000000000000035471046102023000224140ustar 00000000000000//{ #[cfg(not(all(windows, feature = "tokio")))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } #[cfg(all(windows, feature = "tokio"))] #[tokio::main] async fn main() -> Result<(), Box> { //} use { interprocess::os::windows::named_pipe::{pipe_mode, tokio::*}, tokio::{ io::{AsyncReadExt, AsyncWriteExt}, try_join, }, }; // Await this here since we can't do a whole lot without a connection. let conn = DuplexPipeStream::::connect_by_path(r"\\.\pipe\Example").await?; // This consumes our connection and splits it into two owned halves, so that we could // concurrently act on both. Take care not to use the .split() method from the futures crate's // AsyncReadExt. let (mut recver, mut sender) = conn.split(); // Preemptively allocate a sizeable buffer for receiving. This size should be enough and // should be easy to find for the allocator. let mut buffer = String::with_capacity(128); // Describe the send operation as sending our whole string, waiting for that to complete, and // then shutting down the send half, which sends an EOF to the other end to help it determine // where the message ends. let send = async { sender.write_all(b"Hello from client!").await?; sender.shutdown().await?; Ok(()) }; // Describe the receive operation as receiving until EOF into our big buffer. let recv = recver.read_to_string(&mut buffer); // Concurrently perform both operations: send-and-invoke-EOF and receive. try_join!(send, recv)?; // Get rid of those here to close the receive half too. drop((recver, sender)); // Display the results when we're done! println!("Server answered: {}", buffer.trim()); //{ Ok(()) } //} interprocess-2.2.3/examples/named_pipe/tokio/stream/msg.rs000064400000000000000000000004211046102023000220400ustar 00000000000000//{ // TODO(2.3.0)..? #[cfg(not(all(windows, feature = "tokio")))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } #[cfg(all(windows, feature = "tokio"))] fn main() -> std::io::Result<()> { //} //{ Ok(()) } //} interprocess-2.2.3/examples/unnamed_pipe/sync/main.rs000064400000000000000000000004331046102023000211000ustar 00000000000000mod side_a; mod side_b; use std::{io, sync::mpsc, thread}; fn main() -> io::Result<()> { let (htx, hrx) = mpsc::sync_channel(1); let jh = thread::spawn(move || side_a::emain(htx)); let handle = hrx.recv().unwrap(); side_b::emain(handle)?; jh.join().unwrap() } interprocess-2.2.3/examples/unnamed_pipe/sync/side_a.rs000064400000000000000000000025151046102023000214030ustar 00000000000000//{ use std::{io, os, sync::mpsc}; #[cfg(windows)] type Handle = os::windows::io::OwnedHandle; #[cfg(unix)] type Handle = os::unix::io::OwnedFd; pub(crate) fn emain(handle_sender: mpsc::SyncSender) -> io::Result<()> { //} use { interprocess::unnamed_pipe::pipe, std::io::{prelude::*, BufReader}, }; // Create the unnamed pipe, yielding a sender and a receiver. let (tx, rx) = pipe()?; // Let's extract the raw handle or file descriptor of the sender. Note that `OwnedHandle` and // `OwnedFd` both implement `From`. let txh = tx.into(); // Now deliver `txh` to the child process. This may be done by starting it here with a // command-line argument or via stdin. This works because child processes inherit handles and // file descriptors to unnamed pipes. You can also use a different platform-specific way of // transferring handles or file descriptors across a process boundary. //{ handle_sender.send(txh).unwrap(); //} let mut buf = String::with_capacity(128); // We'd like to receive a line, so buffer our input. let mut rx = BufReader::new(rx); // Receive the line from the other process. rx.read_line(&mut buf)?; assert_eq!(buf.trim(), "Hello from side B!"); //{ Ok(()) } #[allow(dead_code)] fn main() {} //} interprocess-2.2.3/examples/unnamed_pipe/sync/side_b.rs000064400000000000000000000015031046102023000214000ustar 00000000000000//{ use std::{io, os}; #[cfg(windows)] type Handle = os::windows::io::OwnedHandle; #[cfg(unix)] type Handle = os::unix::io::OwnedFd; pub(crate) fn emain(handle: Handle) -> io::Result<()> { //} use {interprocess::unnamed_pipe, std::io::prelude::*}; // `handle` here is an `OwnedHandle` or an `OwnedFd` from the standard library. Those // implement `FromRawHandle` and `FromRawFd` respectively. The actual value can be transferred // via a command-line parameter since it's numerically equal to the value obtained in the // parent process via `OwnedHandle::from()`/`OwnedFd::from()` thanks to handle inheritance. let mut tx = unnamed_pipe::Sender::from(handle); // Send our message to the other side. tx.write_all(b"Hello from side B!\n")?; //{ Ok(()) } #[allow(dead_code)] fn main() {} //} interprocess-2.2.3/examples/unnamed_pipe/tokio/main.rs000064400000000000000000000007761046102023000212630ustar 00000000000000#[cfg(feature = "tokio")] mod side_a; #[cfg(feature = "tokio")] mod side_b; #[cfg(feature = "tokio")] #[tokio::main] async fn main() -> std::io::Result<()> { use tokio::{sync::oneshot, task}; let (htx, hrx) = oneshot::channel(); let jh = task::spawn(side_a::emain(htx)); let handle = hrx.await.unwrap(); side_b::emain(handle).await?; jh.await.unwrap() } #[cfg(not(feature = "tokio"))] fn main() { eprintln!("This example is not available when the Tokio feature is disabled."); } interprocess-2.2.3/examples/unnamed_pipe/tokio/side_a.rs000064400000000000000000000026141046102023000215540ustar 00000000000000//{ use { std::{io, os}, tokio::sync::oneshot, }; #[cfg(windows)] type Handle = os::windows::io::OwnedHandle; #[cfg(unix)] type Handle = os::unix::io::OwnedFd; pub(crate) async fn emain(handle_sender: oneshot::Sender) -> io::Result<()> { //} use { interprocess::unnamed_pipe::tokio::pipe, tokio::io::{AsyncBufReadExt, BufReader}, }; // Create the unnamed pipe, yielding a sender and a receiver. let (tx, rx) = pipe()?; // Let's extract the raw handle or file descriptor of the sender. Note that `OwnedHandle` and // `OwnedFd` both implement `TryFrom`. let txh = tx.try_into()?; // Now deliver `txh` to the child process. This may be done by starting it here with a // command-line argument or via stdin. This works because child processes inherit handles and // file descriptors to unnamed pipes. You can also use a different platform-specific way of // transferring handles or file descriptors across a process boundary. //{ handle_sender.send(txh).unwrap(); //} let mut buf = String::with_capacity(128); // We'd like to receive a line, so buffer our input. let mut rx = BufReader::new(rx); // Receive the line from the other process. rx.read_line(&mut buf).await?; assert_eq!(buf.trim(), "Hello from side B!"); //{ Ok(()) } #[allow(dead_code)] fn main() {} //} interprocess-2.2.3/examples/unnamed_pipe/tokio/side_b.rs000064400000000000000000000015571046102023000215620ustar 00000000000000//{ use std::{io, os}; #[cfg(windows)] type Handle = os::windows::io::OwnedHandle; #[cfg(unix)] type Handle = os::unix::io::OwnedFd; pub(crate) async fn emain(handle: Handle) -> io::Result<()> { //} use {interprocess::unnamed_pipe, tokio::io::AsyncWriteExt}; // `handle` here is an `OwnedHandle` or an `OwnedFd` from the standard library. Those // implement `FromRawHandle` and `FromRawFd` respectively. The actual value can be transferred // via a command-line parameter since it's numerically equal to the value obtained in the // parent process via `OwnedHandle::try_from()`/`OwnedFd::try_from()` thanks to handle // inheritance. let mut tx = unnamed_pipe::tokio::Sender::try_from(handle)?; // Send our message to the other side. tx.write_all(b"Hello from side B!\n").await?; //{ Ok(()) } #[allow(dead_code)] fn main() {} //} interprocess-2.2.3/rustfmt.toml000064400000000000000000000004411046102023000147500ustar 00000000000000unstable_features = true max_width = 98 # Rustfmt is a heap of bugs disguised as functional software fn_single_line = true imports_granularity = "One" group_imports = "One" overflow_delimited_expr = true reorder_modules = false use_field_init_shorthand = true use_small_heuristics = "Max" interprocess-2.2.3/src/atomic_enum.rs000064400000000000000000000032501046102023000160050ustar 00000000000000#![allow(dead_code)] use std::{ fmt::{self, Debug, Formatter}, marker::PhantomData, mem::{transmute_copy, ManuallyDrop}, sync::atomic::{ AtomicU8, Ordering::{self, *}, }, }; pub struct AtomicEnum(AtomicU8, PhantomData); impl AtomicEnum { #[inline] pub fn new(val: E) -> Self { Self(AtomicU8::new(val.to_u8()), PhantomData) } #[inline] pub fn load(&self, ordering: Ordering) -> E { let v = self.0.load(ordering); unsafe { E::from_u8(v) } } #[inline] pub fn store(&self, val: E, ordering: Ordering) { self.0.store(val.to_u8(), ordering) } #[inline] pub fn compare_exchange( &self, current: E, new: E, success: Ordering, failure: Ordering, ) -> Result { let unwr = |v: u8| unsafe { E::from_u8(v) }; self.0 .compare_exchange(current.to_u8(), new.to_u8(), success, failure) .map(unwr) .map_err(unwr) } #[inline] pub fn get_mut(&mut self) -> &mut E { // SAFETY: ReprU8 unsafe { &mut *self.0.as_ptr().cast() } } } impl Debug for AtomicEnum { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let val: E = unsafe { E::from_u8(self.0.load(SeqCst)) }; f.debug_tuple("AtomicEnum").field(&val).finish() } } /// # Safety /// Must be `#[repr(u8)]`. pub unsafe trait ReprU8: Sized { #[inline(always)] fn to_u8(self) -> u8 { let slf = ManuallyDrop::new(self); unsafe { transmute_copy(&slf) } } #[inline(always)] unsafe fn from_u8(v: u8) -> Self { unsafe { transmute_copy(&v) } } } interprocess-2.2.3/src/bound_util.rs000064400000000000000000000035341046102023000156560ustar 00000000000000//! Trait bound utilities. use std::io::prelude::*; #[cfg(feature = "tokio")] use tokio::io::{AsyncRead as TokioAsyncRead, AsyncWrite as TokioAsyncWrite}; pub(crate) trait Is {} impl Is for T {} macro_rules! bound_util { (#[doc = $doc:literal] $trtname:ident of $otrt:ident with $aty:ident mtd $mtd:ident) => { #[doc = $doc] pub trait $trtname { #[doc(hidden)] #[allow(private_bounds)] type $aty<'a>: $otrt + Is<&'a Self> where Self: 'a; #[doc = concat!( "Returns `self` with the guarantee that `&Self` implements `", stringify!($otrt), "` encoded in a way which is visible to Rust's type system.", )] fn $mtd(&self) -> Self::$aty<'_>; } impl $trtname for T where for<'a> &'a T: $otrt, { type $aty<'a> = &'a Self where Self: 'a; #[inline(always)] fn $mtd(&self) -> Self::$aty<'_> { self } } }; ($(#[doc = $doc:literal] $trtname:ident of $otrt:ident with $aty:ident mtd $mtd:ident)+) => {$( bound_util!(#[doc = $doc] $trtname of $otrt with $aty mtd $mtd); )+}; } bound_util! { /// [`Read`] by reference. RefRead of Read with Read mtd as_read /// [`Write`] by reference. RefWrite of Write with Write mtd as_write } #[cfg(feature = "tokio")] bound_util! { /// [Tokio's `AsyncRead`](TokioAsyncRead) by reference. RefTokioAsyncRead of TokioAsyncRead with Read mtd as_tokio_async_read /// [Tokio's `AsyncWrite`](TokioAsyncWrite) by reference. RefTokioAsyncWrite of TokioAsyncWrite with Write mtd as_tokio_async_write } interprocess-2.2.3/src/error.rs000064400000000000000000000174411046102023000146450ustar 00000000000000//! Generic error types used throughout the crate. use std::{ error::Error, fmt::{self, Debug, Display, Formatter, Write}, io, }; /// General error type for fallible constructors. /// /// In Interprocess, many types feature conversions to and from handles/file descriptors and types /// from the standard library. Many of those conversions are fallible because the semantic mapping /// between the source and destination types is not always 1:1, with various invariants that need to /// be upheld and which are always queried for. With async types, this is further complicated: /// runtimes typically need to register OS objects in their polling/completion infrastructure to use /// them asynchronously. /// /// All those conversion have one thing in common: they consume ownership of one object and return /// ownership of its new form. If the conversion fails, it would be invaluably useful in some cases /// to return ownership of the original object back to the caller, so that it could use it in some /// other way. The `source` field allows for that, but also reserves the callee's freedom not to do /// so. (Hey, it's not like I *wanted* it to be like this, Tokio just doesn't have `.try_clone()` /// and returns [`io::Error`]. Oh well, unwrapping's always an option.) /// /// Many (but not all) of those conversions also have an OS error they can attribute the failure to. /// /// Additionally, some conversions have an additional "details" error field that contains extra /// infromation about the error. That is typically an enumeration that specifies which stage of the /// conversion the OS error happened on. #[derive(Debug)] pub struct ConversionError { /// Extra information about the error. pub details: E, /// The underlying OS error, if any. pub cause: Option, /// Ownership of the input of the conversion. pub source: Option, } impl ConversionError { /// Constructs an error value without an OS cause and with default contents for the "details" /// field. pub fn from_source(source: S) -> Self { Self { details: Default::default(), cause: None, source: Some(source) } } /// Constructs an error value that doesn't return input ownership, with default contents for the /// "details" field and an OS cause. pub fn from_cause(cause: io::Error) -> Self { Self { details: Default::default(), cause: Some(cause), source: None } } /// Constructs an error value from a given OS cause, filling the "details" field with its /// default value. pub fn from_source_and_cause(source: S, cause: io::Error) -> Self { Self { details: Default::default(), cause: Some(cause), source: Some(source) } } } impl ConversionError { /// Constructs an error value without an OS cause. pub fn from_source_and_details(source: S, details: E) -> Self { Self { details, cause: None, source: Some(source) } } /// Constructs an error value that doesn't return input ownership. pub fn from_cause_and_details(cause: io::Error, details: E) -> Self { Self { details, cause: Some(cause), source: None } } /// Maps the type with which ownership over the input is returned using the given closure. /// /// This utility is mostly used in the crate's internals. pub fn map_source(self, f: impl FnOnce(S) -> Sb) -> ConversionError { ConversionError { details: self.details, cause: self.cause, source: self.source.map(f) } } /// Maps the type with which ownership over the input is returned using the given closure, also /// allowing it to drop the ownership. /// /// This utility is mostly used in the crate's internals. pub fn try_map_source(self, f: impl FnOnce(S) -> Option) -> ConversionError { ConversionError { details: self.details, cause: self.cause, source: self.source.and_then(f), } } } impl ConversionError { /// Boxes the error into an `io::Error`. pub fn to_io_error(&self) -> io::Error { io::Error::other(self.to_string()) } } /// Boxes the error into an `io::Error`, dropping the retained file descriptor in the process. impl From> for io::Error { fn from(e: ConversionError) -> Self { e.to_io_error() } } impl Default for ConversionError { fn default() -> Self { Self { details: Default::default(), cause: None, source: None } } } impl Display for ConversionError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut snp = FormatSnooper::new(f); write!(snp, "{}", &self.details)?; if let Some(e) = &self.cause { if snp.anything_written() { f.write_str(": ")?; } Display::fmt(e, f)?; } Ok(()) } } impl Error for ConversionError { #[inline] #[allow(clippy::as_conversions)] fn source(&self) -> Option<&(dyn Error + 'static)> { self.cause.as_ref().map(|r| r as &_) } } /// Thunk type used to specialize on the type of `details`, preventing ": " from being at the /// beginning of the output with nothing preceding it. struct FormatSnooper<'a, 'b> { formatter: &'b mut Formatter<'a>, anything_written: bool, } impl<'a, 'b> FormatSnooper<'a, 'b> { fn new(formatter: &'b mut Formatter<'a>) -> Self { Self { formatter, anything_written: false } } fn anything_written(&self) -> bool { self.anything_written } } impl Write for FormatSnooper<'_, '_> { fn write_str(&mut self, s: &str) -> fmt::Result { if !s.is_empty() { self.anything_written = true; self.formatter.write_str(s) } else { Ok(()) } } } /// Marker type used as the generic argument of [`ConversionError`] to denote that there are no /// error details. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct NoDetails; impl Display for NoDetails { fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result { Ok(()) // } } /// Error type of `TryFrom` conversions. #[cfg(windows)] #[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))] pub type FromHandleError = ConversionError; /// Error type of `TryFrom` conversions. #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] pub type FromFdError = ConversionError; /// Error type of `.reunite()` on splittable stream types, indicating that the two halves belong to /// different streams. #[derive(Debug)] pub struct ReuniteError { /// Ownership of the receive half. pub rh: R, /// Ownership of the send half. pub sh: S, } impl ReuniteError { /// Maps the halves of the stream using the given closures. #[inline] pub fn map_halves, NS: From>( self, fr: impl FnOnce(R) -> NR, fs: impl FnOnce(S) -> NS, ) -> ReuniteError { let Self { rh, sh } = self; ReuniteError { rh: fr(rh), sh: fs(sh) } } /// Maps the halves of the stream using the [`From`] trait. /// /// This is useful when implementing wrappers around stream types. #[inline] pub fn convert_halves, NS: From>(self) -> ReuniteError { self.map_halves(From::from, From::from) } } impl Display for ReuniteError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("attempt to reunite stream halves that come from different streams") } } impl Error for ReuniteError {} /// Result type of `.reunite()` on splittable stream types. pub type ReuniteResult = Result>; interprocess-2.2.3/src/lib.rs000064400000000000000000000031711046102023000142550ustar 00000000000000#![doc = include_str!("../README.md")] #![cfg_attr(feature = "doc_cfg", feature(doc_cfg))] // If this was in Cargo.toml, it would cover examples as well #![warn( missing_docs, clippy::panic_in_result_fn, clippy::missing_assert_message, clippy::indexing_slicing, clippy::arithmetic_side_effects )] mod platform_check; // TODO(2.3.0) inspect panic points #[macro_use] mod macros; pub mod bound_util; pub mod error; pub mod local_socket; pub mod unnamed_pipe; /// Platform-specific functionality for various interprocess communication primitives. /// /// This module houses two modules: `unix` and `windows`, although only one at a time will be /// visible, depending on which platform the documentation was built on. If you're using /// [Docs.rs](https://docs.rs/interprocess/latest/interprocess), you can view the documentation for /// Windows, macOS, Linux and FreeBSD using the Platform menu on the Docs.rs-specific header bar at /// the top of the page. Docs.rs builds also have the nightly-only `doc_cfg` feature enabled by /// default, with which everything platform-specific has a badge next to it which specifies the /// `cfg(...)` conditions for that item to be available. pub mod os { #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] pub mod unix; #[cfg(windows)] #[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))] pub mod windows; } mod try_clone; pub use try_clone::*; mod atomic_enum; mod misc; pub(crate) use {atomic_enum::*, misc::*}; #[cfg(test)] #[path = "../tests/index.rs"] #[allow(clippy::unwrap_used, clippy::arithmetic_side_effects, clippy::indexing_slicing)] mod tests; interprocess-2.2.3/src/local_socket/concurrency_detector.rs000064400000000000000000000036211046102023000223740ustar 00000000000000use std::{ fmt::{self, Debug, Formatter}, marker::PhantomData, sync::atomic::{AtomicBool, Ordering::*}, }; pub struct ConcurrencyDetector(AtomicBool, PhantomData); impl ConcurrencyDetector { pub const fn new() -> Self { Self(AtomicBool::new(false), PhantomData) } #[track_caller] #[must_use] pub fn lock(&self) -> LockDetectorGuard<'_> { if self.0.compare_exchange(false, true, Acquire, Relaxed).is_err() { concurrency_detected(S::NAME, S::WOULD_ACTUALLY_DEADLOCK); } LockDetectorGuard(&self.0) } } #[cold] #[track_caller] fn concurrency_detected(primname: &str, deadlock: bool) -> ! { let reason = if deadlock { "because it would have caused a deadlock" } else { "to avoid portability issues" }; panic!( "\ concurrent I/O with a {primname} attempted – this leads to deadlocks due to the synchronization \ used by named pipes on Windows internally, and was prevented {reason}", ) } impl Debug for ConcurrencyDetector { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("ConcurrencyDetector") .field("locked", &self.0) .field("primname", &M::NAME) .field("would_actually_deadlock", &M::WOULD_ACTUALLY_DEADLOCK) .finish() } } pub trait ConcurrencyDetectionSite { const NAME: &'static str; const WOULD_ACTUALLY_DEADLOCK: bool; } #[derive(Default)] pub struct LocalSocketSite; impl ConcurrencyDetectionSite for LocalSocketSite { const NAME: &'static str = "local socket"; // Concurrency detection for named pipes happens within named pipes. const WOULD_ACTUALLY_DEADLOCK: bool = false; } pub struct LockDetectorGuard<'ld>(&'ld AtomicBool); impl Drop for LockDetectorGuard<'_> { #[inline] fn drop(&mut self) { self.0.store(false, Release) } } interprocess-2.2.3/src/local_socket/enumdef.rs000064400000000000000000000037601046102023000176000ustar 00000000000000/// Only dispatches with `&self` and `&mut self`. macro_rules! dispatch { (@$arm:ident $nm:ident $e:expr) => {{ let mut _arm2 = $arm; let $nm = &mut _arm2; $e }}; ($ty:ident: $nm:ident in $var:expr => $e:expr) => {{ match $var { #[cfg(windows)] $ty::NamedPipe(arm) => dispatch!(@arm $nm $e), #[cfg(unix)] $ty::UdSocket(arm) => dispatch!(@arm $nm $e), } }}; } macro_rules! mkenum { ($(#[$($attr:tt)+])* $pref:literal $nm:ident) => { $(#[$($attr)+])* pub enum $nm { /// Makes use of Windows named pipes. /// /// Click the struct name in the parentheses to learn more. #[cfg(windows)] #[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))] NamedPipe(np_impl::$nm), /// Makes use of Unix domain sockets. /// /// Click the struct name in the parentheses to learn more. #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] UdSocket(uds_impl::$nm), } impl $crate::Sealed for $nm {} #[cfg(windows)] #[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))] impl From for $nm { fn from(x: np_impl::$nm) -> Self { Self::NamedPipe(x) } } #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] impl From for $nm { fn from(x: uds_impl::$nm) -> Self { Self::UdSocket(x) } } impl std::fmt::Debug for $nm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut dt = f.debug_tuple(concat!($pref, stringify!($nm))); dispatch!($nm: i in self => dt.field(&i)); dt.finish() } } }; ($(#[$($attr:tt)+])* $nm:ident) => { mkenum!($(#[$($attr)+])* "" $nm); }; } interprocess-2.2.3/src/local_socket/listener/enum.rs000064400000000000000000000065221046102023000207450ustar 00000000000000#[cfg(unix)] use crate::os::unix::uds_local_socket as uds_impl; #[cfg(windows)] use crate::os::windows::named_pipe::local_socket as np_impl; use { super::{options::ListenerOptions, r#trait}, crate::local_socket::{ListenerNonblockingMode, Stream}, std::{io, iter::FusedIterator}, }; impmod! {local_socket::dispatch_sync as dispatch} mkenum!( /// Local socket server, listening for connections. /// /// This struct is created by [`ListenerOptions`](super::options::ListenerOptions). /// /// # Name reclamation /// *This section only applies to Unix domain sockets.* /// /// When a Unix domain socket listener is closed, its associated socket file is not automatically /// deleted. Instead, it remains on the filesystem in a zombie state, neither accepting connections /// nor allowing a new listener to reuse it – [`create_sync()`] will return /// [`AddrInUse`](io::ErrorKind::AddrInUse) unless it is deleted manually. /// /// Interprocess implements *automatic name reclamation* via: when the local socket listener is /// dropped, it performs [`std::fs::remove_file()`] (i.e. `unlink()`) with the path that was /// originally passed to [`create_sync()`], allowing for subsequent reuse of the local socket name. /// /// If the program crashes in a way that doesn't unwind the stack, the deletion will not occur and /// the socket file will linger on the filesystem, in which case manual deletion will be necessary. /// Identially, the automatic name reclamation mechanism can be opted out of via /// [`.do_not_reclaim_name_on_drop()`](trait::Listener::do_not_reclaim_name_on_drop) on the listener /// or [`.reclaim_name(false)`](super::options::ListenerOptions::reclaim_name) on the builder. /// /// Note that the socket file can be unlinked by other programs at any time, retaining the inode the /// listener is bound to but making it inaccessible to peers if it was at its last hardlink. If that /// happens and another listener takes the same path before the first one performs name reclamation, /// the socket file deletion wouldn't correspond to the listener being closed, instead deleting the /// socket file of the second listener. If the second listener also performs name reclamation, the /// ensuing deletion will silently fail. Due to the awful design of Unix, this issue cannot be /// mitigated. /// /// [`create_sync()`]: super::options::ListenerOptions::create_sync /// /// # Examples /// /// ## Basic server /// ```no_run #[doc = doctest_file::include_doctest!("examples/local_socket/sync/listener.rs")] /// ``` Listener); impl r#trait::Listener for Listener { type Stream = Stream; #[inline] fn from_options(options: ListenerOptions<'_>) -> io::Result { dispatch::from_options(options) } #[inline] fn accept(&self) -> io::Result { dispatch!(Self: x in self => x.accept()).map(Stream::from) } #[inline] fn set_nonblocking(&self, nonblocking: ListenerNonblockingMode) -> io::Result<()> { dispatch!(Self: x in self => x.set_nonblocking(nonblocking)) } #[inline] fn do_not_reclaim_name_on_drop(&mut self) { dispatch!(Self: x in self => x.do_not_reclaim_name_on_drop()) } } impl Iterator for Listener { type Item = io::Result; #[inline(always)] fn next(&mut self) -> Option { Some(r#trait::Listener::accept(self)) } } impl FusedIterator for Listener {} interprocess-2.2.3/src/local_socket/listener/options.rs000064400000000000000000000070461046102023000214760ustar 00000000000000#[cfg(feature = "tokio")] use crate::local_socket::tokio::Listener as TokioListener; #[cfg(windows)] use crate::os::windows::security_descriptor::SecurityDescriptor; use { crate::{ local_socket::{traits, Listener, ListenerNonblockingMode, Name}, Sealed, TryClone, }, std::io, }; /// A builder for [local socket listeners](traits::Listener), including [`Listener`]. #[derive(Debug)] pub struct ListenerOptions<'n> { pub(crate) name: Name<'n>, pub(crate) nonblocking: ListenerNonblockingMode, pub(crate) reclaim_name: bool, #[cfg(unix)] pub(crate) mode: Option, #[cfg(windows)] pub(crate) security_descriptor: Option, } impl Sealed for ListenerOptions<'_> {} impl TryClone for ListenerOptions<'_> { fn try_clone(&self) -> io::Result { Ok(Self { name: self.name.clone(), nonblocking: self.nonblocking, reclaim_name: self.reclaim_name, #[cfg(unix)] mode: self.mode, #[cfg(windows)] security_descriptor: self .security_descriptor .as_ref() .map(TryClone::try_clone) .transpose()?, }) } } /// Creation. impl ListenerOptions<'_> { /// Creates an options table with default values. #[inline] pub fn new() -> Self { Self { name: Name::invalid(), nonblocking: ListenerNonblockingMode::Neither, reclaim_name: true, #[cfg(unix)] mode: None, #[cfg(windows)] security_descriptor: None, } } } /// Option setters. impl<'n> ListenerOptions<'n> { builder_setters! { /// Sets the name the server will listen on. name: Name<'n>, /// Selects the nonblocking mode to be used by the listener. /// /// The default value is `Neither`. nonblocking: ListenerNonblockingMode, /// Sets whether [name reclamation](Listener#name-reclamation) is to happen or not. /// /// This is enabled by default. reclaim_name: bool, } } /// Listener constructors. impl ListenerOptions<'_> { /// Creates a [`Listener`], binding it to the specified local socket name. /// /// On platforms where there are multiple available implementations, this dispatches to the /// appropriate implementation based on where the name points to. #[inline] pub fn create_sync(self) -> io::Result { self.create_sync_as::() } /// Creates the given [type of listener](traits::Listener), binding it to the specified local /// socket name. #[inline] pub fn create_sync_as(self) -> io::Result { L::from_options(self) } /// Creates a [`Listener`](TokioListener), binding it to the specified local socket name. /// /// On platforms where there are multiple available implementations, this dispatches to the /// appropriate implementation based on where the name points to. #[inline] #[cfg(feature = "tokio")] pub fn create_tokio(self) -> io::Result { self.create_tokio_as::() } /// Creates the given [type of listener](traits::tokio::Listener), binding it to the specified /// local socket name. #[inline] #[cfg(feature = "tokio")] pub fn create_tokio_as(self) -> io::Result { L::from_options(self) } } impl Default for ListenerOptions<'_> { #[inline] fn default() -> Self { Self::new() } } interprocess-2.2.3/src/local_socket/listener/trait.rs000064400000000000000000000100221046102023000211120ustar 00000000000000use { crate::{ local_socket::{stream::r#trait::Stream, ListenerOptions}, Sealed, }, std::{io, iter::FusedIterator}, }; /// Local socket server implementations. /// /// Types on which this trait is implemented are variants of the /// [`Listener` enum](super::enum::Listener). In addition, it is implemented on `Listener` itself, /// which makes it a trait object of sorts. See its documentation for more on the semantics of the /// methods seen here. #[allow(private_bounds)] pub trait Listener: Iterator> + FusedIterator + Send + Sync + Sized + Sealed { /// The stream type associated with this listener. type Stream: Stream; /// Creates a socket server using the specified options. fn from_options(options: ListenerOptions<'_>) -> io::Result; /// Listens for incoming connections to the socket, blocking until a client is connected. /// /// See [`.incoming()`](ListenerExt::incoming) for a convenient way to create a main loop for a /// server. fn accept(&self) -> io::Result; /// Enables or disables the nonblocking mode for the listener. By default, it is disabled. /// /// In the `Accept` and `Both` nonblocking modes, calling [`.accept()`] and iterating through /// [`.incoming()`] will immediately return a [`WouldBlock`](io::ErrorKind::WouldBlock) error /// if there is no client attempting to connect at the moment instead of blocking until one /// arrives. /// /// In the `Stream` and `Both` nonblocking modes, the resulting stream will have nonblocking /// mode enabled. /// /// [`.accept()`]: Listener::accept /// [`.incoming()`]: ListenerExt::incoming fn set_nonblocking(&self, nonblocking: ListenerNonblockingMode) -> io::Result<()>; /// Disables [name reclamation](super::enum::Listener#name-reclamation) on the listener. fn do_not_reclaim_name_on_drop(&mut self); } /// The manner in which a [listener](Listener) is to be nonblocking. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum ListenerNonblockingMode { /// Neither `.accept()` nor the resulting stream are to have nonblocking semantics. Neither, /// `.accept()` will be nonblocking, but the resulting stream will not. Accept, /// The resulting stream will be nonblocking, but `.accept()` will not. Stream, /// Both `.accept()` and the resulting stream are to have nonblocking semantics. Both, } impl ListenerNonblockingMode { /// Returns `true` if `self` prescribes nonblocking `.accept()`, `false` otherwise. #[inline] pub const fn accept_nonblocking(self) -> bool { matches!(self, Self::Accept | Self::Both) } /// Returns `true` if `self` prescribes nonblocking streams, `false` otherwise. #[inline] pub const fn stream_nonblocking(self) -> bool { matches!(self, Self::Stream | Self::Both) } } unsafe impl crate::ReprU8 for ListenerNonblockingMode {} /// Methods derived from the interface of [`Listener`]. pub trait ListenerExt: Listener { /// Creates an infinite iterator which calls [`.accept()`](Listener::accept) with each /// iteration. Used together with `for` loops to conveniently create a main loop for a /// socket server. #[inline] fn incoming(&self) -> Incoming<'_, Self> { self.into() } } impl ListenerExt for T {} /// An infinite iterator over incoming client connections of a [`Listener`]. /// /// This iterator is created by the [`incoming()`](ListenerExt::incoming) method on /// [`ListenerExt`] – see its documentation for more. #[derive(Debug)] pub struct Incoming<'a, L> { listener: &'a L, } impl<'a, L: Listener> From<&'a L> for Incoming<'a, L> { fn from(listener: &'a L) -> Self { Self { listener } } } impl Iterator for Incoming<'_, L> { type Item = io::Result; fn next(&mut self) -> Option { Some(self.listener.accept()) } #[inline] fn size_hint(&self) -> (usize, Option) { (usize::MAX, None) } } impl FusedIterator for Incoming<'_, L> {} interprocess-2.2.3/src/local_socket/name/inner.rs000064400000000000000000000044041046102023000202040ustar 00000000000000use std::borrow::Cow; #[cfg(unix)] use std::ffi::OsStr; #[cfg(windows)] use widestring::U16CStr; #[derive(Clone, Debug, PartialEq, Eq)] #[allow(clippy::enum_variant_names)] pub(crate) enum NameInner<'s> { #[cfg(windows)] NamedPipe(Cow<'s, U16CStr>), #[cfg(unix)] UdSocketPath(Cow<'s, OsStr>), #[cfg(unix)] UdSocketPseudoNs(Cow<'s, OsStr>), #[cfg(any(target_os = "linux", target_os = "android"))] UdSocketNs(Cow<'s, [u8]>), } impl Default for NameInner<'_> { fn default() -> Self { #[cfg(windows)] { Self::NamedPipe(Cow::default()) } #[cfg(unix)] { Self::UdSocketPath(Cow::default()) } } } macro_rules! map_cow { ($nm:ident in $var:expr => $e:expr) => { match $var { #[cfg(windows)] NameInner::NamedPipe($nm) => NameInner::NamedPipe($e), #[cfg(unix)] NameInner::UdSocketPath($nm) => NameInner::UdSocketPath($e), #[cfg(unix)] NameInner::UdSocketPseudoNs($nm) => NameInner::UdSocketPseudoNs($e), #[cfg(any(target_os = "linux", target_os = "android"))] NameInner::UdSocketNs($nm) => NameInner::UdSocketNs($e), } }; } impl NameInner<'_> { pub const fn is_namespaced(&self) -> bool { match self { #[cfg(windows)] Self::NamedPipe(..) => true, #[cfg(unix)] Self::UdSocketPath(..) => false, #[cfg(unix)] Self::UdSocketPseudoNs(..) => false, #[cfg(any(target_os = "linux", target_os = "android"))] Self::UdSocketNs(..) => true, } } pub const fn is_path(&self) -> bool { match self { #[cfg(windows)] Self::NamedPipe(..) => true, #[cfg(unix)] Self::UdSocketPath(..) => true, #[cfg(unix)] Self::UdSocketPseudoNs(..) => false, #[cfg(any(target_os = "linux", target_os = "android"))] Self::UdSocketNs(..) => false, } } #[inline] pub fn borrow(&self) -> NameInner<'_> { map_cow!(cow in self => Cow::Borrowed(cow)) } pub fn into_owned(self) -> NameInner<'static> { map_cow!(cow in self => Cow::Owned(cow.into_owned())) } } interprocess-2.2.3/src/local_socket/name/to_name.rs000064400000000000000000000061331046102023000205140ustar 00000000000000use { super::{ r#type::{NamespacedNameType, PathNameType}, Name, }, std::{ borrow::Cow, ffi::{CStr, CString, OsStr, OsString}, io, path::{Path, PathBuf}, str, }, }; macro_rules! trivial_string_impl { ( $cvttrait:ident<$str:ident> :: $mtd:ident<$nttrait:ident> for $($tgt:ty => $via:ident :: $ctor:ident)+ ) => {$( impl<'s> $cvttrait<'s, $str> for $tgt { #[inline] fn $mtd>(self) -> io::Result> { $via::$ctor(self).$mtd::() } } )+}; } /// Conversion to a filesystem path-type local socket name. pub trait ToFsName<'s, S: ToOwned + ?Sized> { /// Performs the conversion to a filesystem path-type name. /// /// Fails if the resulting name isn't supported by the platform. fn to_fs_name>(self) -> io::Result>; } /// Conversion to a namespaced local socket name. pub trait ToNsName<'s, S: ToOwned + ?Sized> { /// Performs the conversion to a namespaced name. /// /// Fails if the resulting name isn't supported by the platform. fn to_ns_name>(self) -> io::Result>; } #[allow(dead_code)] fn err(s: &'static str) -> io::Error { io::Error::new(io::ErrorKind::Unsupported, s) } impl<'s> ToFsName<'s, OsStr> for &'s Path { #[inline] fn to_fs_name>(self) -> io::Result> { FT::map(Cow::Borrowed(self.as_os_str())) } } impl<'s> ToFsName<'s, OsStr> for PathBuf { #[inline] fn to_fs_name>(self) -> io::Result> { FT::map(Cow::Owned(self.into_os_string())) } } trivial_string_impl! { ToFsName::to_fs_name for &'s str => Path ::new String => PathBuf::from &'s OsStr => Path ::new OsString => PathBuf::from } impl<'s> ToNsName<'s, OsStr> for &'s OsStr { #[inline] fn to_ns_name>(self) -> io::Result> { NT::map(Cow::Borrowed(self)) } } impl<'s> ToNsName<'s, OsStr> for OsString { #[inline] fn to_ns_name>(self) -> io::Result> { NT::map(Cow::Owned(self)) } } trivial_string_impl! { ToNsName::to_ns_name for &'s str => OsStr ::new String => OsString::from } impl<'s> ToFsName<'s, CStr> for &'s CStr { #[inline] fn to_fs_name>(self) -> io::Result> { FT::map(Cow::Borrowed(self)) } } impl<'s> ToFsName<'s, CStr> for CString { #[inline] fn to_fs_name>(self) -> io::Result> { FT::map(Cow::Owned(self)) } } impl<'s> ToNsName<'s, CStr> for &'s CStr { #[inline] fn to_ns_name>(self) -> io::Result> { NT::map(Cow::Borrowed(self)) } } impl<'s> ToNsName<'s, CStr> for CString { #[inline] fn to_ns_name>(self) -> io::Result> { NT::map(Cow::Owned(self)) } } interprocess-2.2.3/src/local_socket/name/type.rs000064400000000000000000000123061046102023000200520ustar 00000000000000//! Construction of local socket names, facilitating local socket implementation dispatch. //! //! The traits [`PathNameType`] and [`NamespacedNameType`] are implemented on uninhabited tag types //! and are to be used via [`ToFsName`](super::ToFsName) and [`ToNsName`](super::ToNsName) //! respectively. They are also sealed (cannot be implemented outside of Interprocess). //! //! The name type you choose affects what local socket implementation will be used. See the //! documentation on the tag types to learn more. #[cfg(unix)] use std::ffi::CStr; use { super::Name, crate::Sealed, std::{borrow::Cow, ffi::OsStr, io}, }; impmod! {local_socket::name_type as n_impl} /// Mappings from string types to [local socket names](Name). /// /// Types that implement this trait are [uninhabited] type-level markers: those which implement /// [`PathNameType`] serve as generic arguments for /// [`ToFsName::to_fs_name()`](super::ToFsName::to_fs_name), while those which implement /// [`NamespacedNameType`] are used with [`ToNsName::to_ns_name()`](super::ToNsName::to_ns_name). /// /// [uninhabited]: https://doc.rust-lang.org/reference/glossary.html#uninhabited /// /// **It is a breaking change for a mapping to meaningfully change.** More concretely, if a name /// produced by this mapping from some input results in a valid listener via /// [server creation](super::super::ListenerOptions) or successfully locates one via /// [client creation](super::super::traits::Stream::connect), the name type will continue to map /// that input to the same name, for the OS's definition of "same". #[allow(private_bounds)] pub trait NameType: Copy + std::fmt::Debug + Eq + Send + Sync + Unpin + Sealed { /// Whether the name type is supported within the runtime circumstances of the program. /// /// May entail querying support status from the OS, returning `false` in the event of an OS /// error. fn is_supported() -> bool; } /// [Mappings](NameType) from paths to [local socket names](Name). /// /// See [`ToFsName::to_fs_name()`](super::ToFsName::to_fs_name). pub trait PathNameType: NameType { /// Maps the given path to a local socket name, failing if the resulting name is unsupported by /// the underlying OS. /// /// The idiomatic way to use this is [`ToFsName::to_fs_name()`](super::ToFsName::to_fs_name). fn map(path: Cow<'_, S>) -> io::Result>; } /// [Mappings](NameType) from strings to [local socket names](Name). /// /// See [`ToNsName::to_ns_name()`](super::ToNsName::to_ns_name). pub trait NamespacedNameType: NameType { /// Maps the given string to a local socket name, failing if the resulting name is unsupported /// by the underlying OS. /// /// The idiomatic way to use this is [`ToNsName::to_ns_name()`](super::ToNsName::to_ns_name). fn map(name: Cow<'_, S>) -> io::Result>; } tag_enum!( /// Consistent platform-specific mapping from filesystem paths to local socket names. /// /// This name type, like [`GenericNamespaced`] is designed to be always supported on all platforms, /// whatever it takes. What follows below is a complete description of how that is implemented. /// /// ## Platform-specific behavior /// ### Windows /// For paths that start with `\\.\pipe\`, maps them to named pipe names without performing any /// transformations. Attempting to map any other type of path, including a normalization-bypassing /// path (`\\?\`) currently returns an error. /// /// ### Unix /// Resolves to filesystem paths to Unix domain sockets without performing any transformations. GenericFilePath); impl NameType for GenericFilePath { fn is_supported() -> bool { true } } impl PathNameType for GenericFilePath { #[inline] fn map(path: Cow<'_, OsStr>) -> io::Result> { n_impl::map_generic_path_osstr(path) } } #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] impl PathNameType for GenericFilePath { #[inline] fn map(path: Cow<'_, CStr>) -> io::Result> { n_impl::map_generic_path_cstr(path) } } tag_enum!( /// Consistent platform-specific mapping from arbitrary OS strings to local socket names. /// /// This name type, like [`GenericFilePath`] is designed to be always supported on all platforms, /// whatever it takes. What follows below is a complete description of how that is implemented. /// /// ## Platform-specific behavior /// ### Windows /// Resolves to named pipe names by prepending `\\.\pipe\` (thus, only local named pipes are /// addressable). /// /// ### Linux /// Resolves to the abstract namespace with no string transformations and thus has a maximum length /// of 107 bytes. /// /// ### Other Unices /// Resolves to filesystem paths by prepending `/tmp/`. GenericNamespaced); impl NameType for GenericNamespaced { fn is_supported() -> bool { true } } impl NamespacedNameType for GenericNamespaced { #[inline] fn map(name: Cow<'_, OsStr>) -> io::Result> { n_impl::map_generic_namespaced_osstr(name) } } #[cfg(unix)] #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] impl NamespacedNameType for GenericNamespaced { #[inline] fn map(name: Cow<'_, CStr>) -> io::Result> { n_impl::map_generic_namespaced_cstr(name) } } interprocess-2.2.3/src/local_socket/name.rs000064400000000000000000000046521046102023000170760ustar 00000000000000mod inner; pub(super) mod to_name; pub(super) mod r#type; pub(crate) use self::inner::*; pub use {r#type::*, to_name::*}; /// Name for a local socket. /// /// Due to significant differences between how different platforms name local sockets, there needs /// to be a way to store and process those in a unified way while also retaining those /// platform-specific pecularities. `Name` exists to bridge the gap between portability and /// correctness, minimizing the amount of platform-dependent code in downstream programs. /// /// # Creation /// Two traits are used to create names from basic strings: [`ToFsName`](super::ToFsName) and /// [`ToNsName`](super::ToNsName). /// /// # Validity /// As mentioned in the [module-level documentation](super), not all platforms support all types of /// local socket names. Names pointing to filesystem locations are only supported on Unix-like /// systems, and names pointing to an abstract namespace reserved specifically for local sockets are /// only available on Linux and Windows. /// /// Instances of this type cannot be constructed from unsupported values. They can, however, be /// constructed from invalid ones. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Name<'s>(pub(crate) NameInner<'s>); impl Name<'_> { /// Returns `true` if the name points to a dedicated local socket namespace, `false` otherwise. #[inline] pub fn is_namespaced(&self) -> bool { self.0.is_namespaced() } /// Returns `true` if the name is stored as a filesystem path, `false` otherwise. /// /// Note that it is possible for [`.is_namespaced()`](Self::is_namespaced) and `.is_path()` to /// return `true` simultaneously: /// ``` /// # #[cfg(windows)] { /// use interprocess::{local_socket::ToFsName, os::windows::local_socket::NamedPipe}; /// let name = r"\\.\pipe\example".to_fs_name::().unwrap(); /// assert!(name.is_namespaced()); // \\.\pipe\ is a namespace /// assert!(name.is_path()); // \\.\pipe\example is a path /// # } /// ``` #[inline] pub const fn is_path(&self) -> bool { self.0.is_path() } /// Produces a `Name` that borrows from `self`. #[inline] pub fn borrow(&self) -> Name<'_> { Name(self.0.borrow()) } /// Extends the lifetime to `'static`, cloning if necessary. #[inline] pub fn into_owned(self) -> Name<'static> { Name(self.0.into_owned()) } pub(crate) fn invalid() -> Self { Self(NameInner::default()) } } interprocess-2.2.3/src/local_socket/stream/enum.rs000064400000000000000000000102451046102023000204100ustar 00000000000000#[cfg(unix)] use crate::os::unix::uds_local_socket as uds_impl; #[cfg(windows)] use crate::os::windows::named_pipe::local_socket as np_impl; use { super::r#trait, crate::{local_socket::Name, TryClone}, std::io::{self, prelude::*, IoSlice, IoSliceMut}, }; impmod! {local_socket::dispatch_sync} macro_rules! dispatch_read { (@iw $ty:ident) => { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { dispatch!($ty: x in self => x.read(buf)) } #[inline] fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { dispatch!($ty: x in self => x.read_vectored(bufs)) } }; ($ty:ident) => { impl Read for &$ty { dispatch_read!(@iw $ty); } impl Read for $ty { dispatch_read!(@iw $ty); } }; } macro_rules! dispatch_write { (@iw $ty:ident) => { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { dispatch!($ty: x in self => x.write(buf)) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } #[inline] fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { dispatch!($ty: x in self => x.write_vectored(bufs)) } }; ($ty:ident) => { /// Flushing is an always successful no-op. impl Write for &$ty { dispatch_write!(@iw $ty); } /// Flushing is an always successful no-op. impl Write for $ty { dispatch_write!(@iw $ty); } }; } mkenum!( /// Local socket byte stream, obtained either from [`Listener`](super::super::Listener) or by /// connecting to an existing local socket. /// /// # Examples /// /// ## Basic client /// ```no_run #[doc = doctest_file::include_doctest!("examples/local_socket/sync/stream.rs")] /// ``` Stream); impl r#trait::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; #[inline] fn connect(name: Name<'_>) -> io::Result { dispatch_sync::connect(name) } #[inline] fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { dispatch!(Self: x in self => x.set_nonblocking(nonblocking)) } fn split(self) -> (RecvHalf, SendHalf) { match self { #[cfg(windows)] Stream::NamedPipe(s) => { let (rh, sh) = s.split(); (RecvHalf::NamedPipe(rh), SendHalf::NamedPipe(sh)) } #[cfg(unix)] Stream::UdSocket(s) => { let (rh, sh) = s.split(); (RecvHalf::UdSocket(rh), SendHalf::UdSocket(sh)) } } } fn reunite(rh: RecvHalf, sh: SendHalf) -> ReuniteResult { match (rh, sh) { #[cfg(windows)] (RecvHalf::NamedPipe(rh), SendHalf::NamedPipe(sh)) => { np_impl::Stream::reunite(rh, sh).map(From::from).map_err(|e| e.convert_halves()) } #[cfg(unix)] (RecvHalf::UdSocket(rh), SendHalf::UdSocket(sh)) => { uds_impl::Stream::reunite(rh, sh).map(From::from).map_err(|e| e.convert_halves()) } #[allow(unreachable_patterns)] (rh, sh) => Err(ReuniteError { rh, sh }), } } } impl TryClone for Stream { fn try_clone(&self) -> io::Result { dispatch!(Self: x in self => x.try_clone()).map(From::from) } } multimacro! { Stream, dispatch_read, dispatch_write, } mkenum!( /// Receive half of a local socket stream, obtained by splitting a [`Stream`]. "local_socket::" RecvHalf); impl r#trait::RecvHalf for RecvHalf { type Stream = Stream; } dispatch_read!(RecvHalf); mkenum!( /// Send half of a local socket stream, obtained by splitting a [`Stream`]. "local_socket::" SendHalf); impl r#trait::SendHalf for SendHalf { type Stream = Stream; } dispatch_write!(SendHalf); /// [`ReuniteError`](crate::error::ReuniteError) for [`Stream`]. pub type ReuniteError = crate::error::ReuniteError; /// Result type for [`.reunite()`](trait::Stream::reunite) on [`Stream`]. pub type ReuniteResult = r#trait::ReuniteResult; interprocess-2.2.3/src/local_socket/stream/trait.rs000064400000000000000000000066021046102023000205710ustar 00000000000000#![allow(private_bounds)] use { crate::{ bound_util::{RefRead, RefWrite}, local_socket::Name, Sealed, }, std::io::{self, prelude::*}, }; /// Local socket stream implementations. /// /// Types on which this trait is implemented are variants of the /// [`Stream` enum](super::enum::Stream). In addition, it is implemented on `Stream` itself, which /// makes it a trait object of sorts. See its documentation for more on the semantics of the methods /// seen here. pub trait Stream: Read + RefRead + Write + RefWrite + Send + Sync + Sized + Sealed { /// Receive half type returned by [`.split()`](Stream::split). type RecvHalf: RecvHalf; /// Send half type returned by [`.split()`](Stream::split). type SendHalf: SendHalf; /// Connects to a remote local socket server. fn connect(name: Name<'_>) -> io::Result; /// Enables or disables the nonblocking mode for the stream. By default, it is disabled. /// /// In nonblocking mode, receiving and sending immediately returns with the /// [`WouldBlock`](io::ErrorKind::WouldBlock) error in situations when they would normally block /// for an uncontrolled amount of time. The specific situations are: /// - Receiving is attempted and there is no new data available; /// - Sending is attempted and the buffer is full due to the other side not yet having /// received previously sent data. fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()>; /// Splits a stream into a receive half and a send half, which can be used to receive from and /// send to the stream concurrently from different threads, entailing a memory allocation. fn split(self) -> (Self::RecvHalf, Self::SendHalf); /// Attempts to reunite a receive half with a send half to yield the original stream back, /// returning both halves as an error if they belong to different streams (or when using this /// method on streams that haven't been split to begin with). fn reunite(rh: Self::RecvHalf, sh: Self::SendHalf) -> ReuniteResult; // Do not add methods to this trait that aren't directly tied to non-async streams. A new trait, // which should be called StreamExtra or StreamCommon or something along those lines, is to be // created for features like impersonation (ones that are instantaneous in nature). } /// Receive halves of [`Stream`]s, obtained through [`.split()`](Stream::split). /// /// Types on which this trait is implemented are variants of the /// [`RecvHalf` enum](super::enum::RecvHalf). In addition, it is implemented on `RecvHalf` itself, /// which makes it a trait object of sorts. pub trait RecvHalf: Sized + Read + RefRead + Sealed { /// The stream type the half is split from. type Stream: Stream; } /// Send halves of [`Stream`]s, obtained through [`.split()`](Stream::split). /// /// Types on which this trait is implemented are variants of the /// [`SendHalf` enum](super::enum::SendHalf). In addition, it is implemented on `SendHalf` itself, /// which makes it a trait object of sorts. pub trait SendHalf: Sized + Write + RefWrite + Sealed { /// The stream type the half is split from. type Stream: Stream; } /// [`ReuniteResult`](crate::error::ReuniteResult) for the [`Stream` trait](Stream). pub type ReuniteResult = crate::error::ReuniteResult::RecvHalf, ::SendHalf>; interprocess-2.2.3/src/local_socket/tokio/listener/enum.rs000064400000000000000000000023331046102023000220660ustar 00000000000000#[cfg(unix)] use crate::os::unix::uds_local_socket::tokio as uds_impl; #[cfg(windows)] use crate::os::windows::named_pipe::local_socket::tokio as np_impl; use { super::r#trait, crate::local_socket::{tokio::Stream, ListenerOptions}, std::io, }; impmod! {local_socket::dispatch_tokio as dispatch} mkenum!( /// Tokio-based local socket server, listening for connections. /// /// This struct is created by [`ListenerOptions`](crate::local_socket::ListenerOptions). /// /// [Name reclamation](super::super::Stream#name-reclamation) is performed by default on /// backends that necessitate it. /// /// # Examples /// /// ## Basic server /// ```no_run #[doc = doctest_file::include_doctest!("examples/local_socket/tokio/listener.rs")] /// ``` Listener); impl r#trait::Listener for Listener { type Stream = Stream; #[inline] fn from_options(options: ListenerOptions<'_>) -> io::Result { dispatch::from_options(options) } #[inline] async fn accept(&self) -> io::Result { dispatch!(Self: x in self => x.accept()).await.map(Stream::from) } #[inline] fn do_not_reclaim_name_on_drop(&mut self) { dispatch!(Self: x in self => x.do_not_reclaim_name_on_drop()) } } interprocess-2.2.3/src/local_socket/tokio/listener/trait.rs000064400000000000000000000021771046102023000222530ustar 00000000000000use { crate::{ local_socket::{tokio::stream::r#trait::Stream, ListenerOptions}, Sealed, }, std::{future::Future, io}, }; /// Tokio local socket server implementations. /// /// Types on which this trait is implemented are variants of the /// [`Listener` enum](super::enum::Listener). In addition, it is implemented on `Listener` itself, /// which makes it a trait object of sorts. See its documentation for more on the semantics of the /// methods seen here. #[allow(private_bounds)] pub trait Listener: Send + Sync + Sized + Sealed { /// The stream type associated with this listener. type Stream: Stream; /// Creates a socket server using the specified options. fn from_options(options: ListenerOptions<'_>) -> io::Result; /// Asynchronously listens for incoming connections to the socket, returning a future that /// finishes only when a client is connected. fn accept(&self) -> impl Future> + Send + Sync; /// Disables [name reclamation](super::enum::Listener#name-reclamation) on the listener. fn do_not_reclaim_name_on_drop(&mut self); } interprocess-2.2.3/src/local_socket/tokio/stream/enum.rs000064400000000000000000000101511046102023000215310ustar 00000000000000#[cfg(unix)] use crate::os::unix::uds_local_socket::tokio as uds_impl; #[cfg(windows)] use crate::os::windows::named_pipe::local_socket::tokio as np_impl; use { super::r#trait, crate::local_socket::Name, std::{ io, pin::Pin, task::{Context, Poll}, }, tokio::io::{AsyncRead, AsyncWrite, ReadBuf}, }; impmod! {local_socket::dispatch_tokio as dispatch} macro_rules! dispatch_read { (@iw $ty:ident) => { #[inline] fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { dispatch!($ty: x in self.get_mut() => Pin::new(x).poll_read(cx, buf)) } }; ($ty:ident) => { impl AsyncRead for &$ty { dispatch_read!(@iw $ty); } impl AsyncRead for $ty { dispatch_read!(@iw $ty); } }; } macro_rules! dispatch_write { (@iw $ty:ident) => { #[inline] fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { dispatch!($ty: x in self.get_mut() => Pin::new(x).poll_write(cx, buf)) } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } }; ($ty:ident) => { /// Flushing and shutdown are always successful no-ops. impl AsyncWrite for &$ty { dispatch_write!(@iw $ty); } /// Flushing and shutdown are always successful no-ops. impl AsyncWrite for $ty { dispatch_write!(@iw $ty); } }; } mkenum!( /// Tokio-based local socket byte stream, obtained either from [`Listener`](super::super::Listener) /// or by connecting to an existing local socket. /// /// # Examples /// /// ## Basic client /// ```no_run #[doc = doctest_file::include_doctest!("examples/local_socket/tokio/listener.rs")] /// ``` Stream); impl r#trait::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; #[inline] async fn connect(name: Name<'_>) -> io::Result { dispatch::connect(name).await } fn split(self) -> (RecvHalf, SendHalf) { match self { #[cfg(windows)] Stream::NamedPipe(s) => { let (rh, sh) = s.split(); (RecvHalf::NamedPipe(rh), SendHalf::NamedPipe(sh)) } #[cfg(unix)] Stream::UdSocket(s) => { let (rh, sh) = s.split(); (RecvHalf::UdSocket(rh), SendHalf::UdSocket(sh)) } } } fn reunite(rh: RecvHalf, sh: SendHalf) -> ReuniteResult { match (rh, sh) { #[cfg(windows)] (RecvHalf::NamedPipe(rh), SendHalf::NamedPipe(sh)) => { np_impl::Stream::reunite(rh, sh).map(From::from).map_err(|e| e.convert_halves()) } #[cfg(unix)] (RecvHalf::UdSocket(rh), SendHalf::UdSocket(sh)) => { uds_impl::Stream::reunite(rh, sh).map(From::from).map_err(|e| e.convert_halves()) } #[allow(unreachable_patterns)] (rh, sh) => Err(ReuniteError { rh, sh }), } } } multimacro! { Stream, dispatch_read, dispatch_write, } mkenum!( /// Receive half of a Tokio-based local socket stream, obtained by splitting a [`Stream`]. "local_socket::tokio::" RecvHalf); impl r#trait::RecvHalf for RecvHalf { type Stream = Stream; } multimacro! { RecvHalf, dispatch_read, } mkenum!( /// Send half of a Tokio-based local socket stream, obtained by splitting a [`Stream`]. "local_socket::tokio::" SendHalf); impl r#trait::SendHalf for SendHalf { type Stream = Stream; } multimacro! { SendHalf, dispatch_write, } /// [`ReuniteError`](crate::error::ReuniteError) for [`Stream`]. pub type ReuniteError = crate::error::ReuniteError; /// Result type for [`.reunite()`](trait::Stream::reunite) on [`Stream`]. pub type ReuniteResult = r#trait::ReuniteResult; interprocess-2.2.3/src/local_socket/tokio/stream/trait.rs000064400000000000000000000052711046102023000217170ustar 00000000000000#![allow(private_bounds)] use { crate::{ bound_util::{RefTokioAsyncRead, RefTokioAsyncWrite}, local_socket::Name, Sealed, }, std::{future::Future, io}, tokio::io::{AsyncRead, AsyncWrite}, }; /// Tokio local socket stream implementations. /// /// Types on which this trait is implemented are variants of the /// [`Stream` enum](super::enum::Stream). In addition, it is implemented on `Stream` itself, which /// makes it a trait object of sorts. See its documentation for more on the semantics of the methods /// seen here. pub trait Stream: AsyncRead + RefTokioAsyncRead + AsyncWrite + RefTokioAsyncWrite + Send + Sync + Sized + Sealed { /// Receive half type returned by [`.split()`](Stream::split). type RecvHalf: RecvHalf; /// Send half type returned by [`.split()`](Stream::split). type SendHalf: SendHalf; /// Asynchronously connects to a remote local socket server. fn connect(name: Name<'_>) -> impl Future> + Send + Sync; /// Splits a stream into a receive half and a send half, which can be used to receive from and /// send to the stream concurrently from different Tokio tasks, entailing a memory allocation. fn split(self) -> (Self::RecvHalf, Self::SendHalf); /// Attempts to reunite a receive half with a send half to yield the original stream back, /// returning both halves as an error if they belong to different streams (or when using this /// method on streams that haven't been split to begin with). fn reunite(rh: Self::RecvHalf, sh: Self::SendHalf) -> ReuniteResult; } /// Receive halves of Tokio [`Stream`]s, obtained through [`.split()`](Stream::split). /// /// Types on which this trait is implemented are variants of the /// [`RecvHalf` enum](super::enum::RecvHalf). In addition, it is implemented on `RecvHalf` itself, /// which makes it a trait object of sorts. pub trait RecvHalf: Sized + AsyncRead + RefTokioAsyncRead + Sealed { /// The stream type the half is split from. type Stream: Stream; } /// Send halves of Tokio [`Stream`]s, obtained through [`.split()`](Stream::split). /// /// Types on which this trait is implemented are variants of the /// [`SendHalf` enum](super::enum::SendHalf). In addition, it is implemented on `SendHalf` itself, /// which makes it a trait object of sorts. pub trait SendHalf: Sized + AsyncWrite + RefTokioAsyncWrite + Sealed { /// The stream type the half is split from. type Stream: Stream; } /// [`ReuniteResult`](crate::error::ReuniteResult) for the [Tokio `Stream` trait](Stream). pub type ReuniteResult = crate::error::ReuniteResult::RecvHalf, ::SendHalf>; interprocess-2.2.3/src/local_socket.rs000064400000000000000000000103541046102023000161520ustar 00000000000000//! Local sockets, an IPC primitive featuring a server and multiple clients connecting to that //! server using a filesystem path or an identifier inside a special namespace, each having a //! private connection to that server. //! //! ## Implementation types //! Local sockets are not a real IPC method implemented by the OS – they exist to paper over the //! differences between the two underlying implementations currently in use: Unix domain sockets and //! Windows named pipes. //! //! Interprocess defines [traits] that implementations of local sockets implement, and enums that //! constitute devirtualized trait objects (not unlike those provided by the `enum_dispatch` crate) //! for those traits. The implementation used, in cases where multiple options apply, is chosen at //! construction via the [name](Name) and [name type](NameType) infrastructure. //! //! ## Differences from regular sockets //! A few missing features, primarily on Windows, require local sockets to omit some important //! functionality, because code relying on it wouldn't be portable. Some notable differences are: //! - No `.shutdown()` – your communication protocol must manually negotiate end of transmission. //! Notably, `.read_to_string()` and `.read_all()` will always block indefinitely at some point. //! - No datagram sockets – the difference in semantics between connectionless datagram Unix-domain //! sockets and connection-based named message pipes on Windows does not allow bridging those two //! into a common API. You can emulate datagrams on top of streams anyway, so no big deal, right? #[macro_use] mod enumdef; mod name; mod stream { pub(super) mod r#enum; pub(super) mod r#trait; } mod listener { pub(super) mod r#enum; pub(super) mod options; pub(super) mod r#trait; } /// Traits representing the interface of local sockets. pub mod traits { pub use super::{ listener::r#trait::{Listener, ListenerExt, ListenerNonblockingMode}, stream::r#trait::*, }; /// Traits for the Tokio variants of local socket objects. #[cfg(feature = "tokio")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "tokio")))] pub mod tokio { pub use super::super::tokio::{listener::r#trait::*, stream::r#trait::*}; } } pub use { listener::{options::ListenerOptions, r#enum::*, r#trait::Incoming}, name::*, stream::r#enum::*, traits::ListenerNonblockingMode, }; /// Re-exports of [traits] done in a way that doesn't pollute the scope, as well as of the /// enum-dispatch types with their names prefixed with `LocalSocket`. pub mod prelude { pub use super::{ name::{NameType as _, ToFsName as _, ToNsName as _}, traits::{Listener as _, ListenerExt as _, Stream as _}, Listener as LocalSocketListener, Stream as LocalSocketStream, }; } /// Asynchronous local sockets which work with the Tokio runtime and event loop. /// /// The Tokio integration allows the local socket streams and listeners to be notified by the OS /// kernel whenever they're ready to be received from of sent to, instead of spawning threads just /// to put them in a wait state of blocking on the I/O. /// /// Types from this module will *not* work with other async runtimes, such as `async-std` or `smol`, /// since the Tokio types' methods will panic whenever they're called outside of a Tokio runtime /// context. Open an issue if you'd like to see other runtimes supported as well. #[cfg(feature = "tokio")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "tokio")))] pub mod tokio { pub(super) mod listener { pub(in super::super) mod r#enum; pub(in super::super) mod r#trait; } pub(super) mod stream { pub(in super::super) mod r#enum; pub(in super::super) mod r#trait; } pub use {listener::r#enum::*, stream::r#enum::*}; /// Like the [sync local socket prelude](super::prelude), but for Tokio local sockets. pub mod prelude { pub use super::{ super::{ name::{NameType as _, ToFsName as _, ToNsName as _}, traits::tokio::{Listener as _, Stream as _}, }, Listener as LocalSocketListener, Stream as LocalSocketStream, }; } } mod concurrency_detector; pub(crate) use concurrency_detector::*; interprocess-2.2.3/src/macros/derive_mut_iorw.rs000064400000000000000000000101631046102023000201750ustar 00000000000000//! Macros that derive `Read` and `Write` (and their Tokio counterparts) on all `T` that satisfy //! `for<'a> &'a T: Trait` for the corresponding trait. macro_rules! derive_sync_mut_read { ($({$($lt:tt)*})? $ty:ty) => { impl $(<$($lt)*>)? ::std::io::Read for $ty { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result { (&*self).read(buf) } #[inline(always)] fn read_vectored( &mut self, bufs: &mut [::std::io::IoSliceMut<'_>], ) -> ::std::io::Result { (&*self).read_vectored(bufs) } // read_to_end isn't here because this macro isn't supposed to be used on Chain-like // adapters // FUTURE is_read_vectored } }; } macro_rules! derive_sync_mut_write { ($({$($lt:tt)*})? $ty:ty) => { impl $(<$($lt)*>)? ::std::io::Write for $ty { #[inline(always)] fn write(&mut self, buf: &[u8]) -> ::std::io::Result { (&*self).write(buf) } #[inline(always)] fn flush(&mut self) -> ::std::io::Result<()> { (&*self).flush() } #[inline(always)] fn write_vectored( &mut self, bufs: &[::std::io::IoSlice<'_>], ) -> ::std::io::Result { (&*self).write_vectored(bufs) } // FUTURE is_write_vectored } }; } macro_rules! derive_sync_mut_rw { ($({$($lt:tt)*})? $ty:ty) => { forward_sync_read!($({$($lt)*})? $ty); forward_sync_write!($({$($lt)*})? $ty); }; } macro_rules! derive_tokio_mut_read { ($({$($lt:tt)*})? $ty:ty) => { const _: () = { use ::tokio::io::{AsyncRead, ReadBuf}; use ::std::{io, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncRead for $ty { #[inline(always)] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { AsyncRead::poll_read(Pin::new(&mut &*self), cx, buf) } } }; }; } macro_rules! derive_tokio_mut_write { ($({$($lt:tt)*})? $ty:ty) => { const _: () = { use ::tokio::io::AsyncWrite; use ::std::{io::{self, IoSlice}, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncWrite for $ty { #[inline(always)] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { AsyncWrite::poll_write(Pin::new(&mut &*self), cx, buf) } #[inline(always)] fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { AsyncWrite::poll_write_vectored(Pin::new(&mut &*self), cx, bufs) } #[inline(always)] fn is_write_vectored(&self) -> bool { AsyncWrite::is_write_vectored(self.refwd()) } #[inline(always)] fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { AsyncWrite::poll_flush(Pin::new(&mut &*self), cx) } #[inline(always)] fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { AsyncWrite::poll_shutdown(Pin::new(&mut &*self), cx) } } }; }; } macro_rules! derive_tokio_mut_rw { ($({$($lt:tt)*})? $ty:ty) => { derive_tokio_mut_read!($({$($lt)*})? $ty); derive_tokio_mut_write!($({$($lt)*})? $ty); }; } interprocess-2.2.3/src/macros/derive_raw.rs000064400000000000000000000105211046102023000171170ustar 00000000000000//! Derive macros that implement raw handle manipulation in terms of safe handle manipulation from //! Rust 1.63+. Lifetime arguments on impls can be specified in curly braces. macro_rules! derive_asraw { (@impl $({$($forcl:tt)*})? $ty:ty, $hty:ident, $trt:ident, $mtd:ident, $strt:ident, $smtd:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($forcl)*>)? ::std::os::$cfg::io::$trt for $ty { #[inline] fn $mtd(&self) -> ::std::os::$cfg::io::$hty { let h = ::std::os::$cfg::io::$strt::$smtd(self); ::std::os::$cfg::io::$trt::$mtd(&h) } } }; ($({$($forcl:tt)*})? $ty:ty, windows) => { derive_asraw!( @impl $({$($forcl)*})? $ty, RawHandle, AsRawHandle, as_raw_handle, AsHandle, as_handle, windows); }; ($({$($forcl:tt)*})? $ty:ty, unix) => { derive_asraw!( @impl $({$($forcl)*})? $ty, RawFd, AsRawFd, as_raw_fd, AsFd, as_fd, unix); }; ($({$($forcl:tt)*})? $ty:ty) => { derive_asraw!($({$($forcl)*})? $ty, windows); derive_asraw!($({$($forcl)*})? $ty, unix); }; } macro_rules! derive_intoraw { (@impl $({$($forcl:tt)*})? $ty:ty, $hty:ident, $ohty:ident, $trt:ident, $mtd:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($forcl)*>)? ::std::os::$cfg::io::$trt for $ty { #[inline] fn $mtd(self) -> ::std::os::$cfg::io::$hty { let h = >::from(self); ::std::os::$cfg::io::$trt::$mtd(h) } } }; ($({$($forcl:tt)*})? $ty:ty, windows) => { derive_intoraw!( @impl $({$($forcl)*})? $ty, RawHandle, OwnedHandle, IntoRawHandle, into_raw_handle, windows); }; ($({$($forcl:tt)*})? $ty:ty, unix) => { derive_intoraw!( @impl $({$($forcl)*})? $ty, RawFd, OwnedFd, IntoRawFd, into_raw_fd, unix); }; ($({$($forcl:tt)*})? $ty:ty) => { derive_intoraw!($({$($forcl)*})? $ty, windows); derive_intoraw!($({$($forcl)*})? $ty, unix); }; } macro_rules! derive_asintoraw { ($({$($forcl:tt)*})? $ty:ty, windows) => { derive_asraw!($({$($forcl)*})? $ty, windows); derive_intoraw!($({$($forcl)*})? $ty, windows); }; ($({$($forcl:tt)*})? $ty:ty, unix) => { derive_asraw!($({$($forcl)*})? $ty, unix); derive_intoraw!($({$($forcl)*})? $ty, unix); }; ($({$($forcl:tt)*})? $ty:ty) => { derive_asintoraw!($({$($forcl)*})? $ty, windows); derive_asintoraw!($({$($forcl)*})? $ty, unix); }; } macro_rules! derive_fromraw { (@impl $({$($forcl:tt)*})? $ty:ty, $hty:ident, $ohty:ident, $trt:ident, $mtd:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($forcl)*>)? ::std::os::$cfg::io::$trt for $ty { #[inline] unsafe fn $mtd(fd: ::std::os::$cfg::io::$hty) -> Self { let h: ::std::os::$cfg::io::$ohty = unsafe { ::std::os::$cfg::io::$trt::$mtd(fd) }; ::std::convert::From::from(h) } } }; ($({$($forcl:tt)*})? $ty:ty, windows) => { derive_fromraw!( @impl $({$($forcl)*})? $ty, RawHandle, OwnedHandle, FromRawHandle, from_raw_handle, windows); }; ($({$($forcl:tt)*})? $ty:ty, unix) => { derive_fromraw!( @impl $({$($forcl)*})? $ty, RawFd, OwnedFd, FromRawFd, from_raw_fd, unix); }; ($({$($forcl:tt)*})? $ty:ty) => { derive_fromraw!($({$($forcl)*})? $ty, windows); derive_fromraw!($({$($forcl)*})? $ty, unix); }; } macro_rules! derive_raw { ($({$($forcl:tt)*})? $ty:ty, windows) => { derive_asintoraw!($({$($forcl)*})? $ty, windows); derive_fromraw!($({$($forcl)*})? $ty, windows); }; ($({$($forcl:tt)*})? $ty:ty, unix) => { derive_asintoraw!($({$($forcl)*})? $ty, unix); derive_fromraw!($({$($forcl)*})? $ty, unix); }; ($({$($forcl:tt)*})? $ty:ty) => { derive_asintoraw!($({$($forcl)*})? $ty); derive_fromraw!($({$($forcl)*})? $ty); }; } interprocess-2.2.3/src/macros/derive_trivconv.rs000064400000000000000000000014571046102023000202100ustar 00000000000000//! Derive macros for trivial `From`. Rust 1.63+. Lifetime arguments on impls can be specified in //! curly braces. macro_rules! derive_trivial_from { ($({$($forcl:tt)*})? $dst:ty, $src:ty) => { impl $(<$($forcl)*>)? ::std::convert::From<$src> for $dst { #[inline] fn from(src: $src) -> Self { Self(src) } } }; } macro_rules! derive_trivial_into { ($({$($forcl:tt)*})? $src:ty, $dst:ty) => { impl $(<$($forcl)*>)? ::std::convert::From<$src> for $dst { #[inline] fn from(src: $src) -> Self { src.0 } } }; } macro_rules! derive_trivial_conv { ($({$($forcl:tt)*})? $ty1:ty, $ty2:ty) => { derive_trivial_from!($({$($forcl)*})? $ty1, $ty2); derive_trivial_into!($({$($forcl)*})? $ty1, $ty2); }; } interprocess-2.2.3/src/macros/forward_fmt.rs000064400000000000000000000017061046102023000173070ustar 00000000000000//! Forwarding of `Debug` for newtypes that allows specifying a descriptive typename. use std::fmt::{self, Debug, Formatter}; #[allow(dead_code)] pub(crate) fn debug_forward_with_custom_name( nm: &str, fld: &dyn Debug, f: &mut Formatter<'_>, ) -> fmt::Result { f.debug_tuple(nm).field(fld).finish() } macro_rules! forward_debug { ($({$($lt:tt)*})? $ty:ty, $nm:literal) => { impl $(<$($lt)*>)? ::std::fmt::Debug for $ty { #[inline(always)] fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { $crate::macros::debug_forward_with_custom_name($nm, &self.0, f) } } }; ($({$($lt:tt)*})? $ty:ty) => { impl $(<$($lt)*>)? ::std::fmt::Debug for $ty { #[inline(always)] fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { ::std::fmt::Debug::fmt(&self.0, f) } } }; } interprocess-2.2.3/src/macros/forward_handle_and_fd.rs000064400000000000000000000135041046102023000212460ustar 00000000000000//! Forwarding macros that implement safe handle manipulation in terms of a field's implementations. //! Usually followed up by one of the derives from `derive_raw`. macro_rules! forward_as_handle { (@impl $({$($lt:tt)*})? $ty:ty, $hty:ident, $trt:ident, $mtd:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($lt)*>)? ::std::os::$cfg::io::$trt for $ty { #[inline] fn $mtd(&self) -> ::std::os::$cfg::io::$hty<'_> { ::std::os::$cfg::io::$trt::$mtd(&self.0) } } }; ($({$($lt:tt)*})? $ty:ty, windows) => { forward_as_handle!(@impl $({$($lt)*})? $ty, BorrowedHandle, AsHandle, as_handle, windows); }; ($({$($lt:tt)*})? $ty:ty, unix) => { forward_as_handle!(@impl $({$($lt)*})? $ty, BorrowedFd, AsFd, as_fd, unix); }; ($({$($lt:tt)*})? $ty:ty) => { forward_as_handle!($({$($lt)*})? $ty, windows); forward_as_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_into_handle { (@impl $({$($lt:tt)*})? $ty:ty, $hty:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($lt)*>)? ::std::convert::From<$ty> for ::std::os::$cfg::io::$hty { #[inline] fn from(x: $ty) -> Self { ::std::convert::From::from(x.0) } } }; ($({$($lt:tt)*})? $ty:ty, windows) => { forward_into_handle!(@impl $({$($lt)*})? $ty, OwnedHandle, windows); }; ($({$($lt:tt)*})? $ty:ty, unix) => { forward_into_handle!(@impl $({$($lt)*})? $ty, OwnedFd, unix); }; ($({$($lt:tt)*})? $ty:ty) => { forward_into_handle!($({$($lt)*})? $ty, windows); forward_into_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_from_handle { (@impl $({$($lt:tt)*})? $ty:ty, $hty:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($lt)*>)? ::std::convert::From<::std::os::$cfg::io::$hty> for $ty { #[inline] fn from(x: ::std::os::$cfg::io::$hty) -> Self { Self(::std::convert::From::from(x)) } } }; ($({$($lt:tt)*})? $ty:ty, windows) => { forward_from_handle!(@impl $({$($lt)*})? $ty, OwnedHandle, windows); }; ($({$($lt:tt)*})? $ty:ty, unix) => { forward_from_handle!(@impl $({$($lt)*})? $ty, OwnedFd, unix); }; ($({$($lt:tt)*})? $ty:ty) => { forward_from_handle!($({$($lt)*})? $ty, windows); forward_from_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_asinto_handle { ($({$($lt:tt)*})? $ty:ty, windows) => { forward_as_handle!($({$($lt)*})? $ty, windows); forward_into_handle!($({$($lt)*})? $ty, windows); }; ($({$($lt:tt)*})? $ty:ty, unix) => { forward_as_handle!($({$($lt)*})? $ty, unix); forward_into_handle!($({$($lt)*})? $ty, unix); }; ($({$($lt:tt)*})? $ty:ty) => { forward_asinto_handle!($({$($lt)*})? $ty, windows); forward_asinto_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_handle { ($({$($lt:tt)*})? $ty:ty, windows) => { forward_asinto_handle!($({$($lt)*})? $ty, windows); forward_from_handle!($({$($lt)*})? $ty, windows); }; ($({$($lt:tt)*})? $ty:ty, unix) => { forward_asinto_handle!($({$($lt)*})? $ty, unix); forward_from_handle!($({$($lt)*})? $ty, unix); }; ($({$($lt:tt)*})? $ty:ty) => { forward_handle!($({$($lt)*})? $ty, windows); forward_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_try_into_handle { (@impl $({$($lt:tt)*})? $ty:ty, $ety:path, $hty:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($lt)*>)? ::std::convert::TryFrom<$ty> for ::std::os::$cfg::io::$hty { type Error = $ety; #[inline] fn try_from(x: $ty) -> Result { ::std::convert::TryFrom::try_from(x.0) } } }; ($({$($lt:tt)*})? $ty:ty, $ety:path, windows) => { forward_try_into_handle!(@impl $({$($lt)*})? $ty, $ety, OwnedHandle, windows); }; ($({$($lt:tt)*})? $ty:ty, $ety:path, unix) => { forward_try_into_handle!(@impl $({$($lt)*})? $ty, $ety, OwnedFd, unix); }; ($({$($lt:tt)*})? $ty:ty, $ety:path) => { forward_try_into_handle!($({$($lt)*})? $ty, windows); forward_try_into_handle!($({$($lt)*})? $ty, unix); }; } macro_rules! forward_try_from_handle { (@impl $({$($lt:tt)*})? $ty:ty, $ety:path, $hty:ident, $cfg:ident) => { #[cfg($cfg)] impl $(<$($lt)*>)? ::std::convert::TryFrom<::std::os::$cfg::io::$hty> for $ty { type Error = $ety; #[inline] fn try_from(x: ::std::os::$cfg::io::$hty) -> Result { Ok(Self(::std::convert::TryFrom::try_from(x)?)) } } }; ($({$($lt:tt)*})? $ty:ty, $ety:path, windows) => { forward_try_from_handle!(@impl $({$($lt)*})? $ty, $ety, OwnedHandle, windows); }; ($({$($lt:tt)*})? $ty:ty, $ety:path, unix) => { forward_try_from_handle!(@impl $({$($lt)*})? $ty, $ety, OwnedFd, unix); }; ($({$($lt:tt)*})? $ty:ty, $ety:path) => { forward_try_from_handle!($({$($lt)*})? $ty, $ety, windows); forward_try_from_handle!($({$($lt)*})? $ty, $ety, unix); }; } macro_rules! forward_try_handle { ($({$($lt:tt)*})? $ty:ty, $ety:path, windows) => { forward_try_into_handle!($({$($lt)*})? $ty, $ety, windows); forward_try_from_handle!($({$($lt)*})? $ty, $ety, windows); }; ($({$($lt:tt)*})? $ty:ty, $ety:path, unix) => { forward_try_into_handle!($({$($lt)*})? $ty, $ety, unix); forward_try_from_handle!($({$($lt)*})? $ty, $ety, unix); }; ($({$($lt:tt)*})? $ty:ty, $ety:path) => { forward_try_handle!($({$($lt)*})? $ty, $ety, windows); forward_try_handle!($({$($lt)*})? $ty, $ety, unix); }; } interprocess-2.2.3/src/macros/forward_iorw.rs000064400000000000000000000221361046102023000175010ustar 00000000000000//! Forwarding of `Read` and `Write` (and their Tokio counterparts) for newtypes. Allows attributes //! on the impl block and on every individual method – only one attribute per type, comma-separated. macro_rules! forward_sync_read { ($({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta])?)?)?) => { $(#[$a1])? impl $(<$($lt)*>)? ::std::io::Read for $ty { $($(#[$a2])?)? #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result { self.0.read(buf) } $($($(#[$a3])?)?)? #[inline(always)] fn read_vectored( &mut self, bufs: &mut [::std::io::IoSliceMut<'_>], ) -> ::std::io::Result { self.0.read_vectored(bufs) } // read_to_end isn't here because this macro isn't supposed to be used on Chain-like // adapters // FUTURE is_read_vectored } }; } macro_rules! forward_sync_write { ( $({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta] $(, #[$a4:meta])?)?)?)? ) => { $(#[$a1])? impl $(<$($lt)*>)? ::std::io::Write for $ty { $($(#[$a2])?)? #[inline(always)] fn write(&mut self, buf: &[u8]) -> ::std::io::Result { self.0.write(buf) } $($(#[$a2])?)? #[inline(always)] fn flush(&mut self) -> ::std::io::Result<()> { self.0.flush() } $($($($(#[$a4])?)?)?)? #[inline(always)] fn write_vectored( &mut self, bufs: &[::std::io::IoSlice<'_>], ) -> ::std::io::Result { self.0.write_vectored(bufs) } // FUTURE is_write_vectored } }; } macro_rules! forward_sync_rw { ( $({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta] $(, #[$a4:meta])?)?)?)? ) => { forward_sync_read!($({$($lt)*})? $ty $(, #[$a1] $(, #[$a2] $(, #[$a3])?)?)?); forward_sync_write!($({$($lt)*})? $ty $(, #[$a1] $(, #[$a2] $(, #[$a3] $(, #[$a4])?)?)?)?); }; } macro_rules! forward_sync_ref_read { ($({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta])?)?)?) => { $(#[$a1])? impl $(<$($lt)*>)? ::std::io::Read for &$ty { $($(#[$a2])?)? #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result { self.refwd().read(buf) } $($($(#[$a3])?)?)? #[inline(always)] fn read_vectored( &mut self, bufs: &mut [::std::io::IoSliceMut<'_>], ) -> ::std::io::Result { self.refwd().read_vectored(bufs) } // FUTURE is_read_vectored } }; } macro_rules! forward_sync_ref_write { ( $({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta] $(, #[$a4:meta])?)?)?)? ) => { $(#[$a1])? impl $(<$($lt)*>)? ::std::io::Write for &$ty { $($(#[$a2])?)? #[inline(always)] fn write(&mut self, buf: &[u8]) -> ::std::io::Result { self.refwd().write(buf) } $($($(#[$a3])?)?)? #[inline(always)] fn flush(&mut self) -> ::std::io::Result<()> { self.refwd().flush() } $($($($(#[$a4])?)?)?)? #[inline(always)] fn write_vectored( &mut self, bufs: &[::std::io::IoSlice<'_>], ) -> ::std::io::Result { self.refwd().write_vectored(bufs) } // FUTURE is_write_vectored } }; } macro_rules! forward_sync_ref_rw { ( $({$($lt:tt)*})? $ty:ty $(, #[$a1:meta] $(, #[$a2:meta] $(, #[$a3:meta] $(, #[$a4:meta])?)?)?)? ) => { forward_sync_ref_read!($({$($lt)*})? $ty $(, #[$a1] $(, #[$a2] $(, #[$a3])?)?)?); forward_sync_ref_write!( $({$($lt)*})? $ty $(, #[$a1] $(, #[$a2] $(, #[$a3] $(, #[$a4])?)?)?)? ); }; } macro_rules! forward_tokio_read { ($({$($lt:tt)*})? $ty:ty, $pinproj:ident) => { const _: () = { use ::tokio::io::{AsyncRead, ReadBuf}; use ::std::{io, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncRead for $ty { #[inline(always)] fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { self.$pinproj().poll_read(cx, buf) } } }; }; ($({$($lt:tt)*})? $ty:ty) => { forward_tokio_read!($({$($lt)*})? $ty, pinproj); }; } macro_rules! forward_tokio_write { ($({$($lt:tt)*})? $ty:ty, $pinproj:ident) => { const _: () = { use ::tokio::io::AsyncWrite; use ::std::{io::{self, IoSlice}, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncWrite for $ty { #[inline(always)] fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.$pinproj().poll_write(cx, buf) } #[inline(always)] fn poll_write_vectored( mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { self.$pinproj().poll_write_vectored(cx, bufs) } #[inline(always)] fn is_write_vectored(&self) -> bool { self.refwd().is_write_vectored() } #[inline(always)] fn poll_flush( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { self.$pinproj().poll_flush(cx) } #[inline(always)] fn poll_shutdown( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { self.$pinproj().poll_shutdown(cx) } } }; }; ($({$($lt:tt)*})? $ty:ty) => { forward_tokio_write!($({$($lt)*})? $ty, pinproj); }; } macro_rules! forward_tokio_rw { ($({$($lt:tt)*})? $ty:ty $(, $pinproj:ident)?) => { forward_tokio_read!($({$($lt)*})? $ty $(, $pinproj)?); forward_tokio_write!($({$($lt)*})? $ty $(, $pinproj)?); }; } macro_rules! forward_tokio_ref_read { ($({$($lt:tt)*})? $ty:ty) => { const _: () = { use ::tokio::io::{AsyncRead, ReadBuf}; use ::std::{io, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncRead for &$ty { #[inline(always)] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut (**self).refwd()).poll_read(cx, buf) } } }; }; } macro_rules! forward_tokio_ref_write { ($({$($lt:tt)*})? $ty:ty) => { const _: () = { use ::tokio::io::AsyncWrite; use ::std::{io::{self, IoSlice}, pin::Pin, task::{Context, Poll}}; impl $(<$($lt)*>)? AsyncWrite for &$ty { #[inline(always)] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut (**self).refwd()).poll_write(cx, buf) } #[inline(always)] fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { Pin::new(&mut (**self).refwd()).poll_write_vectored(cx, bufs) } #[inline(always)] fn is_write_vectored(&self) -> bool { self.refwd().is_write_vectored() } #[inline(always)] fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { Pin::new(&mut (**self).refwd()).poll_flush(cx) } #[inline(always)] fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { Pin::new(&mut (**self).refwd()).poll_shutdown(cx) } } }; }; } macro_rules! forward_tokio_ref_rw { ($({$($lt:tt)*})? $ty:ty) => { forward_tokio_ref_read!($({$($lt)*})? $ty); forward_tokio_ref_write!($({$($lt)*})? $ty); }; } interprocess-2.2.3/src/macros/forward_to_self.rs000064400000000000000000000025131046102023000201510ustar 00000000000000/// Forwards trait methods to inherent ones with the same name and signature. macro_rules! forward_to_self { ( fn $mnm:ident $({$($fgen:tt)*})? (&self $(, $param:ident : $pty:ty)* $(,)?) $(-> $ret:ty)? ) => { #[inline(always)] fn $mnm $(<$($fgen)*>)? (&self, $($param: $pty),*) $(-> $ret)? { self.$mnm($($param),*) } }; ( fn $mnm:ident $({$($fgen:tt)*})? (&mut self $(, $param:ident : $pty:ty),* $(,)?) $(-> $ret:ty)? ) => { #[inline(always)] fn $mnm $(<$($fgen)*>)? (&mut self, $($param: $pty),*) $(-> $ret)? { self.$mnm($($param),*) } }; ( fn $mnm:ident $({$($fgen:tt)*})? (self $(, $param:ident : $pty:ty),* $(,)?) $(-> $ret:ty)? ) => { #[inline(always)] fn $mnm $(<$($fgen)*>)? (self, $($param: $pty),*) $(-> $ret)? { self.$mnm($($param),*) } }; (fn $mnm:ident $({$($fgen:tt)*})? ($($param:ident : $pty:ty),* $(,)?) $(-> $ret:ty)?) => { #[inline(always)] fn $mnm $(<$($fgen)*>)? ($($param: $pty),*) $(-> $ret)? { Self::$mnm($($param),*) } }; ($(fn $mnm:ident $({$($fgen:tt)*})? ($($args:tt)*) $(-> $ret:ty)?);+ $(;)?) => {$( forward_to_self!(fn $mnm $({$($fgen)*})? ($($args)*) $(-> $ret)?); )+}; } interprocess-2.2.3/src/macros/forward_try_clone.rs000064400000000000000000000005731046102023000205200ustar 00000000000000//! Forwarding of `Debug` for newtypes. Is also a derive macro in some sense. macro_rules! forward_try_clone { ($({$($lt:tt)*})? $ty:ty) => { impl $(<$($lt)*>)? crate::TryClone for $ty { #[inline] fn try_clone(&self) -> ::std::io::Result { Ok(Self(crate::TryClone::try_clone(&self.0)?)) } } }; } interprocess-2.2.3/src/macros.rs000064400000000000000000000076511046102023000150020ustar 00000000000000#![allow(unused_macros)] /// Dispatches to a symmetrically named submodule in the target OS module. macro_rules! impmod { ($($osmod:ident)::+ $(as $into:ident)?) => { impmod!($($osmod)::+, self $(as $into)?); }; ($($osmod:ident)::+, $($orig:ident $(as $into:ident)?),* $(,)?) => { #[cfg(unix)] use $crate::os::unix::$($osmod)::+::{$($orig $(as $into)?,)*}; #[cfg(windows)] use $crate::os::windows::$($osmod)::+::{$($orig $(as $into)?,)*}; }; } /// Generates a method that projects `self.0` of type `src` to a `Pin` for type `dst`. macro_rules! pinproj_for_unpin { ($src:ty, $dst:ty) => { impl $src { #[inline(always)] fn pinproj(&mut self) -> ::std::pin::Pin<&mut $dst> { ::std::pin::Pin::new(&mut self.0) } } }; } /// Calls multiple macros, passing the same identifer or type as well as optional per-macro /// parameters. /// /// The identifier or type goes first, then comma-separated macro names without exclamation points. /// To pass per-macro parameters, encase them in parentheses. macro_rules! multimacro { ($pre:tt $ty:ident, $($macro:ident $(($($arg:tt)+))?),+ $(,)?) => {$( $macro!($pre $ty $(, $($arg)+)?); )+}; ($pre:tt $ty:ty, $($macro:ident $(($($arg:tt)+))?),+ $(,)?) => {$( $macro!($pre $ty $(, $($arg)+)?); )+}; ($ty:ident, $($macro:ident $(($($arg:tt)+))?),+ $(,)?) => {$( $macro!($ty $(, $($arg)+)?); )+}; ($ty:ty, $($macro:ident $(($($arg:tt)+))?),+ $(,)?) => {$( $macro!($ty $(, $($arg)+)?); )+}; } /// Generates a method that immutably borrows `self.0` of type `int` for type `ty`. /// /// If `kind` is `&`, `self.0` is borrowed directly. If `kind` is `*`, `self.0` is treated as a /// smart pointer (`Deref` is applied). /// /// The method generated by this macro is used by forwarding macros. macro_rules! forward_rbv { (@$slf:ident, &) => { &$slf.0 }; (@$slf:ident, *) => { &&*$slf.0 }; ($ty:ty, $int:ty, $kind:tt) => { impl $ty { #[inline(always)] fn refwd(&self) -> &$int { forward_rbv!(@self, $kind) } } }; } #[rustfmt::skip] macro_rules! builder_must_use {() => { "builder setters take the entire structure and return it with the corresponding field modified" };} /// Generates public self-by-value setters for builder structures. Assumes that a field of the same /// name is public on `Self`. macro_rules! builder_setters { ($(#[doc = $($doc:expr)+])+ $name:ident : $ty:ty) => { $(#[doc = $($doc)+])+ #[must_use = builder_must_use!()] #[inline(always)] pub fn $name(mut self, $name: $ty) -> Self { self.$name = $name.into(); self } }; ($name:ident : $ty:ty) => { builder_setters!( #[doc = concat!( "Sets the [`", stringify!($name), "`](#structfield.", stringify!($name), ") parameter to the specified value." )] $name : $ty ); }; ($($(#[doc = $($doc:expr)+])* $name:ident : $ty:ty),+ $(,)?) => { $(builder_setters!($(#[doc = $($doc)+])* $name: $ty);)+ }; } /// Creates a public sealed uninhabited type with a bunch of unnecessary trait implementations. macro_rules! tag_enum { ($($(#[$attr:meta])* $tag:ident),+ $(,)?) => {$( $( #[$attr] )* #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum $tag {} impl $crate::Sealed for $tag {} )+}; } /// Generates this module's macro submodules. macro_rules! make_macro_modules { ($($modname:ident),+ $(,)?) => {$( #[macro_use] mod $modname; #[allow(unused_imports)] pub(crate) use $modname::*; )+}; } make_macro_modules! { derive_raw, derive_mut_iorw, derive_trivconv, forward_handle_and_fd, forward_try_clone, forward_to_self, forward_iorw, forward_fmt, } interprocess-2.2.3/src/misc.rs000064400000000000000000000124561046102023000144500ustar 00000000000000#![allow(dead_code)] #[cfg(unix)] use std::os::unix::io::RawFd; use std::{ io, mem::{transmute, MaybeUninit}, num::Saturating, pin::Pin, sync::PoisonError, }; #[cfg(windows)] use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; /// Utility trait that, if used as a supertrait, prevents other crates from implementing the /// trait. pub(crate) trait Sealed {} pub(crate) trait DebugExpectExt: Sized { fn debug_expect(self, msg: &str); } pub(crate) static LOCK_POISON: &str = "unexpected lock poison"; pub(crate) fn poison_error(_: PoisonError) -> io::Error { io::Error::other(LOCK_POISON) } pub(crate) trait OrErrno: Sized { fn true_or_errno(self, f: impl FnOnce() -> T) -> io::Result; #[inline(always)] fn true_val_or_errno(self, value: T) -> io::Result { self.true_or_errno(|| value) } fn false_or_errno(self, f: impl FnOnce() -> T) -> io::Result; #[inline(always)] fn false_val_or_errno(self, value: T) -> io::Result { self.true_or_errno(|| value) } } impl OrErrno for B { #[inline] fn true_or_errno(self, f: impl FnOnce() -> T) -> io::Result { if self.to_bool() { Ok(f()) } else { Err(io::Error::last_os_error()) } } fn false_or_errno(self, f: impl FnOnce() -> T) -> io::Result { if !self.to_bool() { Ok(f()) } else { Err(io::Error::last_os_error()) } } } #[cfg(unix)] pub(crate) trait FdOrErrno: Sized { fn fd_or_errno(self) -> io::Result; } #[cfg(unix)] impl FdOrErrno for RawFd { #[inline] fn fd_or_errno(self) -> io::Result { (self != -1).true_val_or_errno(self) } } #[cfg(windows)] pub(crate) trait HandleOrErrno: Sized { fn handle_or_errno(self) -> io::Result; } #[cfg(windows)] impl HandleOrErrno for HANDLE { #[inline] fn handle_or_errno(self) -> io::Result { (self != INVALID_HANDLE_VALUE).true_val_or_errno(self) } } pub(crate) trait ToBool { fn to_bool(self) -> bool; } impl ToBool for bool { #[inline(always)] fn to_bool(self) -> bool { self } } impl ToBool for i32 { #[inline(always)] fn to_bool(self) -> bool { self != 0 } } pub(crate) trait BoolExt { fn to_i32(self) -> i32; fn to_usize(self) -> usize; } impl BoolExt for bool { #[inline(always)] #[rustfmt::skip] // oh come on now fn to_i32(self) -> i32 { if self { 1 } else { 0 } } #[inline(always)] #[rustfmt::skip] fn to_usize(self) -> usize { if self { 1 } else { 0 } } } pub(crate) trait AsPtr { #[inline(always)] fn as_ptr(&self) -> *const Self { self } } impl AsPtr for T {} pub(crate) trait AsMutPtr { #[inline(always)] fn as_mut_ptr(&mut self) -> *mut Self { self } } impl AsMutPtr for T {} impl DebugExpectExt for Result { #[inline] #[track_caller] fn debug_expect(self, msg: &str) { if cfg!(debug_assertions) { self.expect(msg); } } } impl DebugExpectExt for Option { #[inline] #[track_caller] fn debug_expect(self, msg: &str) { if cfg!(debug_assertions) { self.expect(msg); } } } pub(crate) trait NumExt: Sized { #[inline] fn saturate(self) -> Saturating { Saturating(self) } } impl NumExt for T {} pub(crate) trait SubUsizeExt: TryInto + Sized { fn to_usize(self) -> usize; } pub(crate) trait SubIsizeExt: TryInto + Sized { fn to_isize(self) -> isize; } macro_rules! impl_subsize { ($src:ident to usize) => { impl SubUsizeExt for $src { #[inline(always)] #[allow(clippy::as_conversions)] fn to_usize(self) -> usize { self as usize } } }; ($src:ident to isize) => { impl SubIsizeExt for $src { #[inline(always)] #[allow(clippy::as_conversions)] fn to_isize(self) -> isize { self as isize } } }; ($($src:ident to $dst:ident)+) => {$( impl_subsize!($src to $dst); )+}; } // See platform_check.rs. impl_subsize! { u8 to usize u16 to usize u32 to usize i8 to isize i16 to isize i32 to isize u8 to isize u16 to isize } // TODO(2.3.0) find a more elegant way pub(crate) trait RawOsErrorExt { fn eeq(self, other: u32) -> bool; } impl RawOsErrorExt for Option { #[inline(always)] #[allow(clippy::as_conversions)] fn eeq(self, other: u32) -> bool { match self { Some(n) => n as u32 == other, None => false, } } } #[inline(always)] pub(crate) fn weaken_buf_init(r: &[T]) -> &[MaybeUninit] { unsafe { // SAFETY: same slice, weaker refinement transmute(r) } } #[inline(always)] pub(crate) fn weaken_buf_init_mut(r: &mut [T]) -> &mut [MaybeUninit] { unsafe { // SAFETY: same here transmute(r) } } #[inline(always)] pub(crate) unsafe fn assume_slice_init(r: &[MaybeUninit]) -> &[T] { unsafe { // SAFETY: same slice, stronger refinement transmute(r) } } pub(crate) trait UnpinExt: Unpin { #[inline] fn pin(&mut self) -> Pin<&mut Self> { Pin::new(self) } } impl UnpinExt for T {} interprocess-2.2.3/src/os/unix/c_wrappers.rs000064400000000000000000000222551046102023000172640ustar 00000000000000#[allow(unused_imports)] use crate::{FdOrErrno, OrErrno}; #[cfg(target_os = "android")] use std::os::android::net::SocketAddrExt; #[cfg(target_os = "linux")] use std::os::linux::net::SocketAddrExt; use { super::unixprelude::*, crate::AsPtr, libc::{sockaddr_un, AF_UNIX}, std::{ io, mem::{transmute, zeroed}, os::unix::net::SocketAddr, }, }; macro_rules! cfg_atomic_cloexec { ($($block:tt)+) => { #[cfg(any( // List taken from the standard library, file std/sys/pal/unix/net.rs. target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "hurd", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "nto", ))] $($block)+ }; } macro_rules! cfg_no_atomic_cloexec { ($($block:tt)+) => { #[cfg(not(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "hurd", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "nto", )))] $($block)+ }; } cfg_atomic_cloexec! { pub(super) unsafe fn fcntl_int( fd: BorrowedFd<'_>, cmd: c_int, val: c_int, ) -> io::Result { unsafe { libc::fcntl(fd.as_raw_fd(), cmd, val) }.fd_or_errno() } } pub(super) fn duplicate_fd(fd: BorrowedFd<'_>) -> io::Result { cfg_atomic_cloexec! {{ let new_fd = unsafe { fcntl_int(fd, libc::F_DUPFD_CLOEXEC, 0)? }; Ok(unsafe { OwnedFd::from_raw_fd(new_fd) }) }} cfg_no_atomic_cloexec! {{ let new_fd = unsafe { libc::dup(fd.as_raw_fd()) .fd_or_errno() .map(|fd| OwnedFd::from_raw_fd(fd))? }; set_cloexec(new_fd.as_fd())?; Ok(new_fd) }} } fn get_flflags(fd: BorrowedFd<'_>) -> io::Result { unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFL, 0) }.fd_or_errno() } fn set_flflags(fd: BorrowedFd<'_>, flags: c_int) -> io::Result<()> { unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, flags) != -1 }.true_val_or_errno(()) } pub(super) fn set_nonblocking(fd: BorrowedFd<'_>, nonblocking: bool) -> io::Result<()> { let old_flags = get_flflags(fd)? & libc::O_NONBLOCK; set_flflags(fd, old_flags | if nonblocking { libc::O_NONBLOCK } else { 0 }) } cfg_no_atomic_cloexec! { fn set_cloexec(fd: BorrowedFd<'_>) -> io::Result<()> { unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) != -1 }.true_val_or_errno(()) } } pub(super) fn set_socket_mode(fd: BorrowedFd<'_>, mode: mode_t) -> io::Result<()> { unsafe { libc::fchmod(fd.as_raw_fd(), mode) != -1 }.true_val_or_errno(()) } pub(super) const CAN_CREATE_NONBLOCKING: bool = cfg!(any(target_os = "linux", target_os = "android")); #[cfg(not(any(target_os = "linux", target_os = "android")))] use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; #[cfg(not(any(target_os = "linux", target_os = "android")))] static CAN_FCHMOD_SOCKETS: AtomicBool = AtomicBool::new(true); fn can_fchmod_sockets() -> bool { #[cfg(any(target_os = "linux", target_os = "android"))] { true } #[cfg(not(any(target_os = "linux", target_os = "android")))] { CAN_FCHMOD_SOCKETS.load(Relaxed) } } fn can_not_fchmod_sockets() { #[cfg(any(target_os = "linux", target_os = "android"))] { unreachable!() } #[cfg(not(any(target_os = "linux", target_os = "android")))] { CAN_FCHMOD_SOCKETS.store(false, Relaxed) } } /// Creates a Unix domain socket of the given type. If on Linux or Android and `nonblocking` is /// `true`, also makes it nonblocking. #[allow(unused_mut)] fn create_socket(ty: c_int, nonblocking: bool) -> io::Result { // Suppress warning on platforms that don't support the flag. let _ = nonblocking; let mut flags = 0; #[cfg(any(target_os = "linux", target_os = "android"))] { if nonblocking { flags |= libc::SOCK_NONBLOCK; } } cfg_atomic_cloexec! {{ flags |= libc::SOCK_CLOEXEC; }} let fd = unsafe { libc::socket(AF_UNIX, ty | flags, 0) } .fd_or_errno() .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })?; cfg_no_atomic_cloexec! {{ set_cloexec(fd.as_fd())?; }} Ok(fd) } fn addr_to_slice(addr: &SocketAddr) -> (&[u8], usize) { if let Some(slice) = addr.as_pathname() { (slice.as_os_str().as_bytes(), 0) } else { #[cfg(any(target_os = "linux", target_os = "android"))] if let Some(slice) = addr.as_abstract_name() { return (slice, 1); } (&[], 0) } } #[allow(clippy::as_conversions)] const SUN_PATH_OFFSET: usize = unsafe { // This code may or may not have been copied from the standard library let addr = zeroed::(); let base = (&addr as *const sockaddr_un).cast::(); let path = &addr.sun_path as *const c_char; path.byte_offset_from(base) as usize }; #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects, clippy::as_conversions)] fn bind(fd: BorrowedFd<'_>, addr: &SocketAddr) -> io::Result<()> { let (path, extra) = addr_to_slice(addr); let path = unsafe { transmute::<&[u8], &[libc::c_char]>(path) }; let mut addr = unsafe { zeroed::() }; addr.sun_family = AF_UNIX as _; addr.sun_path[extra..(extra + path.len())].copy_from_slice(path); let len = path.len() + extra + SUN_PATH_OFFSET; unsafe { libc::bind( fd.as_raw_fd(), addr.as_ptr().cast(), // It's impossible for this to exceed socklen_t::MAX, since it came from a valid // SocketAddr len as _, ) != -1 } .true_val_or_errno(()) } fn listen(fd: BorrowedFd<'_>) -> io::Result<()> { // The standard library does this #[cfg(any( target_os = "windows", target_os = "redox", target_os = "espidf", target_os = "horizon" ))] const BACKLOG: libc::c_int = 128; #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "macos" ))] const BACKLOG: libc::c_int = -1; #[cfg(not(any( target_os = "windows", target_os = "redox", target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "macos", target_os = "espidf", target_os = "horizon" )))] const BACKLOG: libc::c_int = libc::SOMAXCONN; unsafe { libc::listen(fd.as_raw_fd(), BACKLOG) != -1 }.true_val_or_errno(()) } struct WithUmask { new: mode_t, old: mode_t, } impl WithUmask { pub fn set(new: mode_t) -> Self { Self { new, old: Self::umask(new) } } fn umask(mode: mode_t) -> mode_t { unsafe { libc::umask(mode) } } } impl Drop for WithUmask { fn drop(&mut self) { let expected_new = Self::umask(self.old); assert_eq!(self.new, expected_new, "parallel umask use detected"); } } pub(super) fn create_server( ty: c_int, addr: &SocketAddr, nonblocking: bool, mode: Option, ) -> io::Result { let dg = if let Some(mode) = mode { // This used to forbid modes with the executable bit set, but no longer does. That is the // OS's business, not ours. if can_fchmod_sockets() { let sock = create_socket(ty, nonblocking)?; match set_socket_mode(sock.as_fd(), mode) { Ok(()) => return bind_and_listen(sock, addr, ()), Err(e) if e.kind() == io::ErrorKind::InvalidInput => can_not_fchmod_sockets(), Err(e) => return Err(e), } } // If we haven't returned by this point, we can't fchmod sockets. Invert the mode to // obtain an anti-mask, call umask() and return a drop guard that lasts until the end of // the whole function's scope. Some(WithUmask::set(!mode & 0o777)) } else { // If no file mode had to be set, we don't get a umask drop guard. None }; // The below code runs under umask if necessary (we race in this muthafucka, better get yo // secure code ass back to Linux). To be clear, if a mode for the socket isn't specified, the // below code runs on both fchmod and non-fchmod platforms; the unifying property here is that // the socket gets its mode solely from the umask. let sock = create_socket(ty, nonblocking)?; bind_and_listen(sock, addr, dg) } fn bind_and_listen(sock: OwnedFd, addr: &SocketAddr, drop_guard: T) -> io::Result { bind(sock.as_fd(), addr)?; drop(drop_guard); // Revert umask as soon as possible listen(sock.as_fd())?; Ok(sock) } #[allow(dead_code)] pub(super) fn shutdown(fd: BorrowedFd<'_>, how: std::net::Shutdown) -> io::Result<()> { use std::net::Shutdown::*; let how = match how { Read => libc::SHUT_RD, Write => libc::SHUT_WR, Both => libc::SHUT_RDWR, }; unsafe { libc::shutdown(fd.as_raw_fd(), how) != -1 }.true_val_or_errno(()) } interprocess-2.2.3/src/os/unix/cfg_doc_templates.rs000064400000000000000000000034711046102023000205600ustar 00000000000000/* // You can't generate those with macros just yet, so copypasting is the way for now. #[cfg_attr( // uds_ucred template feature = "doc_cfg", doc(cfg(any( target_os = "linux", target_os = "redox", target_os = "android", target_os = "fuchsia", ))) )] #[cfg_attr( // uds_cmsgcred template feature = "doc_cfg", doc(cfg(any( target_os = "freebsd", target_os = "dragonfly", ))) )] #[cfg_attr( // uds_credentials template feature = "doc_cfg", doc(cfg(any( target_os = "linux", target_os = "redox", target_os = "android", target_os = "fuchsia", target_os = "freebsd", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos", ))) )] #[cfg_attr( // uds_ancillary_credentials template feature = "doc_cfg", doc(cfg(any( target_os = "linux", target_os = "redox", target_os = "android", target_os = "fuchsia", target_os = "freebsd", target_os = "dragonfly", ))) )] #[cfg_attr( // uds_cont_credentials template feature = "doc_cfg", doc(cfg(any( target_os = "linux", target_os = "redox", target_os = "android", target_os = "fuchsia", target_os = "freebsd", ))) )] #[cfg_attr( // uds_sockcred template feature = "doc_cfg", doc(cfg(target_os = "netbsd")) )] #[cfg_attr( // uds_sockcred2 template feature = "doc_cfg", doc(cfg(target_os = "freebsd")) )] #[cfg_attr( // uds_linux_namespace template feature = "doc_cfg", doc(cfg(any(target_os = "linux", target_os = "android"))) )] */ interprocess-2.2.3/src/os/unix/fdops.rs000064400000000000000000000045441046102023000162330ustar 00000000000000use { super::{c_wrappers, unixprelude::*}, crate::{weaken_buf_init_mut, OrErrno, TryClone}, std::{ io::{self, prelude::*, IoSlice, IoSliceMut}, mem::MaybeUninit, }, }; #[allow(clippy::as_conversions)] fn i2u(i: isize) -> usize { i as usize } #[repr(transparent)] pub(super) struct FdOps(pub(super) OwnedFd); impl FdOps { pub(super) fn read_uninit( fd: BorrowedFd<'_>, buf: &mut [MaybeUninit], ) -> io::Result { let length_to_read = buf.len(); let bytes_read = unsafe { libc::read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), length_to_read) }; (bytes_read >= 0).true_val_or_errno(i2u(bytes_read)) } pub(super) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result { let length_to_write = buf.len(); let bytes_written = unsafe { libc::write(fd.as_raw_fd(), buf.as_ptr().cast(), length_to_write) }; (bytes_written >= 0).true_val_or_errno(i2u(bytes_written)) } } impl Read for &FdOps { fn read(&mut self, buf: &mut [u8]) -> io::Result { FdOps::read_uninit(self.as_fd(), weaken_buf_init_mut(buf)) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { let num_bufs = c_int::try_from(bufs.len()).unwrap_or(c_int::MAX); let bytes_read = unsafe { libc::readv(self.0.as_raw_fd(), bufs.as_ptr().cast(), num_bufs) }; (bytes_read >= 0).true_val_or_errno(i2u(bytes_read)) } // FUTURE can_vector } impl Write for &FdOps { fn write(&mut self, buf: &[u8]) -> io::Result { FdOps::write(self.as_fd(), buf) } fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { let num_bufs = c_int::try_from(bufs.len()).unwrap_or(c_int::MAX); let bytes_written = unsafe { libc::writev(self.0.as_raw_fd(), bufs.as_ptr().cast(), num_bufs) }; (bytes_written >= 0).true_val_or_errno(i2u(bytes_written)) } // FUTURE can_vector fn flush(&mut self) -> io::Result<()> { unsafe { libc::fsync(self.0.as_raw_fd()) >= 0 }.true_val_or_errno(()) } } impl TryClone for FdOps { fn try_clone(&self) -> std::io::Result { let fd = c_wrappers::duplicate_fd(self.0.as_fd())?; Ok(Self(fd)) } } multimacro! { FdOps, forward_handle, forward_debug, derive_raw, } interprocess-2.2.3/src/os/unix/fifo_file.rs000064400000000000000000000042721046102023000170400ustar 00000000000000//! Creation of FIFO files. //! //! On Windows, named pipes can be compared to Unix domain sockets: they can have multiple duplex //! connections on a single path, and the data can be chosen to either preserve or erase the message //! boundaries, resulting in a reliable performant alternative to TCP and UDP working in the bounds //! of a single machine. Those Unix domain sockets are employed by `interprocess` for local sockets //! via [an implementation provided by the standard library](std::os::unix::net). //! //! On Unix, named pipes, referred to as "FIFO files" in this crate, are just files which can have //! a sender and a receiver communicating with each other in one direction without message //! boundaries. If further receivers try to open the file, they will simply receive nothing at all; //! if further senders are connected, the data mixes in an unpredictable way, making it unusable. //! Therefore, FIFOs are to be used specifically to conveniently connect two applications through a //! known path which works like a pipe and nothing else. //! //! ## Usage //! The [`create_fifo()`] function serves for a FIFO file creation. Opening FIFO files works via the //! standard [`File`](std::fs::File)s, opened either only for sending or only for receiving. //! Deletion works the same way as with any regular file, via //! [`remove_file()`](std::fs::remove_file). use { super::unixprelude::*, crate::OrErrno, std::{ffi::CString, io, path::Path}, }; /// Creates a FIFO file at the specified path with the specified permissions. /// /// Since the `mode` parameter is masked with the [`umask`], it's best to leave it at `0o777` unless /// a different value is desired. /// /// ## System calls /// - [`mkfifo`] /// /// [`mkfifo`]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkfifo.html /// [`umask`]: https://en.wikipedia.org/wiki/Umask pub fn create_fifo>(path: P, mode: mode_t) -> io::Result<()> { _create_fifo(path.as_ref(), mode) } fn _create_fifo(path: &Path, mode: mode_t) -> io::Result<()> { let path = CString::new(path.as_os_str().as_bytes())?; unsafe { libc::mkfifo(path.as_bytes_with_nul().as_ptr().cast(), mode) != -1 } .true_val_or_errno(()) } interprocess-2.2.3/src/os/unix/imports.rs000064400000000000000000000000011046102023000165750ustar 00000000000000 interprocess-2.2.3/src/os/unix/local_socket/dispatch_sync.rs000064400000000000000000000006601046102023000224100ustar 00000000000000use { super::super::uds_local_socket as uds_impl, crate::local_socket::{prelude::*, Listener, ListenerOptions, Name, Stream}, std::io, }; #[inline] pub fn from_options(options: ListenerOptions<'_>) -> io::Result { options.create_sync_as::().map(Listener::from) } #[inline] pub fn connect(name: Name<'_>) -> io::Result { uds_impl::Stream::connect(name).map(Stream::from) } interprocess-2.2.3/src/os/unix/local_socket/dispatch_tokio.rs000064400000000000000000000007441046102023000225640ustar 00000000000000use { super::super::uds_local_socket::tokio as uds_impl, crate::local_socket::{ tokio::{prelude::*, Listener, Stream}, ListenerOptions, Name, }, std::io, }; #[inline] pub fn from_options(options: ListenerOptions<'_>) -> io::Result { options.create_tokio_as::().map(Listener::from) } #[inline] pub async fn connect(name: Name<'_>) -> io::Result { uds_impl::Stream::connect(name).await.map(Stream::from) } interprocess-2.2.3/src/os/unix/local_socket/name_type.rs000064400000000000000000000107411046102023000215370ustar 00000000000000use { crate::local_socket::{Name, NameInner, NameType, NamespacedNameType, PathNameType}, std::{ borrow::Cow, ffi::{CStr, OsStr, OsString}, io, os::unix::prelude::*, }, }; fn c2os(ccow: Cow<'_, CStr>) -> Cow<'_, OsStr> { match ccow { Cow::Borrowed(cstr) => Cow::Borrowed(OsStr::from_bytes(cstr.to_bytes())), Cow::Owned(cstring) => Cow::Owned(OsString::from_vec(cstring.into_bytes())), } } tag_enum!( /// [Mapping](NameType) that produces local socket names referring to Unix domain sockets bound to /// the filesystem. /// /// For Unix domain sockets residing in the Linux abstract namespace, see `AbstractNsUdSocket` /// instead. FilesystemUdSocket); impl NameType for FilesystemUdSocket { fn is_supported() -> bool { true } } impl PathNameType for FilesystemUdSocket { #[inline] fn map(path: Cow<'_, OsStr>) -> io::Result> { for b in path.as_bytes() { if *b == 0 { return Err(io::Error::new( io::ErrorKind::InvalidInput, "filesystem paths cannot contain interior nuls", )); } } Ok(Name(NameInner::UdSocketPath(path))) } } impl PathNameType for FilesystemUdSocket { #[inline] fn map(path: Cow<'_, CStr>) -> io::Result> { Self::map(c2os(path)) } } tag_enum!( /// [Mapping](NameType) that produces local socket names referring to Unix domain sockets bound to /// special locations in the filesystems that are interpreted as dedicated namespaces. /// /// This is the substitute for `AbstractNsUdSocket` on non-Linux Unices, and is the only available /// [namespaced name type](NamespacedNameType) on those systems. SpecialDirUdSocket); impl NameType for SpecialDirUdSocket { fn is_supported() -> bool { true } } impl NamespacedNameType for SpecialDirUdSocket { #[inline] fn map(name: Cow<'_, OsStr>) -> io::Result> { for b in name.as_bytes() { if *b == 0 { return Err(io::Error::new( io::ErrorKind::InvalidInput, "special directory-bound names cannot contain interior nuls", )); } } Ok(Name(NameInner::UdSocketPseudoNs(name))) } } impl NamespacedNameType for SpecialDirUdSocket { #[inline] fn map(name: Cow<'_, CStr>) -> io::Result> { Self::map(c2os(name)) } } #[cfg(any(target_os = "linux", target_os = "android"))] tag_enum!( /// [Mapping](NameType) that produces local socket names referring to Unix domain sockets bound to /// the Linux abstract namespace. #[cfg_attr(feature = "doc_cfg", doc(cfg(any(target_os = "linux", target_os = "android"))))] AbstractNsUdSocket); #[cfg(any(target_os = "linux", target_os = "android"))] impl NameType for AbstractNsUdSocket { fn is_supported() -> bool { true // Rust is unsupported on Linux below version 3.2 } } #[cfg(any(target_os = "linux", target_os = "android"))] impl NamespacedNameType for AbstractNsUdSocket { #[inline] fn map(name: Cow<'_, OsStr>) -> io::Result> { let name = match name { Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()), Cow::Owned(o) => Cow::Owned(o.into_vec()), }; Ok(Name(NameInner::UdSocketNs(name))) } } #[cfg(any(target_os = "linux", target_os = "android"))] impl NamespacedNameType for AbstractNsUdSocket { #[inline] fn map(name: Cow<'_, CStr>) -> io::Result> { Self::map(c2os(name)) } } macro_rules! map_generic { (path $name:ident for $str:ident) => { pub(crate) fn $name(path: Cow<'_, $str>) -> io::Result> { FilesystemUdSocket::map(path) } }; (namespaced $name:ident for $str:ident) => { pub(crate) fn $name(name: Cow<'_, $str>) -> io::Result> { #[cfg(any(target_os = "linux", target_os = "android"))] { AbstractNsUdSocket::map(name) } #[cfg(not(any(target_os = "linux", target_os = "android")))] { SpecialDirUdSocket::map(name) } } }; ($($type:ident $name:ident for $str:ident)+) => {$( map_generic!($type $name for $str); )+}; } map_generic! { path map_generic_path_osstr for OsStr path map_generic_path_cstr for CStr namespaced map_generic_namespaced_osstr for OsStr namespaced map_generic_namespaced_cstr for CStr } interprocess-2.2.3/src/os/unix/local_socket.rs000064400000000000000000000040521046102023000175540ustar 00000000000000//! Unix-specific local socket features. pub(crate) mod dispatch_sync; #[cfg(feature = "tokio")] pub(crate) mod dispatch_tokio; pub(crate) mod name_type; use crate::{local_socket::ListenerOptions, Sealed}; pub use name_type::*; /// Unix-specific [listener options](ListenerOptions). #[allow(private_bounds)] pub trait ListenerOptionsExt: Sized + Sealed { /// Sets the file mode (Unix permissions) to be applied to the socket file. /// /// **Not all Unix systems respect this mode when checking permissions in `connect()`!** Linux /// is known to perform full permission checks for all directories along the path to the /// socket file in addition to checking permissions on the socket file itself, while FreeBSD /// only checks directories but not the socket file itself. If you expect your program to be /// used on a wide range of Unix systems, do not rely on this as a security mechanism. /// /// # Implementation notes /// An opportunistic `fchmod()` is performed on the socket. If the system responds with a /// `EINVAL`, Interprocess concludes that `fchmod()` on sockets is not supported on the /// platform, remembers this fact in an atomic global variable and falls back to a temporary /// `umask` change. /// /// Linux is known to support `fchmod()` on Unix domain sockets, while FreeBSD is known not to. /// /// Note that the fallback behavior **inherently racy:** if you specify this mode as, say, /// 666₈ and have another thread create a file during the critical section between the first /// `umask()` call and the one performed just before returning from `.create_*()`, that file /// will have mode 666₈. There is nothing Interprocess can do about this, as POSIX prescribes /// the `umask` to be shared across threads. #[must_use = builder_must_use!()] fn mode(self, mode: libc::mode_t) -> Self; } impl ListenerOptionsExt for ListenerOptions<'_> { #[inline(always)] fn mode(mut self, mode: libc::mode_t) -> Self { self.mode = Some(mode); self } } interprocess-2.2.3/src/os/unix/uds_local_socket/listener.rs000064400000000000000000000066321046102023000222620ustar 00000000000000use { super::{name_to_addr, ReclaimGuard, Stream}, crate::{ local_socket::{ traits::{self, Stream as _}, ListenerNonblockingMode, ListenerOptions, }, os::unix::c_wrappers, }, std::{ io, iter::FusedIterator, os::{ fd::{AsFd, BorrowedFd, OwnedFd}, unix::net::UnixListener, }, sync::atomic::{AtomicBool, Ordering::SeqCst}, }, }; /// Wrapper around [`UnixListener`] that implements [`Listener`](traits::Listener). #[derive(Debug)] pub struct Listener { pub(super) listener: UnixListener, pub(super) reclaim: ReclaimGuard, pub(super) nonblocking_streams: AtomicBool, } impl Listener { fn decode_listen_error(error: io::Error) -> io::Error { io::Error::from(match error.kind() { io::ErrorKind::AlreadyExists => io::ErrorKind::AddrInUse, _ => return error, }) } } impl crate::Sealed for Listener {} impl traits::Listener for Listener { type Stream = Stream; fn from_options(options: ListenerOptions<'_>) -> io::Result { let nonblocking = options.nonblocking.accept_nonblocking(); let listener = c_wrappers::create_server( libc::SOCK_STREAM, &name_to_addr(options.name.borrow(), true)?, nonblocking, options.mode, ) .map(UnixListener::from) .map_err(Self::decode_listen_error)?; if !c_wrappers::CAN_CREATE_NONBLOCKING && nonblocking { listener.set_nonblocking(true)?; } Ok(Self { listener, reclaim: options .reclaim_name .then(|| options.name.into_owned()) .map(ReclaimGuard::new) .unwrap_or_default(), nonblocking_streams: AtomicBool::new(options.nonblocking.stream_nonblocking()), }) } #[inline] fn accept(&self) -> io::Result { // TODO(2.3.0) make use of the second return value in some shape or form let stream = self.listener.accept().map(|(s, _)| Stream::from(s))?; if self.nonblocking_streams.load(SeqCst) { stream.set_nonblocking(true)?; } Ok(stream) } #[inline] fn set_nonblocking(&self, nonblocking: ListenerNonblockingMode) -> io::Result<()> { use ListenerNonblockingMode::*; self.listener.set_nonblocking(matches!(nonblocking, Accept | Both))?; self.nonblocking_streams.store(matches!(nonblocking, Stream | Both), SeqCst); Ok(()) } fn do_not_reclaim_name_on_drop(&mut self) { self.reclaim.forget(); } } impl Iterator for Listener { type Item = io::Result; #[inline(always)] fn next(&mut self) -> Option { Some(traits::Listener::accept(self)) } } impl FusedIterator for Listener {} impl From for UnixListener { fn from(mut l: Listener) -> Self { l.reclaim.forget(); l.listener } } impl AsFd for Listener { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { self.listener.as_fd() } } impl From for OwnedFd { #[inline] fn from(l: Listener) -> Self { UnixListener::from(l).into() } } impl From for Listener { fn from(fd: OwnedFd) -> Self { Listener { listener: fd.into(), reclaim: ReclaimGuard::default(), nonblocking_streams: AtomicBool::new(false), } } } interprocess-2.2.3/src/os/unix/uds_local_socket/stream.rs000064400000000000000000000064441046102023000217310ustar 00000000000000use { super::name_to_addr, crate::{ error::ReuniteError, local_socket::{ traits::{self, ReuniteResult}, ConcurrencyDetector, LocalSocketSite, Name, }, Sealed, TryClone, }, std::{ io::{self, prelude::*, IoSlice, IoSliceMut}, os::{fd::OwnedFd, unix::net::UnixStream}, sync::Arc, }, }; /// Wrapper around [`UnixStream`] that implements [`Stream`](traits::Stream). #[derive(Debug)] pub struct Stream(pub(super) UnixStream, ConcurrencyDetector); impl Sealed for Stream {} impl traits::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; fn connect(name: Name<'_>) -> io::Result { UnixStream::connect_addr(&name_to_addr(name, false)?).map(Self::from) } #[inline] fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { self.0.set_nonblocking(nonblocking) } #[inline] fn split(self) -> (RecvHalf, SendHalf) { let arc = Arc::new(self); (RecvHalf(Arc::clone(&arc)), SendHalf(arc)) } #[inline] #[allow(clippy::unwrap_in_result)] fn reunite(rh: RecvHalf, sh: SendHalf) -> ReuniteResult { if !Arc::ptr_eq(&rh.0, &sh.0) { return Err(ReuniteError { rh, sh }); } drop(rh); let inner = Arc::into_inner(sh.0).expect("stream half inexplicably copied"); Ok(inner) } } impl Read for &Stream { fn read(&mut self, buf: &mut [u8]) -> io::Result { let _guard = self.1.lock(); (&mut &self.0).read(buf) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { let _guard = self.1.lock(); (&mut &self.0).read_vectored(bufs) } // FUTURE is_read_vectored } impl Write for &Stream { fn write(&mut self, buf: &[u8]) -> io::Result { let _guard = self.1.lock(); (&mut &self.0).write(buf) } fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { let _guard = self.1.lock(); (&mut &self.0).write_vectored(bufs) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } // FUTURE is_write_vectored } impl From for Stream { fn from(s: UnixStream) -> Self { Self(s, ConcurrencyDetector::new()) } } impl From for Stream { fn from(fd: OwnedFd) -> Self { UnixStream::from(fd).into() } } impl TryClone for Stream { #[inline] fn try_clone(&self) -> std::io::Result { self.0.try_clone().map(Self::from) } } multimacro! { Stream, forward_asinto_handle(unix), derive_sync_mut_rw, } /// [`Stream`]'s receive half, implemented using [`Arc`]. #[derive(Debug)] pub struct RecvHalf(pub(super) Arc); impl Sealed for RecvHalf {} impl traits::RecvHalf for RecvHalf { type Stream = Stream; } multimacro! { RecvHalf, forward_rbv(Stream, *), forward_sync_ref_read, forward_as_handle, derive_sync_mut_read, } /// [`Stream`]'s send half, implemented using [`Arc`]. #[derive(Debug)] pub struct SendHalf(pub(super) Arc); impl Sealed for SendHalf {} impl traits::SendHalf for SendHalf { type Stream = Stream; } multimacro! { SendHalf, forward_rbv(Stream, *), forward_sync_ref_write, forward_as_handle, derive_sync_mut_write, } interprocess-2.2.3/src/os/unix/uds_local_socket/tokio/listener.rs000064400000000000000000000051361046102023000234050ustar 00000000000000use { super::Stream, crate::{ local_socket::{ prelude::*, traits::tokio as traits, ListenerNonblockingMode, ListenerOptions, }, os::unix::uds_local_socket::{listener::Listener as SyncListener, ReclaimGuard}, Sealed, }, std::{ fmt::{self, Debug, Formatter}, io, os::unix::prelude::*, }, tokio::net::UnixListener, }; /// Wrapper around [`UnixListener`] that implements [`Listener`](traits::Listener). pub struct Listener { listener: UnixListener, reclaim: ReclaimGuard, } impl Sealed for Listener {} impl traits::Listener for Listener { type Stream = Stream; fn from_options(options: ListenerOptions<'_>) -> io::Result { options .nonblocking(ListenerNonblockingMode::Both) .create_sync_as::() .and_then(|mut sync| { let reclaim = sync.reclaim.take(); Ok(Self { listener: UnixListener::from_std(sync.into())?, reclaim }) }) } async fn accept(&self) -> io::Result { let inner = self.listener.accept().await?.0; Ok(Stream::from(inner)) } fn do_not_reclaim_name_on_drop(&mut self) { self.reclaim.forget(); } } /// Does not assume that the sync `Listener` is in nonblocking mode, setting it to /// `ListenerNonblockingMode::Both` automatically. // TODO(3.0.0) remove handholding and assume nonblocking impl TryFrom for Listener { type Error = io::Error; fn try_from(mut sync: SyncListener) -> io::Result { sync.set_nonblocking(ListenerNonblockingMode::Both)?; let reclaim = sync.reclaim.take(); Ok(Self { listener: UnixListener::from_std(sync.into())?, reclaim }) } } impl Debug for Listener { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Listener") .field("fd", &self.listener.as_raw_fd()) .field("reclaim", &self.reclaim) .finish() } } impl AsFd for Listener { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { self.listener.as_fd() } } impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(mut slf: Listener) -> io::Result { slf.listener.into_std().map(|s| { slf.reclaim.forget(); s.into() }) } } /// Does not assume that the listener is in nonblocking mode, setting it to /// `ListenerNonblockingMode::Both` automatically. impl TryFrom for Listener { type Error = io::Error; fn try_from(fd: OwnedFd) -> io::Result { Self::try_from(SyncListener::from(fd)) } } interprocess-2.2.3/src/os/unix/uds_local_socket/tokio/stream.rs000064400000000000000000000146051046102023000230540ustar 00000000000000use { super::super::name_to_addr, crate::{ error::ReuniteError, local_socket::{traits::tokio as traits, Name}, Sealed, }, std::{ io::{self, ErrorKind::WouldBlock}, os::{ fd::{AsFd, OwnedFd}, unix::{ net::{SocketAddr, UnixStream as SyncUnixStream}, prelude::BorrowedFd, }, }, pin::Pin, task::{ready, Context, Poll}, }, tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, net::{ unix::{OwnedReadHalf as RecvHalfImpl, OwnedWriteHalf as SendHalfImpl}, UnixStream, }, }, }; /// Wrapper around [`UnixStream`] that implements [`Stream`](traits::Stream). #[derive(Debug)] pub struct Stream(pub(super) UnixStream); impl Sealed for Stream {} impl Stream { #[allow(clippy::unwrap_used)] async fn _connect(addr: SocketAddr) -> io::Result { #[cfg(any(target_os = "linux", target_os = "android"))] { #[cfg(target_os = "android")] use std::os::android::net::SocketAddrExt; #[cfg(target_os = "linux")] use std::os::linux::net::SocketAddrExt; if addr.as_abstract_name().is_some() { return tokio::task::spawn_blocking(move || { let stream = SyncUnixStream::connect_addr(&addr)?; stream.set_nonblocking(true)?; Ok::<_, io::Error>(stream) }) .await?? .try_into(); } } UnixStream::connect(addr.as_pathname().unwrap()).await } } impl traits::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; async fn connect(name: Name<'_>) -> io::Result { Self::_connect(name_to_addr(name, false)?).await.map(Self::from) } fn split(self) -> (RecvHalf, SendHalf) { let (r, w) = self.0.into_split(); (RecvHalf(r), SendHalf(w)) } #[inline] fn reunite(rh: RecvHalf, sh: SendHalf) -> Result> { rh.0.reunite(sh.0).map(Self::from).map_err(|tokio::net::unix::ReuniteError(rh, sh)| { ReuniteError { rh: RecvHalf(rh), sh: SendHalf(sh) } }) } } fn ioloop( mut try_io: impl FnMut() -> io::Result, mut poll_read_ready: impl FnMut() -> Poll>, ) -> Poll> { loop { match try_io() { Err(e) if e.kind() == WouldBlock => ready!(poll_read_ready()?), els => return Poll::Ready(els), }; } } multimacro! { Stream, pinproj_for_unpin(UnixStream), forward_rbv(UnixStream, &), forward_tokio_rw, forward_as_handle(unix), derive_trivial_conv(UnixStream), } impl AsyncRead for &Stream { #[inline] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { ioloop(|| self.0.try_read_buf(buf), || self.0.poll_read_ready(cx)).map(|e| e.map(|_| ())) } } impl AsyncWrite for &Stream { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { ioloop(|| self.0.try_write(buf), || self.0.poll_write_ready(cx)) } #[inline] fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { ioloop(|| self.0.try_write_vectored(bufs), || self.0.poll_write_ready(cx)) } #[inline] fn is_write_vectored(&self) -> bool { self.0.is_write_vectored() } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } impl TryFrom for OwnedFd { type Error = io::Error; #[inline] fn try_from(slf: Stream) -> io::Result { Ok(slf.0.into_std()?.into()) } } impl TryFrom for Stream { type Error = io::Error; #[inline] fn try_from(fd: OwnedFd) -> io::Result { Ok(UnixStream::from_std(SyncUnixStream::from(fd))?.into()) } } /// [`Stream`]'s receive half, internally implemented using [`Arc`](std::sync::Arc) by Tokio. pub struct RecvHalf(RecvHalfImpl); impl Sealed for RecvHalf {} impl traits::RecvHalf for RecvHalf { type Stream = Stream; } multimacro! { RecvHalf, pinproj_for_unpin(RecvHalfImpl), forward_debug("local_socket::RecvHalf"), forward_tokio_read, } impl AsyncRead for &RecvHalf { #[inline] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { ioloop(|| self.0.try_read_buf(buf), || self.0.as_ref().poll_read_ready(cx)) .map(|e| e.map(|_| ())) } } impl AsFd for RecvHalf { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_ref().as_fd() } } /// [`Stream`]'s send half, internally implemented using [`Arc`](std::sync::Arc) by Tokio. pub struct SendHalf(SendHalfImpl); impl Sealed for SendHalf {} impl traits::SendHalf for SendHalf { type Stream = Stream; } multimacro! { SendHalf, pinproj_for_unpin(SendHalfImpl), forward_rbv(SendHalfImpl, &), forward_debug("local_socket::SendHalf"), forward_tokio_write, } impl AsyncWrite for &SendHalf { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { ioloop(|| self.0.try_write(buf), || self.0.as_ref().poll_write_ready(cx)) } #[inline] fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { ioloop(|| self.0.try_write_vectored(bufs), || self.0.as_ref().poll_write_ready(cx)) } #[inline] fn is_write_vectored(&self) -> bool { self.0.is_write_vectored() } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } impl AsFd for SendHalf { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_ref().as_fd() } } interprocess-2.2.3/src/os/unix/uds_local_socket.rs000064400000000000000000000070271046102023000204340ustar 00000000000000//! Local sockets implemented using Unix domain sockets. mod listener; mod stream; pub use {listener::*, stream::*}; /// Async Local sockets for Tokio implemented using Unix domain sockets. #[cfg(feature = "tokio")] pub mod tokio { mod listener; mod stream; pub use {listener::*, stream::*}; } #[cfg(target_os = "android")] use std::os::android::net::SocketAddrExt; #[cfg(target_os = "linux")] use std::os::linux::net::SocketAddrExt; use { crate::{ local_socket::{Name, NameInner}, os::unix::unixprelude::*, }, std::{ borrow::Cow, ffi::{OsStr, OsString}, fs, io, mem, os::unix::net::SocketAddr, path::Path, }, }; #[derive(Clone, Debug, Default)] struct ReclaimGuard(Option>); impl ReclaimGuard { fn new(name: Name<'static>) -> Self { Self(if name.is_path() { Some(name) } else { None }) } #[cfg_attr(not(feature = "tokio"), allow(dead_code))] fn take(&mut self) -> Self { Self(self.0.take()) } fn forget(&mut self) { self.0 = None; } } impl Drop for ReclaimGuard { fn drop(&mut self) { if let Self(Some(Name(NameInner::UdSocketPath(path)))) = self { let _ = std::fs::remove_file(path); } } } #[allow(clippy::indexing_slicing)] fn name_to_addr(name: Name<'_>, create_dirs: bool) -> io::Result { match name.0 { NameInner::UdSocketPath(path) => SocketAddr::from_pathname(path), NameInner::UdSocketPseudoNs(name) => construct_and_prepare_pseudo_ns(name, create_dirs), #[cfg(any(target_os = "linux", target_os = "android"))] NameInner::UdSocketNs(name) => SocketAddr::from_abstract_name(name), } } const SUN_LEN: usize = { let dummy = unsafe { mem::zeroed::() }; dummy.sun_path.len() }; const NMCAP: usize = SUN_LEN - "/run/user/18446744073709551614/".len(); static TOOLONG: &str = "local socket name length exceeds capacity of sun_path of sockaddr_un"; /// Checks if `/run/user/` exists, returning that path if it does. fn get_run_user() -> io::Result> { let path = format!("/run/user/{}", unsafe { libc::getuid() }).into(); match fs::metadata(&path) { Ok(..) => Ok(Some(path)), Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), Err(e) => Err(e), } } static TMPDIR: &str = { #[cfg(target_os = "android")] { "/data/local/tmp" } #[cfg(not(target_os = "android"))] { "/tmp" } }; #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)] fn construct_and_prepare_pseudo_ns( name: Cow<'_, OsStr>, create_dirs: bool, ) -> io::Result { let nlen = name.len(); if nlen > NMCAP { return Err(io::Error::new(io::ErrorKind::InvalidInput, TOOLONG)); } let run_user = get_run_user()?; let pfx = run_user.map(Cow::Owned).unwrap_or(Cow::Borrowed(OsStr::new(TMPDIR))); let pl = pfx.len(); let mut path = [0; SUN_LEN]; path[..pl].copy_from_slice(pfx.as_bytes()); path[pl] = b'/'; let namestart = pl + 1; let fulllen = pl + 1 + nlen; path[namestart..fulllen].copy_from_slice(name.as_bytes()); const ESCCHAR: u8 = b'_'; for byte in path[namestart..fulllen].iter_mut() { if *byte == 0 { *byte = ESCCHAR; } } let opath = Path::new(OsStr::from_bytes(&path[..fulllen])); if create_dirs { let parent = opath.parent(); if let Some(p) = parent { fs::create_dir_all(p)?; } } SocketAddr::from_pathname(opath) } interprocess-2.2.3/src/os/unix/unnamed_pipe/tokio.rs000064400000000000000000000104661046102023000207110ustar 00000000000000use { super::UnnamedPipeExt, crate::{ os::unix::{unixprelude::*, FdOps}, unnamed_pipe::{ tokio::{Recver as PubRecver, Sender as PubSender}, Recver as SyncRecver, Sender as SyncSender, }, }, std::{ io, pin::Pin, task::{ready, Context, Poll}, }, tokio::io::{unix::AsyncFd, AsyncRead, AsyncWrite, Interest, ReadBuf, Ready}, }; type RecverImpl = AsyncFd; type SenderImpl = AsyncFd; pub(crate) fn pipe_impl() -> io::Result<(PubSender, PubRecver)> { let (tx, rx) = super::pipe(true)?; Ok((PubSender(Sender::try_from_nb(tx)?), PubRecver(Recver::try_from_nb(rx)?))) } #[derive(Debug)] pub(crate) struct Recver(RecverImpl); impl Recver { fn try_from_nb(rx: SyncRecver) -> io::Result { Ok(Self(RecverImpl::with_interest(FdOps(rx.into()), Interest::READABLE)?)) } } impl AsyncRead for Recver { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let slf = self.get_mut(); loop { let fd = slf.0.get_ref().as_raw_fd(); let mut readiness = ready!(slf.0.poll_read_ready_mut(cx))?; unsafe { // SAFETY(unfilled_mut): what the fuck does "de-initialize" mean // SAFETY(borrow_raw): we're getting it from an OwnedFd that we don't drop match FdOps::read_uninit(BorrowedFd::borrow_raw(fd), buf.unfilled_mut()) { Ok(bytes_read) => { buf.assume_init(bytes_read); buf.advance(bytes_read); break Poll::Ready(Ok(())); } Err(e) if e.kind() == io::ErrorKind::WouldBlock => { readiness.clear_ready_matching(Ready::READABLE); } Err(e) => break Poll::Ready(Err(e)), } } } } } impl TryFrom for Recver { type Error = io::Error; fn try_from(rx: SyncRecver) -> io::Result { rx.set_nonblocking(true)?; Self::try_from_nb(rx) } } impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(rx: Recver) -> io::Result { Ok(rx.0.into_inner().into()) } } impl TryFrom for Recver { type Error = io::Error; fn try_from(rx: OwnedFd) -> io::Result { SyncRecver::from(rx).try_into() } } forward_as_handle!(Recver); #[derive(Debug)] pub(crate) struct Sender(SenderImpl); impl Sender { fn try_from_nb(tx: SyncSender) -> io::Result { Ok(Self(SenderImpl::with_interest(FdOps(tx.into()), Interest::WRITABLE)?)) } } impl AsyncWrite for Sender { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { let slf = self.get_mut(); loop { let fd = slf.0.get_ref().as_raw_fd(); let mut readiness = ready!(slf.0.poll_write_ready_mut(cx))?; unsafe { // SAFETY(borrow_raw): we're getting it from an OwnedFd that we don't drop match FdOps::write(BorrowedFd::borrow_raw(fd), buf) { Ok(bytes_read) => break Poll::Ready(Ok(bytes_read)), Err(e) if e.kind() == io::ErrorKind::WouldBlock => { readiness.clear_ready_matching(Ready::WRITABLE); } Err(e) => break Poll::Ready(Err(e)), } } } } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } impl TryFrom for Sender { type Error = io::Error; fn try_from(tx: SyncSender) -> io::Result { tx.set_nonblocking(true)?; Self::try_from_nb(tx) } } impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(rx: Sender) -> io::Result { Ok(rx.0.into_inner().into()) } } impl TryFrom for Sender { type Error = io::Error; fn try_from(tx: OwnedFd) -> io::Result { SyncSender::from(tx).try_into() } } forward_as_handle!(Sender); interprocess-2.2.3/src/os/unix/unnamed_pipe.rs000064400000000000000000000074771046102023000175740ustar 00000000000000//! Unix-specific named pipe functionality. use { super::{c_wrappers, FdOps}, crate::{ os::unix::unixprelude::*, unnamed_pipe::{Recver as PubRecver, Sender as PubSender}, Sealed, }, std::{ fmt::{self, Debug, Formatter}, io, os::fd::OwnedFd, }, }; #[cfg(feature = "tokio")] pub(crate) mod tokio; /// Unix-specific extensions to synchronous named pipe senders and receivers. #[allow(private_bounds)] pub trait UnnamedPipeExt: AsFd + Sealed { /// Sets whether the nonblocking mode for the pipe half is enabled. By default, it is /// disabled. /// /// In nonblocking mode, attempts to receive from a [`Recver`](PubRecver) when there is no /// data available, much like attempts to send data via a [`Sender`](PubSender) when the send /// buffer has filled up because the receiving side hasn't received enough bytes in time, /// never block like they normally do. Instead, a [`WouldBlock`](io::ErrorKind::WouldBlock) /// error is immediately returned, allowing the thread to perform useful actions in the /// meantime. #[inline] fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { c_wrappers::set_nonblocking(self.as_fd(), nonblocking) } } #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] impl UnnamedPipeExt for PubRecver {} #[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))] impl UnnamedPipeExt for PubSender {} /// Like [platform-general `pipe()`](crate::unnamed_pipe::pipe), but allows pipe pairs to be /// immediately created in nonblocking mode on Linux, eliding a `fcntl()`. /// /// ## System calls /// - `pipe2` (Linux) /// - `pipe` (not Linux) /// - `fcntl` (not Linux, only if `nonblocking` is `true`) pub fn pipe(nonblocking: bool) -> io::Result<(PubSender, PubRecver)> { let (success, fds) = unsafe { let mut fds: [c_int; 2] = [0; 2]; let result; #[cfg(any(target_os = "linux", target_os = "android"))] { result = libc::pipe2(fds.as_mut_ptr(), if nonblocking { libc::O_NONBLOCK } else { 0 }); } #[cfg(not(any(target_os = "linux", target_os = "android")))] { result = libc::pipe(fds.as_mut_ptr()); } (result == 0, fds) }; if success { let (w, r) = unsafe { // SAFETY: we just created both of those file descriptors, which means that neither of // them can be in use elsewhere. let w = OwnedFd::from_raw_fd(fds[1]); let r = OwnedFd::from_raw_fd(fds[0]); (w, r) }; let w = PubSender(Sender(FdOps(w))); let r = PubRecver(Recver(FdOps(r))); #[cfg(not(any(target_os = "linux", target_os = "android")))] { if nonblocking { w.set_nonblocking(true)?; r.set_nonblocking(true)?; } } Ok((w, r)) } else { Err(io::Error::last_os_error()) } } // This is imported by a macro, hence the confusing name. #[inline] pub(crate) fn pipe_impl() -> io::Result<(PubSender, PubRecver)> { pipe(false) } pub(crate) struct Recver(FdOps); impl Sealed for Recver {} impl Debug for Recver { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Recver").field("fd", &self.0 .0.as_raw_fd()).finish() } } multimacro! { Recver, forward_rbv(FdOps, &), forward_sync_ref_read, forward_try_clone, forward_handle, derive_sync_mut_read, } pub(crate) struct Sender(FdOps); impl Sealed for Sender {} impl Debug for Sender { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Sender").field("fd", &self.0 .0.as_raw_fd()).finish() } } multimacro! { Sender, forward_rbv(FdOps, &), forward_sync_ref_write, forward_try_clone, forward_handle, derive_sync_mut_write, } interprocess-2.2.3/src/os/unix.rs000064400000000000000000000016171046102023000151160ustar 00000000000000//! Unix-specific functionality for various interprocess communication primitives, as well as //! Unix-specific ones. //! //! ## FIFO files //! This type of interprocess communication similar to unnamed pipes in that they are unidirectional //! byte channels which behave like files. The difference is that FIFO files are actual //! (pseudo)files on the filesystem and thus can be accessed by unrelated applications (one doesn't //! need to be spawned by another). //! //! FIFO files are available on all supported systems. pub(crate) mod imports; mod c_wrappers; mod fdops; // Exported into child modules specifically, not this file. use fdops::*; pub mod fifo_file; pub mod local_socket; pub mod uds_local_socket; pub mod unnamed_pipe; mod unixprelude { #[allow(unused_imports)] pub use libc::{c_char, c_int, c_short, gid_t, mode_t, pid_t, size_t, uid_t}; pub use std::os::unix::prelude::*; } interprocess-2.2.3/src/os/windows/c_wrappers.rs000064400000000000000000000021501046102023000177630ustar 00000000000000use { super::winprelude::*, crate::OrErrno, std::io, windows_sys::Win32::{ Foundation::{DuplicateHandle, DUPLICATE_SAME_ACCESS}, System::Threading::GetCurrentProcess, }, }; pub fn duplicate_handle(handle: BorrowedHandle<'_>) -> io::Result { let raw = duplicate_handle_inner(handle, None)?; unsafe { Ok(OwnedHandle::from_raw_handle(raw.to_std())) } } pub fn duplicate_handle_to_foreign( handle: BorrowedHandle<'_>, other_process: BorrowedHandle<'_>, ) -> io::Result { duplicate_handle_inner(handle, Some(other_process)) } fn duplicate_handle_inner( handle: BorrowedHandle<'_>, other_process: Option>, ) -> io::Result { let mut new_handle = INVALID_HANDLE_VALUE; unsafe { let proc = GetCurrentProcess(); DuplicateHandle( proc, handle.as_int_handle(), other_process.map(|h| h.as_int_handle()).unwrap_or(proc), &mut new_handle, 0, 0, DUPLICATE_SAME_ACCESS, ) } .true_val_or_errno(new_handle) } interprocess-2.2.3/src/os/windows/file_handle.rs000064400000000000000000000064331046102023000200600ustar 00000000000000use { super::{c_wrappers, downgrade_eof, winprelude::*}, crate::{AsMutPtr, OrErrno, SubUsizeExt, TryClone}, std::{io, mem::MaybeUninit, ptr}, windows_sys::Win32::{ Foundation::MAX_PATH, Storage::FileSystem::{FlushFileBuffers, GetFinalPathNameByHandleW, ReadFile, WriteFile}, }, }; /// Newtype wrapper which defines file I/O operations on a handle to a file. #[repr(transparent)] pub(crate) struct FileHandle(OwnedHandle); impl FileHandle { pub fn read(&self, buf: &mut [MaybeUninit]) -> io::Result { let len = u32::try_from(buf.len()).unwrap_or(u32::MAX); let mut bytes_read: u32 = 0; unsafe { ReadFile( self.as_int_handle(), buf.as_mut_ptr().cast(), len, bytes_read.as_mut_ptr(), ptr::null_mut(), ) } .true_val_or_errno(bytes_read.to_usize()) } pub fn write(&self, buf: &[u8]) -> io::Result { let len = u32::try_from(buf.len()).unwrap_or(u32::MAX); let mut bytes_written: u32 = 0; unsafe { WriteFile( self.as_int_handle(), buf.as_ptr().cast(), len, bytes_written.as_mut_ptr(), ptr::null_mut(), ) } .true_val_or_errno(bytes_written.to_usize()) } #[inline(always)] pub fn flush(&self) -> io::Result<()> { Self::flush_hndl(self.as_int_handle()) } #[inline] pub fn flush_hndl(handle: HANDLE) -> io::Result<()> { downgrade_eof(unsafe { FlushFileBuffers(handle) }.true_val_or_errno(())) } // The second arm is unreachable if cap > len. #[allow(dead_code, clippy::arithmetic_side_effects)] pub fn path(handle: BorrowedHandle<'_>) -> io::Result> { let mut buf = Vec::with_capacity((MAX_PATH + 1).to_usize()); match Self::_path(handle.as_int_handle(), &mut buf) { (_, Ok(true)) => Ok(buf), (len, Ok(false)) => { buf.reserve_exact(len - buf.capacity()); match Self::_path(handle.as_int_handle(), &mut buf) { (_, Ok(true)) => Ok(buf), (_, Ok(false)) => unreachable!(), (_, Err(e)) => Err(e), } } (_, Err(e)) => Err(e), } } #[allow(clippy::arithmetic_side_effects)] // Path lengths can never overflow usize. fn _path(handle: HANDLE, buf: &mut Vec) -> (usize, io::Result) { buf.clear(); let buflen = buf.capacity().try_into().unwrap_or(u32::MAX); let rslt = unsafe { GetFinalPathNameByHandleW(handle, buf.as_mut_ptr(), buflen, 0) }; let len = rslt.to_usize(); let e = if rslt >= buflen { Ok(false) } else if rslt == 0 { Err(io::Error::last_os_error()) } else { // +1 to include the nul terminator in the size. unsafe { buf.set_len(rslt.to_usize() + 1) } Ok(true) }; (len, e) } } impl TryClone for FileHandle { fn try_clone(&self) -> io::Result { c_wrappers::duplicate_handle(self.as_handle()).map(Self) } } multimacro! { FileHandle, forward_handle, forward_debug, derive_raw, } interprocess-2.2.3/src/os/windows/impersonation_guard.rs000064400000000000000000000012701046102023000216710ustar 00000000000000use { crate::{DebugExpectExt, OrErrno, ToBool}, std::fmt::{self, Debug}, windows_sys::Win32::Security::RevertToSelf, }; /// [Reverts impersonation][rd] when dropped. /// /// [rd]: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-reverttoself pub struct ImpersonationGuard(pub(crate) ()); impl Drop for ImpersonationGuard { fn drop(&mut self) { unsafe { RevertToSelf() } .to_bool() .true_val_or_errno(()) .debug_expect("failed to revert impersonation") } } impl Debug for ImpersonationGuard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("ImpersonationGuard") } } interprocess-2.2.3/src/os/windows/limbo/sync.rs000064400000000000000000000045321046102023000177020ustar 00000000000000use { crate::{ os::windows::{ limbo_pool::{LimboPool, MaybeReject}, winprelude::*, FileHandle, }, DebugExpectExt, OrErrno, LOCK_POISON, }, std::{ io, sync::{ mpsc::{sync_channel, SyncSender, TrySendError}, Mutex, OnceLock, }, thread, }, windows_sys::Win32::System::Pipes::DisconnectNamedPipe, }; pub(crate) struct Corpse { pub handle: FileHandle, pub is_server: bool, } impl Corpse { #[inline] pub fn disconnect(&self) -> io::Result<()> { unsafe { DisconnectNamedPipe(self.handle.as_int_handle()).true_val_or_errno(()) } } } impl Drop for Corpse { fn drop(&mut self) { if self.is_server { self.disconnect().debug_expect("named pipe server disconnect failed"); } } } type Limbo = LimboPool>; static LIMBO: OnceLock> = OnceLock::new(); fn limbo_keeper_name(idx: usize) -> String { match idx { usize::MAX => "limbo keeper".to_string(), x => format!("limbo keeper {}", x.wrapping_add(1)), } } pub(crate) fn send_off(c: Corpse) { fn bury(c: Corpse) { c.handle.flush().debug_expect("limbo flush failed"); } fn tryf(sender: &mut SyncSender, c: Corpse) -> MaybeReject { sender.try_send(c).map_err(|e| match e { TrySendError::Full(c) | TrySendError::Disconnected(c) => c, }) } fn createf(idx: usize, c: Corpse) -> SyncSender { let (tx, rx) = sync_channel::(1); thread::Builder::new() .name(limbo_keeper_name(idx)) .spawn(move || { while let Ok(h) = rx.recv() { bury(h); } }) .debug_expect("failed to spawn newcomer to limbo pool"); tx.try_send(c).debug_expect("newcomer to limbo pool already failed"); tx } fn fullf(idx: usize, c: Corpse) { thread::Builder::new() .name(limbo_keeper_name(idx)) .spawn(move || { bury(c); }) .debug_expect("failed to spawn newcomer to limbo pool"); } let mutex = LIMBO.get_or_init(Default::default); let mut limbo = mutex.lock().expect(LOCK_POISON); limbo.linear_try_or_create(c, tryf, createf, fullf); } interprocess-2.2.3/src/os/windows/limbo/tokio.rs000064400000000000000000000051071046102023000200520ustar 00000000000000//! Does not use the limbo pool. use { crate::{ os::windows::{winprelude::*, FileHandle}, DebugExpectExt, LOCK_POISON, }, std::sync::{Mutex, OnceLock}, tokio::{ fs::File, net::windows::named_pipe::{NamedPipeClient, NamedPipeServer}, runtime::{self, Handle as RuntimeHandle, Runtime}, sync::mpsc::{unbounded_channel, UnboundedSender}, task, }, }; pub(crate) enum Corpse { NpServer(NamedPipeServer), NpClient(NamedPipeClient), Unnamed(File), } impl Drop for Corpse { fn drop(&mut self) { if let Self::NpServer(server) = self { server.disconnect().debug_expect("named pipe server disconnect failed"); } } } impl AsRawHandle for Corpse { fn as_raw_handle(&self) -> RawHandle { match self { Corpse::NpServer(o) => o.as_raw_handle(), Corpse::NpClient(o) => o.as_raw_handle(), Corpse::Unnamed(o) => o.as_raw_handle(), } } } type Limbo = UnboundedSender; static LIMBO: OnceLock> = OnceLock::new(); static LIMBO_RT: OnceLock = OnceLock::new(); fn static_runtime_handle() -> &'static RuntimeHandle { LIMBO_RT .get_or_init(|| { runtime::Builder::new_multi_thread() .worker_threads(1) .enable_io() .thread_name("Tokio limbo dispatcher") .thread_stack_size(1024 * 1024) .build() .expect( "\ failed to build Tokio limbo helper (only necessary if the first pipe to be dropped happens to go \ out of scope outside of another Tokio runtime)", ) }) .handle() } fn bury(c: Corpse) { task::spawn_blocking(move || { let handle = c.as_int_handle(); FileHandle::flush_hndl(handle).debug_expect("limbo flush failed"); }); } fn create_limbo() -> Limbo { let (tx, mut rx) = unbounded_channel(); let mut _guard = None; if RuntimeHandle::try_current().is_err() { _guard = Some(static_runtime_handle().enter()); } task::spawn(async move { while let Some(c) = rx.recv().await { bury(c); } }); tx } pub(crate) fn send_off(c: Corpse) { let mutex = LIMBO.get_or_init(|| Mutex::new(create_limbo())); let mut limbo = mutex.lock().expect(LOCK_POISON); if let Err(c) = limbo.send(c) { *limbo = create_limbo(); limbo .send(c.0) .ok() .debug_expect("fresh Tokio limbo helper died immediately after being created"); } } interprocess-2.2.3/src/os/windows/limbo_pool.rs000064400000000000000000000062221046102023000177550ustar 00000000000000//! The limbo which dropped streams are sent to if send buffer preservation is enabled. //! //! Because dropping a named pipe file handle, be it a client or a server, discards its send buffer, //! the portability-conscious local socket interface requires this additional feature to allow for //! the common use case of dropping right after sending a graceful shutdown message. use crate::SubUsizeExt; const LIMBO_SLOTS: u8 = 16; /// Common result type for operations that complete with no output but may reject their input, /// requiring some form of retry. pub(crate) type MaybeReject = Result<(), T>; #[allow(clippy::as_conversions)] pub(crate) struct LimboPool { senders: [Option; LIMBO_SLOTS as _], count: u8, count_including_overflow: usize, } impl LimboPool { fn incr_count_including_overflow(&mut self) { self.count_including_overflow = self.count_including_overflow.saturating_add(1); } pub fn add_sender(&mut self, s: S) -> MaybeReject { self.incr_count_including_overflow(); #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] if self.count < LIMBO_SLOTS { self.senders[self.count.to_usize()] = Some(s); self.count += 1; Ok(()) } else { Err(s) } } /// Tries shoving the given accumulant into the given maybe-rejecting function with every /// available sender. #[allow(clippy::unwrap_used, clippy::unwrap_in_result)] // Used to work with ownership. pub fn linear_try( &mut self, acc: T, mut f: impl FnMut(&mut S, T) -> MaybeReject, ) -> MaybeReject { let mut acc = Some(acc); #[allow(clippy::indexing_slicing)] for sender in &mut self.senders[0..(usize::from(self.count))] { let regain = match f(sender.as_mut().unwrap(), acc.take().unwrap()) { Ok(()) => return Ok(()), Err(r) => r, }; acc = Some(regain); } Err(acc.unwrap()) } /// Performs `linear_try` with `acc` and `tryf`, and if that fails, calls `createf` and consumes /// its output with `add_sender` if the pool has vacant space, resorting to `fullf` otherwise. pub fn linear_try_or_create( &mut self, acc: T, tryf: impl FnMut(&mut S, T) -> MaybeReject, // First argument is the index of the new sender. createf: impl FnOnce(usize, T) -> S, // Same here. fullf: impl FnOnce(usize, T), ) { let acc = match self.linear_try(acc, tryf) { Ok(()) => return, Err(regain) => regain, }; if self.count < LIMBO_SLOTS { // Cannot error. let _ = self.add_sender(createf(self.count.into(), acc)); } else { fullf(self.count_including_overflow, acc); self.incr_count_including_overflow(); } } } impl Default for LimboPool { fn default() -> Self { Self { // hmm today i will initialize an array senders: Default::default(), count: 0, count_including_overflow: 0, } } } interprocess-2.2.3/src/os/windows/local_socket/dispatch_sync.rs000064400000000000000000000006531046102023000231210ustar 00000000000000use { super::super::named_pipe::local_socket as np_impl, crate::local_socket::{prelude::*, Listener, ListenerOptions, Name, Stream}, std::io, }; #[inline] pub fn from_options(options: ListenerOptions<'_>) -> io::Result { options.create_sync_as::().map(Listener::from) } pub fn connect(name: Name<'_>) -> io::Result { np_impl::Stream::connect(name).map(Stream::from) } interprocess-2.2.3/src/os/windows/local_socket/dispatch_tokio.rs000064400000000000000000000007371046102023000232750ustar 00000000000000use { super::super::named_pipe::local_socket::tokio as np_impl, crate::local_socket::{ tokio::{prelude::*, Listener, Stream}, ListenerOptions, Name, }, std::io, }; #[inline] pub fn from_options(options: ListenerOptions<'_>) -> io::Result { options.create_tokio_as::().map(Listener::from) } pub async fn connect(name: Name<'_>) -> io::Result { np_impl::Stream::connect(name).await.map(Stream::from) } interprocess-2.2.3/src/os/windows/local_socket/name_type.rs000064400000000000000000000057671046102023000222620ustar 00000000000000use { crate::{ local_socket::{Name, NameInner, NameType, PathNameType}, os::windows::{convert_and_encode_path, convert_osstr}, }, std::{borrow::Cow, ffi::OsStr, io}, }; tag_enum!( /// [Mapping](NameType) that produces /// [named pipe local socket](crate::os::windows::named_pipe::local_socket) names. /// /// Named pipe paths of the form `\\HOSTNAME\pipe\PIPENAME` are passed through verbatim. Other paths /// yield an error, as they do not point to NPFS. /// /// Namespaced strings have `\\.\pipe\` prepended to them – using /// [`ToNsName`](crate::local_socket::ToNsName) conversions implies the hostname `.`, which is the /// local system. NamedPipe); impl NameType for NamedPipe { fn is_supported() -> bool { true } } impl PathNameType for NamedPipe { fn map(path: Cow<'_, OsStr>) -> io::Result> { if !is_pipefs(&path) { return Err(io::Error::new(io::ErrorKind::Unsupported, "not a named pipe path")); } Ok(Name(NameInner::NamedPipe(Cow::Owned(convert_osstr(&path)?)))) } } pub(crate) fn map_generic_path_osstr(path: Cow<'_, OsStr>) -> io::Result> { // TODO(2.3.0) do something meaningful for non-NPFS paths instead of rejecting them // TODO(2.3.0) normskip (`\\?\`) paths NamedPipe::map(path) } pub(crate) fn map_generic_namespaced_osstr(name: Cow<'_, OsStr>) -> io::Result> { // The prepending currently happens at a later point. Ok(Name(NameInner::NamedPipe(Cow::Owned(convert_and_encode_path(&name, None)?)))) } #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)] // minlen check fn is_pipefs(slf: &OsStr) -> bool { const PFX1: &[u8] = br"\\"; const PFX2: &[u8] = br"\pipe\"; const LEN_PFX1: usize = PFX1.len(); const LEN_PFX2: usize = PFX2.len(); const MINLEN: usize = LEN_PFX1 + LEN_PFX2 + 1; let b = slf.as_encoded_bytes(); if (b.len() < MINLEN) || (&b[..LEN_PFX1] != PFX1) { return false; } let Some(slashidx) = findslash(&b[LEN_PFX1..]) else { return false; }; let hostbase = LEN_PFX1 + slashidx; &b[hostbase..(hostbase + LEN_PFX2)] == PFX2 } #[inline] fn findslash(slice: &[u8]) -> Option { for (i, e) in slice.iter().copied().enumerate() { if e == b'\\' { return Some(i); } } None } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::*; static NP: &str = r"Именованная труба\yeah"; #[track_caller] fn assert_pipefs(s: impl AsRef) { assert!(is_pipefs(s.as_ref())); } #[track_caller] fn assert_not_pipefs(s: impl AsRef) { assert!(!is_pipefs(s.as_ref())); } #[test] fn local() { assert_pipefs(format!(r"\\.\pipe\{NP}")); } #[test] fn remote() { assert_pipefs(format!(r"\\CHARA\pipe\{NP}")); } #[test] fn bad() { assert_not_pipefs("iwiwiwiwiwiwiwiwiwiwiwiwi"); } #[test] fn can_not_do_unix_things() { assert_not_pipefs(r"C:\Users\GetSilly\neovide.sock"); } } interprocess-2.2.3/src/os/windows/local_socket.rs000064400000000000000000000015061046102023000202640ustar 00000000000000//! Windows-specific local socket functionality. pub(crate) mod dispatch_sync; #[cfg(feature = "tokio")] pub(crate) mod dispatch_tokio; pub(crate) mod name_type; pub use name_type::*; use { super::security_descriptor::SecurityDescriptor, crate::{local_socket::ListenerOptions, Sealed}, }; /// Windows-specific [listener options](ListenerOptions). #[allow(private_bounds)] pub trait ListenerOptionsExt: Sized + Sealed { /// Sets the security descriptor that will control access to the underlying named pipe. #[must_use = builder_must_use!()] fn security_descriptor(self, sd: SecurityDescriptor) -> Self; } impl ListenerOptionsExt for ListenerOptions<'_> { #[inline(always)] fn security_descriptor(mut self, sd: SecurityDescriptor) -> Self { self.security_descriptor = Some(sd); self } } interprocess-2.2.3/src/os/windows/mailslot.rs000064400000000000000000000003571046102023000174510ustar 00000000000000//! Windows-specific IPC primitive designed for short multiple-producer-single-consumer message //! communication with UDP reliability guarantees, which works both on the local system and across //! the network. // TODO(2.4.0) this thing interprocess-2.2.3/src/os/windows/misc.rs000064400000000000000000000023601046102023000165540ustar 00000000000000pub(super) mod winprelude { pub(crate) use { super::{AsRawHandleExt as _, HANDLEExt as _}, std::os::windows::prelude::*, windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}, }; } use { crate::RawOsErrorExt as _, std::io::{self, ErrorKind::BrokenPipe}, winprelude::*, }; pub(crate) trait AsRawHandleExt: AsRawHandle { #[inline(always)] #[allow(clippy::as_conversions)] fn as_int_handle(&self) -> HANDLE { self.as_raw_handle() as HANDLE } } impl AsRawHandleExt for T {} pub(crate) trait HANDLEExt { fn to_std(self) -> RawHandle; } impl HANDLEExt for HANDLE { #[inline(always)] #[allow(clippy::as_conversions)] fn to_std(self) -> RawHandle { self as RawHandle } } pub(super) fn decode_eof(r: io::Result) -> io::Result { use windows_sys::Win32::Foundation::ERROR_PIPE_NOT_CONNECTED; match r { Err(e) if e.raw_os_error().eeq(ERROR_PIPE_NOT_CONNECTED) => { Err(io::Error::from(BrokenPipe)) } els => els, } } pub(super) fn downgrade_eof(r: io::Result) -> io::Result { match decode_eof(r) { Err(e) if e.kind() == BrokenPipe => Ok(T::default()), els => els, } } interprocess-2.2.3/src/os/windows/named_pipe/c_wrappers.rs000064400000000000000000000136111046102023000220700ustar 00000000000000use { crate::{ os::windows::{ decode_eof, named_pipe::{PipeMode, WaitTimeout}, winprelude::*, FileHandle, }, AsMutPtr, HandleOrErrno, OrErrno, RawOsErrorExt, SubUsizeExt, }, std::{io, mem::MaybeUninit, os::windows::prelude::*, ptr}, widestring::U16CStr, windows_sys::Win32::{ Foundation::{ERROR_PIPE_BUSY, GENERIC_READ, GENERIC_WRITE}, Storage::FileSystem::{ CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING, }, System::Pipes::{ GetNamedPipeHandleStateW, GetNamedPipeInfo, PeekNamedPipe, SetNamedPipeHandleState, WaitNamedPipeW, PIPE_NOWAIT, }, }, }; fn optional_out_ptr(outref: Option<&mut T>) -> *mut T { match outref { Some(outref) => outref.as_mut_ptr(), None => ptr::null_mut(), } } /// Helper for several functions that take a handle and a u32 out-pointer. pub(crate) unsafe fn hget( handle: BorrowedHandle<'_>, f: unsafe extern "system" fn(HANDLE, *mut u32) -> i32, ) -> io::Result { let mut x: u32 = 0; unsafe { f(handle.as_int_handle(), x.as_mut_ptr()) }.true_val_or_errno(x) } pub(crate) fn get_np_info( handle: BorrowedHandle<'_>, flags: Option<&mut u32>, in_buf: Option<&mut u32>, out_buf: Option<&mut u32>, max_instances: Option<&mut u32>, ) -> io::Result<()> { unsafe { GetNamedPipeInfo( handle.as_int_handle(), optional_out_ptr(flags), optional_out_ptr(in_buf), optional_out_ptr(out_buf), optional_out_ptr(max_instances), ) } .true_val_or_errno(()) } pub(crate) fn get_np_handle_state( handle: BorrowedHandle<'_>, mode: Option<&mut u32>, cur_instances: Option<&mut u32>, max_collection_count: Option<&mut u32>, collect_data_timeout: Option<&mut u32>, mut username: Option<&mut [MaybeUninit]>, ) -> io::Result<()> { // TODO(2.3.0) expose the rest of the owl as public API unsafe { GetNamedPipeHandleStateW( handle.as_int_handle(), optional_out_ptr(mode), optional_out_ptr(cur_instances), optional_out_ptr(max_collection_count), optional_out_ptr(collect_data_timeout), username.as_deref_mut().map(|s| s.as_mut_ptr().cast()).unwrap_or(ptr::null_mut()), username.map(|s| u32::try_from(s.len()).unwrap_or(u32::MAX)).unwrap_or(0), ) } .true_val_or_errno(()) } pub(crate) fn set_np_handle_state( handle: BorrowedHandle<'_>, mode: Option, max_collection_count: Option, collect_data_timeout: Option, ) -> io::Result<()> { let (mut mode_, has_mode) = (mode.unwrap_or_default(), mode.is_some()); let (mut mcc, has_mcc) = (max_collection_count.unwrap_or_default(), max_collection_count.is_some()); let (mut cdt, has_cdt) = (collect_data_timeout.unwrap_or_default(), collect_data_timeout.is_some()); let null = ptr::null_mut(); unsafe { SetNamedPipeHandleState( handle.as_int_handle(), if has_mode { mode_.as_mut_ptr() } else { null }, if has_mcc { mcc.as_mut_ptr() } else { null }, if has_cdt { cdt.as_mut_ptr() } else { null }, ) } .true_val_or_errno(()) } #[inline] pub(crate) fn get_flags(handle: BorrowedHandle<'_>) -> io::Result { let mut flags: u32 = 0; get_np_info(handle, Some(&mut flags), None, None, None)?; Ok(flags) } #[allow(dead_code)] pub(crate) fn get_np_handle_mode(handle: BorrowedHandle<'_>) -> io::Result { let mut mode = 0_u32; get_np_handle_state(handle, Some(&mut mode), None, None, None, None)?; Ok(mode) } pub(crate) fn peek_msg_len(handle: BorrowedHandle<'_>) -> io::Result { let mut msglen: u32 = 0; let rslt = unsafe { PeekNamedPipe( handle.as_int_handle(), ptr::null_mut(), 0, ptr::null_mut(), ptr::null_mut(), msglen.as_mut_ptr(), ) } .true_val_or_errno(msglen.to_usize()); decode_eof(rslt) } fn modes_to_access_flags(recv: Option, send: Option) -> u32 { let mut access_flags = 0; if recv.is_some() { access_flags |= GENERIC_READ; } if recv == Some(PipeMode::Messages) { access_flags |= FILE_WRITE_ATTRIBUTES; } if send.is_some() { access_flags |= GENERIC_WRITE; } access_flags } pub(crate) fn connect_without_waiting( path: &U16CStr, recv: Option, send: Option, overlapped: bool, ) -> io::Result { let access_flags = modes_to_access_flags(recv, send); let flags = if overlapped { FILE_FLAG_OVERLAPPED } else { 0 }; match unsafe { CreateFileW( path.as_ptr(), access_flags, FILE_SHARE_READ | FILE_SHARE_WRITE, ptr::null_mut(), OPEN_EXISTING, flags, 0, ) .handle_or_errno() .map(|h| // SAFETY: we just created this handle FileHandle::from(OwnedHandle::from_raw_handle(h.to_std()))) } { Err(e) if e.raw_os_error().eeq(ERROR_PIPE_BUSY) => Err(io::ErrorKind::WouldBlock.into()), els => els, } } pub(crate) fn set_nonblocking_given_readmode( handle: BorrowedHandle<'_>, nonblocking: bool, recv: Option, ) -> io::Result<()> { // PIPE_READMODE_BYTE is the default let mut mode = recv.unwrap_or(PipeMode::Bytes).to_readmode(); if nonblocking { mode |= PIPE_NOWAIT; } set_np_handle_state(handle, Some(mode), None, None) } pub(crate) fn block_for_server(path: &U16CStr, timeout: WaitTimeout) -> io::Result<()> { unsafe { WaitNamedPipeW(path.as_ptr().cast_mut(), timeout.to_raw()) }.true_val_or_errno(()) } interprocess-2.2.3/src/os/windows/named_pipe/enums.rs000064400000000000000000000202521046102023000210510ustar 00000000000000use { super::PipeModeTag, std::mem, windows_sys::Win32::{ Storage::FileSystem::{PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, PIPE_ACCESS_OUTBOUND}, System::Pipes::{ PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE, PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE, }, }, }; /// The direction of a named pipe connection, designating who can receive data and who can send it. /// This describes the direction of the data flow unambiguously, so that the meaning of the values /// is the same for the client and server – [`ClientToServer`](PipeDirection::ClientToServer) always /// means client → server, for example. #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum PipeDirection { /// Represents server ← client data flow: clients send data, the server receives it. ClientToServer = PIPE_ACCESS_INBOUND, /// Represents server → client data flow: the server sends data, clients receive it. ServerToClient = PIPE_ACCESS_OUTBOUND, /// Represents server ⇄ client data flow: the server can send data which is then received by the /// client, while the client sends data which is received by the server. Duplex = PIPE_ACCESS_DUPLEX, } impl PipeDirection { /// Returns the role which the pipe client will have in this direction setting. /// /// # Usage /// ``` /// # use interprocess::os::windows::named_pipe::{PipeDirection, PipeStreamRole}; /// assert_eq!( /// PipeDirection::ClientToServer.client_role(), /// PipeStreamRole::Sender, /// ); /// assert_eq!( /// PipeDirection::ServerToClient.client_role(), /// PipeStreamRole::Recver, /// ); /// assert_eq!( /// PipeDirection::Duplex.client_role(), /// PipeStreamRole::RecverAndSender, /// ); /// ``` pub const fn client_role(self) -> PipeStreamRole { match self { Self::ClientToServer => PipeStreamRole::Sender, Self::ServerToClient => PipeStreamRole::Recver, Self::Duplex => PipeStreamRole::RecverAndSender, } } /// Returns the role which the pipe server will have in this direction setting. /// /// # Usage /// ``` /// # use interprocess::os::windows::named_pipe::{PipeDirection, PipeStreamRole}; /// assert_eq!( /// PipeDirection::ClientToServer.server_role(), /// PipeStreamRole::Recver, /// ); /// assert_eq!( /// PipeDirection::ServerToClient.server_role(), /// PipeStreamRole::Sender, /// ); /// assert_eq!( /// PipeDirection::Duplex.server_role(), /// PipeStreamRole::RecverAndSender, /// ); /// ``` pub const fn server_role(self) -> PipeStreamRole { match self { Self::ClientToServer => PipeStreamRole::Recver, Self::ServerToClient => PipeStreamRole::Sender, Self::Duplex => PipeStreamRole::RecverAndSender, } } } impl TryFrom for PipeDirection { type Error = (); /// Converts a Windows constant to a `PipeDirection` if it's in range. /// /// # Errors /// Returns `Err` if the value is not a valid pipe direction constant. fn try_from(op: u32) -> Result { Ok(match op { PIPE_ACCESS_INBOUND => Self::ClientToServer, PIPE_ACCESS_OUTBOUND => Self::ServerToClient, PIPE_ACCESS_DUPLEX => Self::Duplex, _ => return Err(()), }) } } impl From for u32 { fn from(op: PipeDirection) -> Self { unsafe { mem::transmute(op) } } } /// Describes the role of a named pipe stream. In constrast to [`PipeDirection`], the meaning of /// values here is relative – for example, [`Recver`](PipeStreamRole::Recver) means /// [`ServerToClient`](PipeDirection::ServerToClient) if you're creating a server and /// [`ClientToServer`](PipeDirection::ClientToServer) if you're creating a client. /// /// This enumeration is not layout-compatible with the `PIPE_ACCESS_*` constants, in contrast /// to [`PipeDirection`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum PipeStreamRole { /// The stream only receives data. Recver, /// The stream only sends data. Sender, /// The stream both receives and sends data. RecverAndSender, } impl PipeStreamRole { /// Returns the data flow direction of the data stream, assuming that the value describes the /// role of the server. /// /// # Usage /// ``` /// # use interprocess::os::windows::named_pipe::{PipeDirection, PipeStreamRole}; /// assert_eq!( /// PipeStreamRole::Recver.direction_as_server(), /// PipeDirection::ClientToServer, /// ); /// assert_eq!( /// PipeStreamRole::Sender.direction_as_server(), /// PipeDirection::ServerToClient, /// ); /// assert_eq!( /// PipeStreamRole::RecverAndSender.direction_as_server(), /// PipeDirection::Duplex, /// ); /// ``` pub const fn direction_as_server(self) -> PipeDirection { match self { Self::Recver => PipeDirection::ClientToServer, Self::Sender => PipeDirection::ServerToClient, Self::RecverAndSender => PipeDirection::Duplex, } } /// Returns the data flow direction of the data stream, assuming that the value describes the /// role of the client. /// /// # Usage /// ``` /// # use interprocess::os::windows::named_pipe::{PipeDirection, PipeStreamRole}; /// assert_eq!( /// PipeStreamRole::Recver.direction_as_client(), /// PipeDirection::ServerToClient, /// ); /// assert_eq!( /// PipeStreamRole::Sender.direction_as_client(), /// PipeDirection::ClientToServer, /// ); /// assert_eq!( /// PipeStreamRole::RecverAndSender.direction_as_client(), /// PipeDirection::Duplex, /// ); /// ``` pub const fn direction_as_client(self) -> PipeDirection { match self { Self::Recver => PipeDirection::ServerToClient, Self::Sender => PipeDirection::ClientToServer, Self::RecverAndSender => PipeDirection::Duplex, } } pub(crate) const fn get_for_rm_sm() -> Self { match (Rm::MODE, Sm::MODE) { (Some(..), Some(..)) => Self::RecverAndSender, (Some(..), None) => Self::Recver, (None, Some(..)) => Self::Sender, (None, None) => unimplemented!(), } } } /// Specifies the mode for a pipe stream. #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum PipeMode { /// Designates that the pipe stream works in byte stream mode, erasing the boundaries of /// separate messages. Bytes = PIPE_TYPE_BYTE, /// Designates that the pipe stream works in message stream mode, preserving the boundaries of /// separate messages, though still allowing them to be received via a byte stream named pipe /// object. Messages = PIPE_TYPE_MESSAGE, } impl PipeMode { /// Converts the value into a raw `u32`-typed constant, either `PIPE_TYPE_BYTE` or /// `PIPE_TYPE_MESSAGE` depending on the value. #[inline] #[allow(clippy::as_conversions)] pub const fn to_pipe_type(self) -> u32 { self as _ } /// Converts the value into a raw `u32`-typed constant, either `PIPE_READMODE_BYTE` or /// `PIPE_READMODE_MESSAGE` depending on the value. pub const fn to_readmode(self) -> u32 { match self { Self::Bytes => PIPE_READMODE_BYTE, Self::Messages => PIPE_READMODE_MESSAGE, } } } impl TryFrom for PipeMode { type Error = (); /// Converts a Windows constant to a `PipeMode` if it's in range. Both `PIPE_TYPE_*` and /// `PIPE_READMODE_*` are supported. /// /// # Errors /// Returns `Err` if the value is not a valid pipe stream mode constant. fn try_from(op: u32) -> Result { // It's nicer to only match than to check and transmute #[allow(unreachable_patterns)] // PIPE_READMODE_BYTE and PIPE_TYPE_BYTE are equal match op { PIPE_TYPE_BYTE | PIPE_READMODE_BYTE => Ok(Self::Bytes), PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE => Ok(Self::Messages), _ => Err(()), } } } interprocess-2.2.3/src/os/windows/named_pipe/listener/collect_options.rs000064400000000000000000000067111046102023000247530ustar 00000000000000use { super::*, crate::{ os::windows::{ named_pipe::{pipe_mode, PipeMode, WaitTimeout}, path_conversion::*, SecurityDescriptor, }, TryClone, }, std::{borrow::Cow, num::NonZeroU8, os::windows::prelude::*}, widestring::{u16cstr, U16CStr, U16CString}, windows_sys::Win32::System::Pipes::{PIPE_NOWAIT, PIPE_TYPE_MESSAGE}, }; impl PipeListenerOptions<'_> { // TODO(2.3.0) detailed error information like with streams #[allow(clippy::unwrap_used, clippy::unwrap_in_result)] pub fn collect_from_handle(handle: BorrowedHandle<'_>) -> io::Result { let mut rslt = Self::default(); let [mut flags, mut max_instances] = [0_u32; 2]; c_wrappers::get_np_info( handle, Some(&mut flags), Some(&mut rslt.input_buffer_size_hint), Some(&mut rslt.output_buffer_size_hint), Some(&mut max_instances), )?; rslt.mode = PipeMode::try_from(flags & PIPE_TYPE_MESSAGE).unwrap(); if max_instances == 255 { // 255 is sentinel for unlimited instances. We re-sentinel via NonZeroU8. max_instances = 0; } rslt.instance_limit = NonZeroU8::new(u8::try_from(max_instances).unwrap_or(0)); // TODO(2.3.0) error out if PIPE_SERVER_END in flags, check for REJECT_REMOTE_CLIENTS (its presence // in the flags is not documented) let mode = c_wrappers::get_np_handle_mode(handle)?; rslt.nonblocking = mode & PIPE_NOWAIT != 0; let path = FileHandle::path(handle)?; rslt.path = Cow::Owned(unsafe { // SAFETY: Windows will never write interior nuls there. U16CString::from_vec_unchecked(path) }); // TODO(2.3.0) security descriptor, inheritable Ok(rslt) } } #[cfg(test)] mod tests { use super::*; // TODO(2.3.0) fn check_collect(original: PipeListenerOptions<'_>) { let listener = original.create::(); let collected = PipeListenerOptions::collect_from_handle(todo!("as_handle")) .expect("failed to collect options"); assert_eq!(collected.path, original.path); assert_eq!(collected.mode, original.mode); assert_eq!(collected.nonblocking, original.nonblocking); assert_eq!(collected.instance_limit, original.instance_limit); assert_eq!(collected.accept_remote, original.accept_remote); assert_eq!(collected.input_buffer_size_hint, original.input_buffer_size_hint); assert_eq!(collected.output_buffer_size_hint, original.output_buffer_size_hint); // FIXME(2.3.0) can't PartialEq security descriptors assert!(collected.security_descriptor.is_some()); assert_eq!(collected.inheritable, original.inheritable); } #[test] fn collect_duplex_byte() { let opts = PipeListenerOptions { path: todo!(), // (2.3.0) mode: PipeMode::Bytes, nonblocking: true, instance_limit: NonZeroU8::new(250), write_through: true, accept_remote: false, input_buffer_size_hint: 420, output_buffer_size_hint: 228, wait_timeout: WaitTimeout::from_raw(1987), security_descriptor: todo!(), // (2.3.0) inheritable: true, ..Default::default() }; check_collect::(opts); } } interprocess-2.2.3/src/os/windows/named_pipe/listener/create_instance.rs000064400000000000000000000072421046102023000247020ustar 00000000000000use { super::*, crate::{ os::windows::{ named_pipe::PipeMode, security_descriptor::create_security_attributes, winprelude::*, }, AsPtr, HandleOrErrno, }, std::num::NonZeroU8, windows_sys::Win32::{ Storage::FileSystem::{ FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_FLAG_WRITE_THROUGH, }, System::Pipes::{CreateNamedPipeW, PIPE_NOWAIT, PIPE_REJECT_REMOTE_CLIENTS}, }, }; impl PipeListenerOptions<'_> { pub(super) fn _create( &self, role: PipeStreamRole, recv_mode: Option, ) -> io::Result<(PipeListenerOptions<'static>, FileHandle)> { let owned_config = self.to_owned()?; let instance = self .create_instance(true, self.nonblocking, false, role, recv_mode) .map(FileHandle::from)?; Ok((owned_config, instance)) } /// Creates an instance of a pipe for a listener with the specified stream type and with the /// first-instance flag set to the specified value. pub(crate) fn create_instance( &self, first: bool, nonblocking: bool, overlapped: bool, role: PipeStreamRole, recv_mode: Option, ) -> io::Result { if recv_mode == Some(PipeMode::Messages) && self.mode == PipeMode::Bytes { return Err(io::Error::new( io::ErrorKind::InvalidInput, "\ cannot create pipe server that has byte type but receives messages – have you forgotten to set the \ `mode` field in `PipeListenerOptions`?", )); } let open_mode = self.open_mode(first, role, overlapped); let pipe_mode = self.pipe_mode(recv_mode, nonblocking); let sa = create_security_attributes( self.security_descriptor.as_ref().map(|sd| sd.borrow()), self.inheritable, ); let max_instances = match self.instance_limit.map(NonZeroU8::get) { Some(255) => return Err(io::Error::new( io::ErrorKind::InvalidInput, "cannot set 255 as the named pipe instance limit due to 255 being a reserved value", )), Some(x) => x.into(), None => 255, }; unsafe { CreateNamedPipeW( (*self.path).as_ptr(), open_mode, pipe_mode, max_instances, self.output_buffer_size_hint, self.input_buffer_size_hint, self.wait_timeout.to_raw(), sa.as_ptr().cast_mut().cast(), ) .handle_or_errno() .map(|h| // SAFETY: we just made it and received ownership OwnedHandle::from_raw_handle(h.to_std())) } } fn open_mode(&self, first: bool, role: PipeStreamRole, overlapped: bool) -> u32 { let mut open_mode = 0_u32; open_mode |= u32::from(role.direction_as_server()); if first { open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE; } if self.write_through { open_mode |= FILE_FLAG_WRITE_THROUGH; } if overlapped { open_mode |= FILE_FLAG_OVERLAPPED; } open_mode } fn pipe_mode(&self, recv_mode: Option, nonblocking: bool) -> u32 { let mut pipe_mode = 0_u32; pipe_mode |= self.mode.to_pipe_type(); pipe_mode |= recv_mode.map_or(0, PipeMode::to_readmode); if nonblocking { pipe_mode |= PIPE_NOWAIT; } if !self.accept_remote { pipe_mode |= PIPE_REJECT_REMOTE_CLIENTS; } pipe_mode } } interprocess-2.2.3/src/os/windows/named_pipe/listener/incoming.rs000064400000000000000000000017631046102023000233600ustar 00000000000000use { super::{PipeListener, PipeModeTag, PipeStream}, std::{io, iter::FusedIterator}, }; /// An iterator that infinitely [`.accept`](PipeListener::accept)s connections on a /// [`PipeListener`]. /// /// This iterator is created by the [`.incoming()`](PipeListener::incoming) method on /// [`PipeListener`]. See its documentation for more. pub struct Incoming<'a, Rm: PipeModeTag, Sm: PipeModeTag>(pub(super) &'a PipeListener); impl<'a, Rm: PipeModeTag, Sm: PipeModeTag> Iterator for Incoming<'a, Rm, Sm> { type Item = io::Result>; #[inline] fn next(&mut self) -> Option { Some(self.0.accept()) } } impl<'a, Rm: PipeModeTag, Sm: PipeModeTag> IntoIterator for &'a PipeListener { type IntoIter = Incoming<'a, Rm, Sm>; type Item = as Iterator>::Item; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.incoming() } } impl FusedIterator for Incoming<'_, Rm, Sm> {} interprocess-2.2.3/src/os/windows/named_pipe/listener/options.rs000064400000000000000000000217041046102023000232450ustar 00000000000000use { super::*, crate::{ os::windows::{ named_pipe::{pipe_mode, PipeMode, WaitTimeout}, path_conversion::*, security_descriptor::SecurityDescriptor, }, TryClone, }, std::{borrow::Cow, num::NonZeroU8}, widestring::{u16cstr, U16CStr}, }; /// Allows for thorough customization of [`PipeListener`]s during creation. // TODO(2.3.0) allow partial modification for later instances #[derive(Debug)] #[non_exhaustive] pub struct PipeListenerOptions<'path> { /// Specifies the name for the named pipe. The necessary `\\.\pipe\` prefix is *not* /// automatically prepended! pub path: Cow<'path, U16CStr>, /// Specifies how data is written into the data stream. This is required in all cases, /// regardless of whether the pipe is inbound, outbound or duplex, since this affects all data /// being written into the pipe, not just the data written by the server. pub mode: PipeMode, /// Specifies whether nonblocking mode will be enabled for all stream instances upon creation. /// By default, it is disabled. /// /// There are two ways in which the listener is affected by nonblocking mode: /// - Whenever [`accept()`] is called or [`incoming()`] is being iterated through, if there is /// no client currently attempting to connect to the named pipe server, the method will return /// immediately with the [`WouldBlock`](io::ErrorKind::WouldBlock) error instead of blocking /// until one arrives. /// - The streams created by [`accept()`] and [`incoming()`] behave similarly to how client-side /// streams behave in nonblocking mode. See the documentation for `set_nonblocking` for an /// explanation of the exact effects. /// /// [`accept()`]: PipeListener::accept /// [`incoming()`]: PipeListener::incoming pub nonblocking: bool, /// Specifies the maximum amount of instances of the pipe which can be created, i.e. how many /// clients can be communicated with at once. If set to 1, trying to create multiple instances /// at the same time will return an error (in fact, this breaks `.accept()`). If set to `None`, /// no limit is applied. The value 255 is not allowed because it is the underlying Windows API's /// sentinel for not having a limit. pub instance_limit: Option, /// Enables write-through mode, which applies only to network connections to the pipe. If /// enabled, sending to the pipe will always block until all data is delivered to the other end /// instead of piling up in the kernel's network buffer until a certain amount of data /// accumulates or a certain period of time passes, which is when the system actually sends the /// contents of the buffer over the network. /// /// If debug assertions are enabled, setting this parameter on a local-only pipe will cause a /// panic when the pipe is created; in release builds, creation will successfully complete /// without any errors and the flag will be completely ignored. pub write_through: bool, /// Enables remote machines to connect to the named pipe over the network. pub accept_remote: bool, /// Specifies how big the input buffer should be. The system will automatically adjust this size /// to align it as required or clip it by the minimum or maximum buffer size. pub input_buffer_size_hint: u32, /// Specifies how big the output buffer should be. The system will automatically adjust this /// size to align it as required or clip it by the minimum or maximum buffer size. pub output_buffer_size_hint: u32, /// The default timeout clients use when connecting. Used unless another timeout is specified /// when waiting by a client. pub wait_timeout: WaitTimeout, /// The security descriptor to create the named pipe server with. pub security_descriptor: Option, /// Whether the resulting handle is to be inheritable by child processes or not. /// /// There is little to no reason for this to ever be `true`. pub inheritable: bool, } impl<'path> PipeListenerOptions<'path> { /// Creates a new builder with default options. #[allow(clippy::indexing_slicing)] // are you fucking with me pub fn new() -> Self { Self { path: Cow::Borrowed(u16cstr!("")), mode: PipeMode::Bytes, nonblocking: false, instance_limit: None, write_through: false, accept_remote: false, input_buffer_size_hint: 512, output_buffer_size_hint: 512, wait_timeout: WaitTimeout::DEFAULT, security_descriptor: None, inheritable: false, } } /// Clones configuration options which are not owned by value and returns a copy of the original /// option table which is guaranteed not to borrow anything and thus ascribes to the `'static` /// lifetime. pub fn to_owned(&self) -> io::Result> { // We need this ugliness because the compiler does not understand that // PipeListenerOptions<'a> can coerce into PipeListenerOptions<'static> if we manually // replace the name field with Cow::Owned and just copy all other elements over thanks // to the fact that they don't contain a mention of the lifetime 'a. Tbh we need an // RFC for this, would be nice. Ok(PipeListenerOptions { path: Cow::Owned(self.path.clone().into_owned()), mode: self.mode, nonblocking: self.nonblocking, instance_limit: self.instance_limit, write_through: self.write_through, accept_remote: self.accept_remote, input_buffer_size_hint: self.input_buffer_size_hint, output_buffer_size_hint: self.output_buffer_size_hint, wait_timeout: self.wait_timeout, security_descriptor: self .security_descriptor .as_ref() .map(|sd| sd.try_clone()) .transpose()?, inheritable: self.inheritable, }) } /// Sets the [`path`](#structfield.path) parameter to the specified value. #[inline] pub fn path(mut self, path: impl ToWtf16<'path>) -> Self { self.path = path.to_wtf_16().expect(EXPECT_WTF16); self } builder_setters! { mode: PipeMode, nonblocking: bool, instance_limit: Option, write_through: bool, accept_remote: bool, input_buffer_size_hint: u32, output_buffer_size_hint: u32, wait_timeout: WaitTimeout, security_descriptor: Option, inheritable: bool, } /// Creates the pipe listener from the builder. The `Rm` and `Sm` generic arguments specify the /// type of pipe stream that the listener will create, thus determining the direction of the /// pipe and its mode. /// /// # Errors /// In addition to regular OS errors, an error will be returned if the given `Rm` is /// [`pipe_mode::Messages`], but the `mode` field isn't also [`pipe_mode::Messages`]. pub fn create(&self) -> io::Result> { let (owned_config, instance) = self._create(PipeListener::::STREAM_ROLE, Rm::MODE)?; Ok(PipeListener::from_handle_and_options(instance.into(), owned_config)) } /// Alias for [`.create()`](Self::create) with the same `Rm` and `Sm`. #[inline] pub fn create_duplex(&self) -> io::Result> { self.create::() } /// Alias for [`.create()`](Self::create) with an `Sm` of [`pipe_mode::None`]. #[inline] pub fn create_recv_only( &self, ) -> io::Result> { self.create::() } /// Alias for [`.create()`](Self::create) with an `Rm` of [`pipe_mode::None`]. #[inline] pub fn create_send_only( &self, ) -> io::Result> { self.create::() } } impl Default for PipeListenerOptions<'_> { #[inline(always)] fn default() -> Self { Self::new() } } impl TryClone for PipeListenerOptions<'_> { fn try_clone(&self) -> io::Result { Ok(Self { path: self.path.clone(), mode: self.mode, nonblocking: self.nonblocking, instance_limit: self.instance_limit, write_through: self.write_through, accept_remote: self.accept_remote, input_buffer_size_hint: self.input_buffer_size_hint, output_buffer_size_hint: self.output_buffer_size_hint, wait_timeout: self.wait_timeout, security_descriptor: self .security_descriptor .as_ref() .map(|sd| sd.try_clone()) .transpose()?, inheritable: self.inheritable, }) } } interprocess-2.2.3/src/os/windows/named_pipe/listener.rs000064400000000000000000000140701046102023000215500ustar 00000000000000mod create_instance; mod incoming; mod options; use { super::{c_wrappers, PipeModeTag, PipeStream, PipeStreamRole, RawPipeStream}, crate::{ os::windows::{winprelude::*, FileHandle}, poison_error, OrErrno, RawOsErrorExt, LOCK_POISON, }, std::{ fmt::{self, Debug, Formatter}, io, marker::PhantomData, mem::replace, ptr, sync::{ atomic::{AtomicBool, Ordering::Relaxed}, Mutex, }, }, windows_sys::Win32::{ Foundation::{ERROR_PIPE_CONNECTED, ERROR_PIPE_LISTENING}, System::Pipes::ConnectNamedPipe, }, }; pub use {incoming::*, options::*}; // TODO(2.3.0) finish collect_options and add conversion from handles after all /// The server for a named pipe, listening for connections to clients and producing pipe streams. /// /// Note that this type does not correspond to any Win32 object, and is an invention of Interprocess /// in its entirety. /// /// The only way to create a `PipeListener` is to use [`PipeListenerOptions`]. See its documentation /// for more. // TODO(2.3.0) examples pub struct PipeListener { config: PipeListenerOptions<'static>, // We need the options to create new instances nonblocking: AtomicBool, stored_instance: Mutex, _phantom: PhantomData<(Rm, Sm)>, } impl PipeListener { const STREAM_ROLE: PipeStreamRole = PipeStreamRole::get_for_rm_sm::(); /// Blocks until a client connects to the named pipe, creating a `Stream` to communicate with /// the pipe. /// /// See `incoming` for an iterator version of this. pub fn accept(&self) -> io::Result> { let instance_to_hand_out = { let mut stored_instance = self.stored_instance.lock().map_err(poison_error)?; // Doesn't actually even need to be atomic to begin with, but it's simpler and more // convenient to do this instead. The mutex takes care of ordering. let nonblocking = self.nonblocking.load(Relaxed); block_on_connect(stored_instance.as_handle())?; let new_instance = self.create_instance(nonblocking)?; replace(&mut *stored_instance, new_instance) }; let raw = RawPipeStream::new_server(instance_to_hand_out); Ok(PipeStream::new(raw)) } /// Creates an iterator which accepts connections from clients, blocking each time `next()` is /// called until one connects. #[inline] pub fn incoming(&self) -> Incoming<'_, Rm, Sm> { Incoming(self) } /// Enables or disables the nonblocking mode for all existing instances of the listener and /// future ones. By default, it is disabled. /// /// This should generally be done during creation, using the /// [`nonblocking` field](PipeListenerOptions::nonblocking) of the creation options (unless /// there's a good reason not to), which allows making one less system call during creation. /// /// See the documentation of the aforementioned field for the exact effects of enabling this /// mode. pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { let instance = self.stored_instance.lock().map_err(poison_error)?; // Doesn't actually even need to be atomic to begin with, but it's simpler and more // convenient to do this instead. The mutex takes care of ordering. self.nonblocking.store(nonblocking, Relaxed); c_wrappers::set_nonblocking_given_readmode(instance.as_handle(), nonblocking, Rm::MODE)?; // Make it clear that the lock survives until this moment. drop(instance); Ok(()) } /// Creates a listener from a handle and a [`PipeListenerOptions`] table with the assumption /// that the handle was created with those options. /// /// The options are necessary to provide because the listener needs to create new instances of /// the named pipe server in `.accept()`. // TODO(2.3.0) mention TryFrom here pub fn from_handle_and_options( handle: OwnedHandle, options: PipeListenerOptions<'static>, ) -> Self { Self { nonblocking: AtomicBool::new(options.nonblocking), config: options, stored_instance: Mutex::new(FileHandle::from(handle)), _phantom: PhantomData, } } fn create_instance(&self, nonblocking: bool) -> io::Result { self.config .create_instance(false, nonblocking, false, Self::STREAM_ROLE, Rm::MODE) .map(FileHandle::from) } } impl Debug for PipeListener { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PipeListener") .field("config", &self.config) .field("instance", &self.stored_instance) .field("nonblocking", &self.nonblocking.load(Relaxed)) .finish() } } /// The returned handle is owned by the listener until the next call to /// `.accept()`/`::next()`, after which it is owned by the returned stream /// instead. /// /// This momentarily locks an internal mutex. impl AsRawHandle for PipeListener { fn as_raw_handle(&self) -> RawHandle { self.stored_instance.lock().expect(LOCK_POISON).as_raw_handle() } } impl From> for OwnedHandle { fn from(p: PipeListener) -> Self { p.stored_instance.into_inner().expect(LOCK_POISON).into() } } fn block_on_connect(handle: BorrowedHandle<'_>) -> io::Result<()> { unsafe { ConnectNamedPipe(handle.as_int_handle(), ptr::null_mut()) != 0 } .true_val_or_errno(()) .or_else(thunk_accept_error) } fn thunk_accept_error(e: io::Error) -> io::Result<()> { if e.raw_os_error().eeq(ERROR_PIPE_CONNECTED) { Ok(()) } else if e.raw_os_error().eeq(ERROR_PIPE_LISTENING) { Err(io::Error::from(io::ErrorKind::WouldBlock)) } else { Err(e) } } interprocess-2.2.3/src/os/windows/named_pipe/local_socket/listener.rs000064400000000000000000000044711046102023000242160ustar 00000000000000use { super::stream::Stream, crate::{ local_socket::{ traits::{self, ListenerNonblockingMode, Stream as _}, ListenerOptions, NameInner, }, os::windows::named_pipe::{pipe_mode::Bytes, PipeListener, PipeListenerOptions}, AtomicEnum, Sealed, }, std::{io, iter::FusedIterator, os::windows::prelude::*, sync::atomic::Ordering::SeqCst}, }; type ListenerImpl = PipeListener; /// Wrapper around [`PipeListener`] that implements [`Listener`](traits::Listener). #[derive(Debug)] pub struct Listener { listener: ListenerImpl, nonblocking: AtomicEnum, } impl Sealed for Listener {} impl traits::Listener for Listener { type Stream = Stream; fn from_options(options: ListenerOptions<'_>) -> io::Result { let mut impl_options = PipeListenerOptions::new(); let NameInner::NamedPipe(path) = options.name.0; impl_options.path = path; impl_options.nonblocking = options.nonblocking.accept_nonblocking(); impl_options.security_descriptor = options.security_descriptor; Ok(Self { listener: impl_options.create()?, nonblocking: AtomicEnum::new(options.nonblocking), }) } fn accept(&self) -> io::Result { use ListenerNonblockingMode as LNM; let stream = self.listener.accept().map(Stream)?; // TODO(2.3.0) verify necessity of orderings let nonblocking = self.nonblocking.load(SeqCst); if matches!(nonblocking, LNM::Accept) { stream.set_nonblocking(false)?; } else if matches!(nonblocking, LNM::Stream) { stream.set_nonblocking(true)?; } Ok(stream) } fn set_nonblocking(&self, nonblocking: ListenerNonblockingMode) -> io::Result<()> { self.listener.set_nonblocking(nonblocking.accept_nonblocking())?; self.nonblocking.store(nonblocking, SeqCst); Ok(()) } fn do_not_reclaim_name_on_drop(&mut self) {} } impl Iterator for Listener { type Item = io::Result; #[inline(always)] fn next(&mut self) -> Option { Some(traits::Listener::accept(self)) } } impl FusedIterator for Listener {} impl From for OwnedHandle { #[inline] fn from(l: Listener) -> Self { l.listener.into() } } interprocess-2.2.3/src/os/windows/named_pipe/local_socket/stream.rs000064400000000000000000000076751046102023000236750ustar 00000000000000use { crate::{ error::{FromHandleError, ReuniteError}, local_socket::{ traits::{self, ReuniteResult}, Name, NameInner, }, os::windows::named_pipe::{ pipe_mode::Bytes, DuplexPipeStream, RecvPipeStream, SendPipeStream, }, Sealed, }, std::{ io::{self, Write}, os::windows::io::OwnedHandle, }, }; type StreamImpl = DuplexPipeStream; type RecvHalfImpl = RecvPipeStream; type SendHalfImpl = SendPipeStream; /// Wrapper around [`DuplexPipeStream`] that implements [`Stream`](traits::Stream). #[derive(Debug)] pub struct Stream(pub(super) StreamImpl); impl Sealed for Stream {} impl traits::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; fn connect(name: Name<'_>) -> io::Result { let NameInner::NamedPipe(path) = name.0; StreamImpl::connect_by_path(path).map(Self) } #[inline] fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { self.0.set_nonblocking(nonblocking) } #[inline] fn split(self) -> (RecvHalf, SendHalf) { let (rh, sh) = self.0.split(); (RecvHalf(rh), SendHalf(sh)) } fn reunite(rh: RecvHalf, sh: SendHalf) -> ReuniteResult { StreamImpl::reunite(rh.0, sh.0).map(Self).map_err(|ReuniteError { rh, sh }| { ReuniteError { rh: RecvHalf(rh), sh: SendHalf(sh) } }) } } impl Write for &Stream { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { (&self.0).write(buf) } #[inline] fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { (&self.0).write_vectored(bufs) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } // FUTURE is_write_vectored } impl From for OwnedHandle { fn from(s: Stream) -> Self { // The outer local socket interface has receive and send halves and is always duplex in the // unsplit type, so a split pipe stream can never appear here. s.0.try_into().expect("split named pipe stream inside `local_socket::Stream`") } } impl TryFrom for Stream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { match StreamImpl::try_from(handle) { Ok(s) => Ok(Self(s)), Err(e) => Err(FromHandleError { details: Default::default(), cause: Some(e.details.into()), source: e.source, }), } } } multimacro! { Stream, forward_rbv(StreamImpl, &), forward_sync_read, forward_sync_ref_read, forward_as_handle, forward_try_clone, derive_sync_mut_write, derive_trivial_conv(StreamImpl), } /// Wrapper around [`RecvPipeStream`] that implements [`RecvHalf`](traits::RecvHalf). pub struct RecvHalf(pub(super) RecvHalfImpl); multimacro! { RecvHalf, forward_rbv(RecvHalfImpl, &), forward_sync_read, forward_sync_ref_read, forward_as_handle, forward_debug("local_socket::RecvHalf"), derive_trivial_conv(RecvHalfImpl), } /// Wrapper around [`SendPipeStream`] that implements [`SendHalf`](traits::SendHalf). pub struct SendHalf(pub(super) SendHalfImpl); multimacro! { SendHalf, forward_as_handle, forward_debug("local_socket::SendHalf"), derive_sync_mut_write, derive_trivial_conv(SendHalfImpl), } impl Write for &SendHalf { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { (&self.0).write(buf) } #[inline] fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { (&self.0).write_vectored(bufs) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } // FUTURE is_write_vectored } impl Sealed for RecvHalf {} impl traits::RecvHalf for RecvHalf { type Stream = Stream; } impl Sealed for SendHalf {} impl traits::SendHalf for SendHalf { type Stream = Stream; } interprocess-2.2.3/src/os/windows/named_pipe/local_socket/tokio/listener.rs000064400000000000000000000022261046102023000253370ustar 00000000000000use { super::Stream, crate::{ local_socket::{traits::tokio as traits, ListenerOptions, NameInner}, os::windows::named_pipe::{ pipe_mode, tokio::{PipeListener as GenericPipeListener, PipeListenerOptionsExt as _}, PipeListenerOptions, }, Sealed, }, std::io, }; type PipeListener = GenericPipeListener; /// Wrapper around [`PipeListener`] that implements [`Listener`](traits::Listener). #[derive(Debug)] pub struct Listener(PipeListener); impl Sealed for Listener {} impl traits::Listener for Listener { type Stream = Stream; fn from_options(options: ListenerOptions<'_>) -> io::Result { let mut impl_options = PipeListenerOptions::new(); let NameInner::NamedPipe(path) = options.name.0; impl_options.path = path; impl_options.security_descriptor = options.security_descriptor; impl_options.create_tokio().map(Self) } async fn accept(&self) -> io::Result { let inner = self.0.accept().await?; Ok(Stream(inner)) } fn do_not_reclaim_name_on_drop(&mut self) {} } interprocess-2.2.3/src/os/windows/named_pipe/local_socket/tokio/stream.rs000064400000000000000000000077531046102023000250170ustar 00000000000000use { crate::{ error::{FromHandleError, ReuniteError}, local_socket::{ traits::tokio::{self as traits, ReuniteResult}, Name, NameInner, }, os::windows::named_pipe::{ pipe_mode::Bytes, tokio::{DuplexPipeStream, RecvPipeStream, SendPipeStream}, }, Sealed, }, std::{ io, os::windows::prelude::*, pin::Pin, task::{Context, Poll}, }, tokio::io::AsyncWrite, }; type StreamImpl = DuplexPipeStream; type RecvHalfImpl = RecvPipeStream; type SendHalfImpl = SendPipeStream; /// Wrapper around [`DuplexPipeStream`] that implements [`Stream`](traits::Stream). #[derive(Debug)] pub struct Stream(pub(super) StreamImpl); impl Sealed for Stream {} impl traits::Stream for Stream { type RecvHalf = RecvHalf; type SendHalf = SendHalf; async fn connect(name: Name<'_>) -> io::Result { let NameInner::NamedPipe(path) = name.0; StreamImpl::connect_by_path(path).await.map(Self) } #[inline] fn split(self) -> (RecvHalf, SendHalf) { let (r, w) = self.0.split(); (RecvHalf(r), SendHalf(w)) } #[inline] fn reunite(rh: RecvHalf, sh: SendHalf) -> ReuniteResult { StreamImpl::reunite(rh.0, sh.0).map(Self).map_err(|ReuniteError { rh, sh }| { ReuniteError { rh: RecvHalf(rh), sh: SendHalf(sh) } }) } } impl AsyncWrite for &Stream { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut &self.get_mut().0).poll_write(cx, buf) } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } impl TryFrom for Stream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { match StreamImpl::try_from(handle) { Ok(s) => Ok(Self(s)), Err(e) => Err(FromHandleError { details: Default::default(), cause: Some(e.details.into()), source: e.source, }), } } } multimacro! { Stream, pinproj_for_unpin(StreamImpl), forward_rbv(StreamImpl, &), forward_tokio_read, forward_tokio_ref_read, forward_as_handle, derive_tokio_mut_write, derive_trivial_conv(StreamImpl), } /// Wrapper around [`RecvPipeStream`] that implements [`RecvHalf`](traits::RecvHalf). pub struct RecvHalf(pub(super) RecvHalfImpl); impl Sealed for RecvHalf {} impl traits::RecvHalf for RecvHalf { type Stream = Stream; } multimacro! { RecvHalf, pinproj_for_unpin(RecvHalfImpl), forward_rbv(RecvHalfImpl, &), forward_tokio_read, forward_tokio_ref_read, forward_as_handle, forward_debug("local_socket::RecvHalf"), derive_trivial_conv(RecvHalfImpl), } /// Wrapper around [`SendPipeStream`] that implements [`SendHalf`](traits::SendHalf). pub struct SendHalf(pub(super) SendHalfImpl); impl Sealed for SendHalf {} impl traits::SendHalf for SendHalf { type Stream = Stream; } impl AsyncWrite for &SendHalf { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut &self.get_mut().0).poll_write(cx, buf) } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } multimacro! { SendHalf, forward_rbv(SendHalfImpl, &), forward_as_handle, forward_debug("local_socket::SendHalf"), derive_tokio_mut_write, derive_trivial_conv(SendHalfImpl), } interprocess-2.2.3/src/os/windows/named_pipe/maybe_arc.rs000064400000000000000000000052021046102023000216420ustar 00000000000000use { crate::AsMutPtr, std::{mem::ManuallyDrop, ops::Deref, ptr, sync::Arc}, }; /// Inlining optimization for `Arc`. #[derive(Debug)] pub enum MaybeArc { Inline(T), Shared(Arc), } impl MaybeArc { /// `Arc::clone` in place. // TODO(2.3.0) this whole function is dodgy, Miri correction needed pub fn refclone(&mut self) -> Self { let arc = match self { Self::Inline(mx) => { let x = unsafe { // SAFETY: generally a no-op from a safety perspective; the ManuallyDrop ensures // that it stays that way in the event of a panic in Arc::new ptr::read(mx.as_mut_ptr().cast::>()) }; let arc = Arc::new(x); // BEGIN no-panic zone let arc = unsafe { // SAFETY: ManuallyDrop is layout-transparent Arc::from_raw(Arc::into_raw(arc).cast::()) }; unsafe { // SAFETY: self, being a mutable reference, is valid for writes ptr::write(self, Self::Shared(arc)); } // END no-panic zone, the danger has passed let ref_for_clone = match self { Self::Shared(s) => &*s, Self::Inline(..) => unreachable!(), }; Arc::clone(ref_for_clone) } Self::Shared(arc) => Arc::clone(arc), }; Self::Shared(arc) } pub fn try_make_owned(&mut self) -> bool { if let Self::Shared(am) = self { let a = unsafe { ptr::read(am) }; if let Ok(x) = Arc::try_unwrap(a) { unsafe { ptr::write(self, Self::Inline(x)); } true } else { false } } else { true } } pub fn ptr_eq(this: &Self, other: &Self) -> bool { match this { Self::Inline(..) => false, Self::Shared(a) => match other { Self::Inline(..) => false, Self::Shared(b) => Arc::ptr_eq(a, b), }, } } } impl Deref for MaybeArc { type Target = T; #[inline(always)] fn deref(&self) -> &Self::Target { match self { Self::Inline(x) => x, Self::Shared(a) => a, } } } impl From for MaybeArc { #[inline] fn from(x: T) -> Self { Self::Inline(x) } } impl From> for MaybeArc { #[inline] fn from(a: Arc) -> Self { Self::Shared(a) } } interprocess-2.2.3/src/os/windows/named_pipe/stream/enums.rs000064400000000000000000000054621046102023000223520ustar 00000000000000use super::super::*; /// Tags for [`PipeStream`]'s generic arguments that specify the directionality of the stream and /// how it receives and/or sends data (as bytes or as messages). /// /// This is a sort of const-generic incarnation of the [`PipeMode`] enumeration that allows /// `PipeStream` to be consolidated into a single struct with generic parameters that decide which /// traits are implemented on it. /// /// Some examples of how different `PipeStream` signatures would look: /// - **`PipeStream`** (or, thanks to default generic arguments, simply `PipeStream`) /// is a duplex stream that receives and sends bytes. /// - **`PipeStream`** is a receive-only message stream. /// - **`PipeStream`** is a duplex stream that receives bytes but sends messages. pub mod pipe_mode { use super::*; mod seal { use super::*; pub trait PipeModeTag: Copy + std::fmt::Debug + Eq + Send + Sync + Unpin { const MODE: Option; #[cfg(feature = "tokio")] type TokioFlusher: std::fmt::Debug + Default; } #[cfg(feature = "tokio")] pub trait NotNone: PipeModeTag { } #[cfg(not(feature = "tokio"))] pub trait NotNone: PipeModeTag {} } pub(crate) use seal::*; macro_rules! present_tag { ($(#[$attr:meta])* $tag:ident is $mode:expr ; no_tokio_flusher) => { tag_enum!($( #[$attr] )* $tag); impl PipeModeTag for $tag { const MODE: Option = $mode; #[cfg(feature = "tokio")] type TokioFlusher = (); } }; ($(#[$attr:meta])* $tag:ident is $mode:expr) => { tag_enum!($( #[$attr] )* $tag); impl PipeModeTag for $tag { const MODE: Option = $mode; #[cfg(feature = "tokio")] type TokioFlusher = crate::os::windows::tokio_flusher::TokioFlusher; } }; ($($(#[$attr:meta])* $tag:ident is $mode:expr $(; $yayornay:ident)?),+ $(,)?) => { $(present_tag!($( #[$attr] )* $tag is $mode $(; $yayornay)?);)+ }; } present_tag! { /// Tags a direction of a [`PipeStream`] to be absent. None is None ; no_tokio_flusher, /// Tags a direction of a [`PipeStream`] to be present with byte-wise semantics. Bytes is Some(PipeMode::Bytes), /// Tags a direction of a [`PipeStream`] to be present with message-wise semantics. Messages is Some(PipeMode::Messages), } impl NotNone for Bytes {} impl NotNone for Messages {} } pub(crate) use pipe_mode::{NotNone as PmtNotNone, PipeModeTag}; interprocess-2.2.3/src/os/windows/named_pipe/stream/error.rs000064400000000000000000000033451046102023000223520ustar 00000000000000use { super::*, crate::error::ConversionError, std::{ fmt::{self, Debug, Display, Formatter}, io, os::windows::prelude::*, }, }; /// Additional contextual information for conversions from a raw handle to a named pipe stream. /// /// Not to be confused with the Tokio version. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FromHandleErrorKind { /// It wasn't possible to determine whether the pipe handle corresponds to a pipe server or a /// pipe client. IsServerCheckFailed, /// The type being converted into has message semantics, but message boundaries are not /// preserved in the pipe. NoMessageBoundaries, } impl FromHandleErrorKind { const fn msg(self) -> &'static str { use FromHandleErrorKind::*; match self { IsServerCheckFailed => "failed to determine if the pipe is server-side or not", NoMessageBoundaries => "the pipe does not preserve message boundaries", } } } impl From for io::Error { fn from(e: FromHandleErrorKind) -> Self { io::Error::other(e.msg()) } } impl Display for FromHandleErrorKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.msg()) } } /// Error type for [`TryFrom`](TryFrom) constructors. /// /// Not to be confused with the Tokio version. pub type FromHandleError = ConversionError; /// [`ReuniteError`](crate::error::ReuniteError) for sync named pipe streams. pub type ReuniteError = crate::error::ReuniteError, SendPipeStream>; /// Result type for [`PipeStream::reunite()`]. pub type ReuniteResult = Result, ReuniteError>; interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/ctor.rs000064400000000000000000000043261046102023000231310ustar 00000000000000use { super::*, crate::os::windows::{named_pipe::WaitTimeout, path_conversion::*}, widestring::U16CStr, windows_sys::Win32::System::Pipes::PIPE_READMODE_MESSAGE, }; impl RawPipeStream { pub(super) fn new(handle: FileHandle, is_server: bool, nfv: NeedsFlushVal) -> Self { Self { handle: Some(handle), is_server, needs_flush: NeedsFlush::from(nfv), concurrency_detector: ConcurrencyDetector::new(), } } pub(crate) fn new_server(handle: FileHandle) -> Self { Self::new(handle, true, NeedsFlushVal::No) } fn new_client(handle: FileHandle) -> Self { Self::new(handle, false, NeedsFlushVal::No) } fn connect( path: &U16CStr, recv: Option, send: Option, ) -> io::Result { let handle = loop { match c_wrappers::connect_without_waiting(path, recv, send, false) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { c_wrappers::block_for_server(path, WaitTimeout::DEFAULT)?; continue; } els => break els, } }?; if recv == Some(PipeMode::Messages) { c_wrappers::set_np_handle_state( handle.as_handle(), Some(PIPE_READMODE_MESSAGE), None, None, )?; } Ok(Self::new_client(handle)) } } impl PipeStream { /// Connects to the specified named pipe at the specified path (the `\\\pipe\` prefix /// is not added automatically), blocking until a server instance is dispatched. #[inline] pub fn connect_by_path<'p>(path: impl ToWtf16<'p>) -> io::Result { RawPipeStream::connect(&path.to_wtf_16().map_err(to_io_error)?, Rm::MODE, Sm::MODE) .map(Self::new) } /// Internal constructor used by the listener. It's a logic error, but not UB, to create the /// thing from the wrong kind of thing, but that never ever happens, to the best of my ability. pub(crate) fn new(raw: RawPipeStream) -> Self { Self { raw: raw.into(), _phantom: PhantomData } } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/debug.rs000064400000000000000000000015271046102023000232500ustar 00000000000000use { super::*, std::fmt::{self, Debug, DebugStruct, Formatter}, }; impl RawPipeStream { fn fill_fields<'a, 'b, 'c>( &self, dbst: &'a mut DebugStruct<'b, 'c>, recv_mode: Option, send_mode: Option, ) -> &'a mut DebugStruct<'b, 'c> { if let Some(recv_mode) = recv_mode { dbst.field("recv_mode", &recv_mode); } if let Some(send_mode) = send_mode { dbst.field("send_mode", &send_mode); } dbst.field("handle", &self.handle).field("is_server", &self.is_server) } } impl Debug for PipeStream { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut dbst = f.debug_struct("PipeStream"); self.raw.fill_fields(&mut dbst, Rm::MODE, Sm::MODE).finish() } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/handle.rs000064400000000000000000000076361046102023000234240ustar 00000000000000use { super::*, crate::{ os::windows::{c_wrappers::duplicate_handle, limbo::LIMBO_ERR}, TryClone, }, std::mem::ManuallyDrop, windows_sys::Win32::System::Pipes::{PIPE_SERVER_END, PIPE_TYPE_MESSAGE}, }; impl AsHandle for RawPipeStream { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { self.file_handle().as_handle() } } derive_asraw!(RawPipeStream); impl RawPipeStream { fn from_handle_given_flags(handle: OwnedHandle, flags: u32) -> Self { Self::new(FileHandle::from(handle), flags & PIPE_SERVER_END != 0, NeedsFlushVal::Once) } } fn is_server_check_failed_error(cause: io::Error, handle: OwnedHandle) -> FromHandleError { FromHandleError { details: FromHandleErrorKind::IsServerCheckFailed, cause: Some(cause), source: Some(handle), } } impl TryFrom for RawPipeStream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { let flags = match c_wrappers::get_flags(handle.as_handle()) { Ok(f) => f, Err(e) => return Err(is_server_check_failed_error(e, handle)), }; Ok(Self::from_handle_given_flags(handle, flags)) } } impl From for OwnedHandle { #[inline] fn from(x: RawPipeStream) -> Self { let x = ManuallyDrop::new(x); let handle = unsafe { std::ptr::read(&x.handle) }; handle.expect(LIMBO_ERR).into() } } /// Attempts to unwrap the given stream into the raw owned handle type, returning itself back if /// no ownership over it is available, as is the case when the stream is split. impl TryFrom> for OwnedHandle { type Error = PipeStream; #[inline] fn try_from(s: PipeStream) -> Result { match s.raw { MaybeArc::Inline(x) => Ok(x.into()), MaybeArc::Shared(..) => Err(s), } } } /// Attempts to wrap the given handle into the high-level pipe stream type. If the underlying pipe /// type is wrong or trying to figure out whether it's wrong or not caused a system call error, the /// corresponding error condition is returned. /// /// For more on why this can fail, see [`FromHandleError`]. Most notably, server-side send-only /// pipes will cause "access denied" errors because they lack permissions to check whether it's a /// server-side pipe and whether it has message boundaries. impl TryFrom for PipeStream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { let flags = match c_wrappers::get_flags(handle.as_handle()) { Ok(f) => f, Err(e) => return Err(is_server_check_failed_error(e, handle)), }; // If the wrapper type tries to receive incoming data as messages, that might break if // the underlying pipe has no message boundaries. Let's check for that. if Rm::MODE == Some(PipeMode::Messages) && flags & PIPE_TYPE_MESSAGE == 0 { return Err(FromHandleError { details: FromHandleErrorKind::NoMessageBoundaries, cause: None, source: Some(handle), }); } Ok(Self::new(RawPipeStream::from_handle_given_flags(handle, flags))) } } impl TryClone for PipeStream { fn try_clone(&self) -> io::Result { let handle = duplicate_handle(self.as_handle())?; self.raw.needs_flush.on_clone(); let new = RawPipeStream::new(handle.into(), self.is_server(), NeedsFlushVal::Always); Ok(Self::new(new)) } } impl AsHandle for PipeStream { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { self.raw.as_handle() } } derive_asraw!({Rm: PipeModeTag, Sm: PipeModeTag} PipeStream, windows); interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/recv_bytes.rs000064400000000000000000000024601046102023000243240ustar 00000000000000use { super::*, crate::{os::windows::downgrade_eof, weaken_buf_init_mut}, }; impl RawPipeStream { #[track_caller] fn read(&self, buf: &mut [u8]) -> io::Result { self.read_to_uninit(weaken_buf_init_mut(buf)) } #[track_caller] fn read_to_uninit(&self, buf: &mut [MaybeUninit]) -> io::Result { let _guard = self.concurrency_detector.lock(); self.file_handle().read(buf) } } impl PipeStream { /// Same as `.read()` from the [`Read`] trait, but accepts an uninitialized buffer. /// /// Interacts with [concurrency prevention](#concurrency-prevention). #[inline] pub fn read_to_uninit(&self, buf: &mut [MaybeUninit]) -> io::Result { downgrade_eof(self.raw.read_to_uninit(buf)) } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl Read for &PipeStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { downgrade_eof(self.raw.read(buf)) } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl Read for PipeStream { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> io::Result { (&*self).read(buf) } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/recv_msg.rs000064400000000000000000000115101046102023000237600ustar 00000000000000use { super::*, crate::{os::windows::downgrade_eof, RawOsErrorExt as _}, recvmsg::{prelude::*, NoAddrBuf, RecvResult}, windows_sys::Win32::Foundation::ERROR_MORE_DATA, }; pub(crate) const DISCARD_BUF_SIZE: usize = { // Debug builds are more prone to stack explosions. if cfg!(debug_assertions) { 512 } else { 4096 } }; impl RawPipeStream { fn peek_msg_len(&self) -> io::Result { let _guard = self.concurrency_detector.lock(); c_wrappers::peek_msg_len(self.as_handle()) } #[track_caller] fn discard_msg(&self) -> io::Result<()> { let _guard = self.concurrency_detector.lock(); let mut buf = [MaybeUninit::uninit(); DISCARD_BUF_SIZE]; let fh = self.file_handle(); loop { match downgrade_eof(fh.read(&mut buf)) { Ok(..) => break Ok(()), Err(e) if e.raw_os_error().eeq(ERROR_MORE_DATA) => {} Err(e) => break Err(e), } } } #[track_caller] fn recv_msg(&self, buf: &mut MsgBuf<'_>) -> io::Result { let _guard = self.concurrency_detector.lock(); buf.set_fill(0); buf.has_msg = false; let mut more_data = true; let mut partial = false; let mut spilled = false; let fh = self.file_handle(); while more_data { let slice = buf.unfilled_part(); if slice.is_empty() { match buf.grow() { Ok(()) => { spilled = true; debug_assert!( !buf.unfilled_part().is_empty(), "successful buffer growth did not yield additional capacity" ); continue; } Err(e) => { if more_data { // A partially successful partial receive must result in the rest of the // message being discarded. let _ = self.discard_msg(); } return Ok(RecvResult::QuotaExceeded(e)); } } } let rslt = fh.read(slice); more_data = false; let incr = match decode_eof(rslt) { Ok(incr) => incr, Err(e) if e.raw_os_error().eeq(ERROR_MORE_DATA) => { more_data = true; partial = true; slice.len() } Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { buf.set_fill(0); return Ok(RecvResult::EndOfStream); } Err(e) => { if partial { // This is irrelevant to normal operation of downstream // programs, but still makes them easier to debug. let _ = self.discard_msg(); } return Err(e); } }; #[allow(clippy::arithmetic_side_effects)] // this cannot panic due to the isize limit unsafe { // SAFETY: this one is on Windows buf.advance_init_and_set_fill(buf.len_filled() + incr) }; } buf.has_msg = true; Ok(if spilled { RecvResult::Spilled } else { RecvResult::Fit }) } } impl PipeStream { /// Returns the length of the next incoming message without receiving it or blocking the /// thread. Note that a return value of `Ok(0)` does not allow the lack of an incoming message /// to be distinguished from a zero-length message. /// /// If the message stream has been closed, this returns a /// [`BrokenPipe`](io::ErrorKind::BrokenPipe) error. /// /// Interacts with [concurrency prevention](#concurrency-prevention). #[inline] pub fn peek_msg_len(&self) -> io::Result { self.raw.peek_msg_len() } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl RecvMsg for &PipeStream { type Error = io::Error; type AddrBuf = NoAddrBuf; #[inline] fn recv_msg( &mut self, buf: &mut MsgBuf<'_>, _: Option<&mut NoAddrBuf>, ) -> io::Result { self.raw.recv_msg(buf) } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl RecvMsg for PipeStream { type Error = io::Error; type AddrBuf = NoAddrBuf; #[inline] fn recv_msg( &mut self, buf: &mut MsgBuf<'_>, _: Option<&mut NoAddrBuf>, ) -> io::Result { (&*self).recv_msg(buf, None) } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/send.rs000064400000000000000000000053121046102023000231070ustar 00000000000000use super::*; impl RawPipeStream { #[track_caller] fn send(&self, buf: &[u8]) -> io::Result { let r = { let _guard = self.concurrency_detector.lock(); self.file_handle().write(buf) }; if r.is_ok() { self.needs_flush.mark_dirty(); } r } #[track_caller] fn flush(&self) -> io::Result<()> { if self.needs_flush.take() { let r = self.file_handle().flush(); if r.is_err() { self.needs_flush.mark_dirty(); } r } else { Ok(()) } } } impl PipeStream { /// Flushes the stream, blocking until the send buffer is empty (has been received by the other /// end in its entirety). /// /// Only available on streams that have a send mode. #[inline] pub fn flush(&self) -> io::Result<()> { self.raw.flush() } /// Marks the stream as unflushed, preventing elision of the next flush operation (which /// includes limbo). #[inline] pub fn mark_dirty(&self) { self.raw.needs_flush.mark_dirty(); } /// Assumes that the other side has consumed everything that's been written so far. This will /// turn the next flush into a no-op, but will cause the send buffer to be cleared when the /// stream is closed, since it won't be sent to limbo. #[inline] pub fn assume_flushed(&self) { self.raw.needs_flush.take(); } /// Drops the stream without sending it to limbo. This is the same as calling /// `assume_flushed()` right before dropping it. #[inline] pub fn evade_limbo(self) { self.assume_flushed(); } } impl PipeStream { /// Sends a message into the pipe, returning how many bytes were successfully sent (typically /// equal to the size of what was requested to be sent). /// /// Interacts with [concurrency prevention](#concurrency-prevention). #[inline] pub fn send(&self, buf: &[u8]) -> io::Result { self.raw.send(buf) } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl Write for &PipeStream { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.raw.send(buf) } #[inline] fn flush(&mut self) -> io::Result<()> { self.raw.flush() } } /// Interacts with [concurrency prevention](#concurrency-prevention). impl Write for PipeStream { #[inline(always)] fn write(&mut self, buf: &[u8]) -> io::Result { (&*self).write(buf) } #[inline(always)] fn flush(&mut self) -> io::Result<()> { (&mut &*self).flush() } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl/send_off.rs000064400000000000000000000007751046102023000237510ustar 00000000000000use { super::*, crate::os::windows::limbo::{ sync::{send_off, Corpse}, LIMBO_ERR, REBURY_ERR, }, }; impl RawPipeStream { pub(super) fn file_handle(&self) -> &FileHandle { self.handle.as_ref().expect(LIMBO_ERR) } } impl Drop for RawPipeStream { fn drop(&mut self) { let corpse = Corpse { handle: self.handle.take().expect(REBURY_ERR), is_server: self.is_server }; if self.needs_flush.get_mut() { send_off(corpse); } } } interprocess-2.2.3/src/os/windows/named_pipe/stream/impl.rs000064400000000000000000000115141046102023000221570ustar 00000000000000//! Methods and trait implementations for `PipeStream`. mod ctor; mod debug; mod handle; mod recv_bytes; mod recv_msg; mod send; mod send_off; use { super::*, crate::{ os::windows::{ decode_eof, named_pipe::{ c_wrappers::{self as c_wrappers, hget}, PipeMode, }, AsRawHandleExt, FileHandle, ImpersonationGuard, NeedsFlushVal, }, OrErrno, ToBool, }, std::{ io::{self, prelude::*}, marker::PhantomData, mem::MaybeUninit, }, windows_sys::Win32::System::Pipes, }; impl PipeStream { /// Splits the pipe stream by value, returning a receive half and a send half. The stream is /// closed when both are dropped, kind of like an `Arc` (which is how it's implemented under the /// hood). pub fn split(mut self) -> (RecvPipeStream, SendPipeStream) { let (raw_ac, raw_a) = (self.raw.refclone(), self.raw); (RecvPipeStream { raw: raw_a, _phantom: PhantomData }, SendPipeStream { raw: raw_ac, _phantom: PhantomData, }) } /// Attempts to reunite a receive half with a send half to yield the original stream back, /// returning both halves as an error if they belong to different streams (or when using /// this method on streams that haven't been split to begin with). pub fn reunite(rh: RecvPipeStream, sh: SendPipeStream) -> ReuniteResult { if !MaybeArc::ptr_eq(&rh.raw, &sh.raw) { return Err(ReuniteError { rh, sh }); } let mut raw = sh.raw; drop(rh); raw.try_make_owned(); Ok(PipeStream { raw, _phantom: PhantomData }) } /// Retrieves the process identifier of the client side of the named pipe connection. #[inline] pub fn client_process_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeClientProcessId) } } /// Retrieves the session identifier of the client side of the named pipe connection. #[inline] pub fn client_session_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeClientSessionId) } } /// Retrieves the process identifier of the server side of the named pipe connection. #[inline] pub fn server_process_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeServerProcessId) } } /// Retrieves the session identifier of the server side of the named pipe connection. #[inline] pub fn server_session_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeServerSessionId) } } /// Returns `true` if the stream was created by a listener (server-side), `false` if it was /// created by connecting to a server (server-side). #[inline] pub fn is_server(&self) -> bool { self.raw.is_server } /// Returns `true` if the stream was created by connecting to a server (client-side), `false` if /// it was created by a listener (server-side). #[inline] pub fn is_client(&self) -> bool { !self.raw.is_server } /// Sets whether the nonblocking mode for the pipe stream is enabled. By default, it is /// disabled. /// /// In nonblocking mode, attempts to receive from the pipe when there is no data available or to /// send when the buffer has filled up because the receiving side hasn't received enough bytes /// in time never block like they normally do. Instead, a /// [`WouldBlock`](io::ErrorKind::WouldBlock) error is immediately returned, allowing the thread /// to perform useful actions in the meantime. /// /// *If called on the server side, the flag will be set only for one stream instance.* A /// listener creation option, [`nonblocking`], and a similar method on the listener, /// [`.set_nonblocking()`], can be used to set the mode in bulk for all current instances and /// future ones. /// /// [`nonblocking`]: super::super::PipeListenerOptions::nonblocking /// [`.set_nonblocking()`]: super::super::PipeListener::set_nonblocking #[inline] pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { c_wrappers::set_nonblocking_given_readmode(self.as_handle(), nonblocking, Rm::MODE) } /// [Impersonates the client][imp] of the named pipe. /// /// The returned impersonation guard automatically reverts impersonation when it goes out of /// scope. /// /// [imp]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient pub fn impersonate_client(&self) -> io::Result { unsafe { Pipes::ImpersonateNamedPipeClient(self.as_int_handle()) } .to_bool() .true_or_errno(|| ImpersonationGuard(())) } } interprocess-2.2.3/src/os/windows/named_pipe/stream.rs000064400000000000000000000100141046102023000212100ustar 00000000000000mod enums; mod error; pub use {enums::*, error::*}; mod r#impl; use { super::MaybeArc, crate::{ local_socket::{ConcurrencyDetectionSite, ConcurrencyDetector}, os::windows::{FileHandle, NeedsFlush}, }, std::{marker::PhantomData, os::windows::prelude::*}, }; /// Named pipe stream, created by a server-side listener or by connecting to a server. /// /// This type combines in itself all possible combinations of /// [receive modes and send modes](pipe_mode), plugged into it using the `Rm` and `Sm` generic /// parameters respectively. /// /// Pipe streams can be split by reference and by value for concurrent receive and send operations. /// Splitting by reference is ephemeral and can be achieved by simply borrowing the stream, since /// both `PipeStream` and `&PipeStream` implement I/O traits. Splitting by value is done using the /// [`.split()`](Self::split) method, producing a receive half and a send half, and can be reverted /// via [`.reunite()`](PipeStream::reunite). /// /// # Additional features /// This section documents behavior introduced by this named pipe implementation which is not /// present in the underlying Windows API. /// /// ## Connection termination condition thunking /// `ERROR_PIPE_NOT_CONNECTED` and [`BrokenPipe`](std::io::ErrorKind::BrokenPipe) errors are /// translated to EOF (`Ok(0)`) for bytestreams and `RecvResult::EndOfStream` for message streams. /// /// ## Flushing behavior /// Upon being dropped, streams that haven't been flushed since the last send are transparently sent /// to **limbo** – a thread pool that ensures that the peer does not get `BrokenPipe`/EOF /// immediately after all data has been sent, which would otherwise discard everything. Named pipe /// handles on this thread pool are flushed first and only then closed, ensuring that they are only /// destroyed when the peer is done reading them. /// /// If a stream hasn't seen a single send since the last explicit flush by the time it is dropped, /// it will evade limbo. This can be overriden with [`.mark_dirty()`](PipeStream::mark_dirty). /// /// Similarly to limbo elision, explicit flushes are elided on streams that haven't sent anything /// since the last flush – thus, the second of any two consecutive `.flush()` calls is a no-op that /// returns immediately and cannot fail. This can also be overridden in the same manner. /// /// ## Concurrency prevention /// Multiple I/O operations [cannot be performed on the same named pipe concurrently][ms], and /// attempts to do so will be caught by the concurrency detector in order to avoid deadlocks and /// other unexpected, chaotic behavior. /// /// [ms]: https://learn.microsoft.com/en-nz/windows/win32/ipc/named-pipe-server-using-overlapped-i-o /// /// # Examples /// /// ## Basic bytestream client /// ```no_run #[doc = doctest_file::include_doctest!("examples/named_pipe/sync/stream/bytes.rs")] /// ``` /// /// ## Basic message stream client /// ```no_run #[doc = doctest_file::include_doctest!("examples/named_pipe/sync/stream/msg.rs")] /// ``` pub struct PipeStream { raw: MaybeArc, _phantom: PhantomData<(Rm, Sm)>, } /// Type alias for a pipe stream with the same receive mode and send mode. pub type DuplexPipeStream = PipeStream; /// Type alias for a pipe stream with a receive mode but no send mode. /// /// This can be produced by the listener, by connecting, or by splitting. pub type RecvPipeStream = PipeStream; /// Type alias for a pipe stream with a send mode but no receive mode. /// /// This can be produced by the listener, by connecting, or by splitting. pub type SendPipeStream = PipeStream; pub(crate) struct RawPipeStream { handle: Option, is_server: bool, needs_flush: NeedsFlush, concurrency_detector: ConcurrencyDetector, } #[derive(Default)] struct NamedPipeSite; impl ConcurrencyDetectionSite for NamedPipeSite { const NAME: &'static str = "named pipe"; const WOULD_ACTUALLY_DEADLOCK: bool = true; } interprocess-2.2.3/src/os/windows/named_pipe/tokio/listener.rs000064400000000000000000000142661046102023000227040ustar 00000000000000use { crate::{ os::windows::{ named_pipe::{ enums::{PipeMode, PipeStreamRole}, pipe_mode, tokio::{PipeStream, RawPipeStream}, PipeListenerOptions, PipeModeTag, }, winprelude::*, }, Sealed, }, std::{ fmt::{self, Debug, Formatter}, io, marker::PhantomData, mem::replace, }, tokio::{net::windows::named_pipe::NamedPipeServer as TokioNPServer, sync::Mutex}, }; /// Tokio-based async server for a named pipe, asynchronously listening for connections to clients /// and producing asynchronous pipe streams. /// /// Note that this type does not correspond to any Tokio object, and is an invention of Interprocess /// in its entirety. /// /// The only way to create a `PipeListener` is to use [`PipeListenerOptions`]. See its documentation /// for more. /// /// # Examples /// /// ## Basic server /// ```no_run #[doc = doctest_file::include_doctest!("examples/named_pipe/sync/stream/bytes.rs")] /// ``` pub struct PipeListener { config: PipeListenerOptions<'static>, // We need the options to create new instances stored_instance: Mutex, _phantom: PhantomData<(Rm, Sm)>, } impl PipeListener { const STREAM_ROLE: PipeStreamRole = PipeStreamRole::get_for_rm_sm::(); /// Asynchronously waits until a client connects to the named pipe, creating a `Stream` to /// communicate with the pipe. pub async fn accept(&self) -> io::Result> { let instance_to_hand_out = { let mut stored_instance = self.stored_instance.lock().await; stored_instance.connect().await?; let new_instance = self.create_instance()?; replace(&mut *stored_instance, new_instance) }; let raw = RawPipeStream::new_server(instance_to_hand_out); Ok(PipeStream::new(raw)) } /// Creates a listener from a [corresponding Tokio object](TokioNPServer) and a /// [`PipeListenerOptions`] table with the assumption that the handle was created with those /// options. /// /// The options are necessary to provide because the listener needs to create new instances of /// the named pipe server in `.accept()`. // TODO(2.3.0) mention TryFrom here pub fn from_tokio_and_options( tokio_object: TokioNPServer, options: PipeListenerOptions<'static>, ) -> Self { Self { config: options, stored_instance: Mutex::new(tokio_object), _phantom: PhantomData } } /// Creates a listener from a handle and a [`PipeListenerOptions`] table with the assumption /// that the handle was created with those options. /// /// The options are necessary to provide because the listener needs to create new instances of /// the named pipe server in `.accept()`. /// /// # Errors /// Returns an error if called outside a Tokio runtime. // TODO(2.3.0) mention TryFrom here pub fn from_handle_and_options( handle: OwnedHandle, options: PipeListenerOptions<'static>, ) -> io::Result { Ok(Self::from_tokio_and_options(npserver_from_handle(handle)?, options)) } fn create_instance(&self) -> io::Result { self.config .create_instance(false, false, true, Self::STREAM_ROLE, Rm::MODE) .and_then(npserver_from_handle) } } impl Debug for PipeListener { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PipeListener") .field("config", &self.config) .field("instance", &self.stored_instance) .finish() } } /// Extends [`PipeListenerOptions`] with a constructor method for the Tokio [`PipeListener`]. #[allow(private_bounds)] pub trait PipeListenerOptionsExt: Sealed { /// Creates a Tokio pipe listener from the builder. See the /// [non-async `create` method on `PipeListenerOptions`](PipeListenerOptions::create) for more. /// /// The `nonblocking` parameter is ignored and forced to be enabled. fn create_tokio(&self) -> io::Result>; /// Alias for [`.create_tokio()`](PipeListenerOptionsExt::create_tokio) with the same `Rm` and /// `Sm`. #[inline] fn create_tokio_duplex(&self) -> io::Result> { self.create_tokio::() } /// Alias for [`.create_tokio()`](PipeListenerOptionsExt::create_tokio) with an `Sm` of /// [`pipe_mode::None`]. #[inline] fn create_tokio_recv_only( &self, ) -> io::Result> { self.create_tokio::() } /// Alias for [`.create_tokio()`](PipeListenerOptionsExt::create_tokio) with an `Rm` of /// [`pipe_mode::None`]. #[inline] fn create_tokio_send_only( &self, ) -> io::Result> { self.create_tokio::() } } impl PipeListenerOptionsExt for PipeListenerOptions<'_> { fn create_tokio(&self) -> io::Result> { let (owned_config, instance) = _create_tokio(self, PipeListener::::STREAM_ROLE, Rm::MODE)?; Ok(PipeListener::from_tokio_and_options(instance, owned_config)) } } impl Sealed for PipeListenerOptions<'_> {} fn _create_tokio( config: &PipeListenerOptions<'_>, role: PipeStreamRole, recv_mode: Option, ) -> io::Result<(PipeListenerOptions<'static>, TokioNPServer)> { // Shadow to avoid mixing them up. let mut config = config.to_owned()?; // Tokio should ideally already set that, but let's do it just in case. config.nonblocking = false; let instance = config .create_instance(true, false, true, role, recv_mode) .and_then(npserver_from_handle)?; Ok((config, instance)) } fn npserver_from_handle(handle: OwnedHandle) -> io::Result { unsafe { TokioNPServer::from_raw_handle(handle.into_raw_handle()) } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/error.rs000064400000000000000000000042361046102023000234770ustar 00000000000000use { super::*, crate::{error::ConversionError, os::windows::winprelude::*}, std::fmt::{self, Display, Formatter}, }; /// Additional contextual information for conversions from a raw handle to a named pipe stream. /// /// Not to be confused with the /// [non-Tokio version](crate::os::windows::named_pipe::stream::FromHandleErrorKind). #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FromHandleErrorKind { /// It wasn't possible to determine whether the pipe handle corresponds to a pipe server or a /// pipe client. IsServerCheckFailed, /// The type being converted into has message semantics, but message boundaries are not /// preserved in the pipe. NoMessageBoundaries, /// An error was reported by Tokio. /// /// Most of the time, this means that `from_raw_handle()` call was performed outside of the /// Tokio runtime, but OS errors associated with the registration of the handle in the runtime /// belong to this category as well. TokioError, } impl FromHandleErrorKind { const fn msg(self) -> &'static str { use FromHandleErrorKind::*; match self { IsServerCheckFailed => "failed to determine if the pipe is server-side or not", NoMessageBoundaries => "the pipe does not preserve message boundaries", TokioError => "Tokio error", } } } impl From for io::Error { fn from(e: FromHandleErrorKind) -> Self { io::Error::other(e.msg()) } } impl Display for FromHandleErrorKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.msg()) } } /// Error type for [`TryFrom`](TryFrom) constructors. /// /// Not to be confused with the /// [non-Tokio version](crate::os::windows::named_pipe::stream::FromHandleError). pub type FromHandleError = ConversionError; /// [`ReuniteError`](crate::error::ReuniteError) for Tokio named pipe streams. pub type ReuniteError = crate::error::ReuniteError, SendPipeStream>; /// Result type for [`PipeStream::reunite()`]. pub type ReuniteResult = Result, ReuniteError>; interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/ctor.rs000064400000000000000000000055561046102023000242640ustar 00000000000000use { super::*, crate::os::windows::{named_pipe::WaitTimeout, path_conversion::*, NeedsFlushVal}, std::{borrow::Cow, mem::take}, widestring::U16CString, }; impl RawPipeStream { pub(super) fn new(inner: InnerTokio, nfv: NeedsFlushVal) -> Self { Self { inner: Some(inner), needs_flush: NeedsFlush::from(nfv), //recv_msg_state: Mutex::new(RecvMsgState::NotRecving), } } pub(crate) fn new_server(server: TokioNPServer) -> Self { Self::new(InnerTokio::Server(server), NeedsFlushVal::No) } fn new_client(client: TokioNPClient) -> Self { Self::new(InnerTokio::Client(client), NeedsFlushVal::No) } async fn wait_for_server(path: U16CString) -> io::Result { tokio::task::spawn_blocking(move || { c_wrappers::block_for_server(&path, WaitTimeout::DEFAULT)?; Ok(path) }) .await .expect("waiting for server panicked") } async fn connect( mut path: U16CString, recv: Option, send: Option, ) -> io::Result { let client = loop { match c_wrappers::connect_without_waiting(&path, recv, send, true) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { let path_take = Self::wait_for_server(take(&mut path)).await?; path = path_take; } not_waiting => break not_waiting?, } }; let client = unsafe { TokioNPClient::from_raw_handle(client.into_raw_handle())? }; /* MESSAGE READING DISABLED // FIXME(2.3.0) should probably upstream FILE_WRITE_ATTRIBUTES for PipeMode::Messages to Tokio if recv == Some(PipeMode::Messages) { set_named_pipe_handle_state(client.as_handle(), Some(PIPE_READMODE_MESSAGE), None, None)?; } */ Ok(Self::new_client(client)) } } impl PipeStream { /// Connects to the specified named pipe at the specified path (the `\\\pipe\` prefix /// is not added automatically), waiting until a server instance is dispatched. #[inline] pub async fn connect_by_path<'s>(path: impl ToWtf16<'s>) -> io::Result { RawPipeStream::connect( path.to_wtf_16().map(Cow::into_owned).map_err(to_io_error)?, Rm::MODE, Sm::MODE, ) .await .map(Self::new) } /// Internal constructor used by the listener. It's a logic error, but not UB, to create the /// thing from the wrong kind of thing, but that never ever happens, to the best of my ability. pub(crate) fn new(raw: RawPipeStream) -> Self { Self { raw: MaybeArc::Inline(raw), flusher: Sm::TokioFlusher::default(), _phantom: PhantomData, } } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/debug.rs000064400000000000000000000022321046102023000243670ustar 00000000000000use { super::*, std::fmt::{self, Debug, DebugStruct, Formatter}, }; impl RawPipeStream { #[allow(clippy::as_conversions)] fn fill_fields<'a, 'b, 'c>( &self, dbst: &'a mut DebugStruct<'b, 'c>, recv_mode: Option, send_mode: Option, ) -> &'a mut DebugStruct<'b, 'c> { let (tokio_object, is_server) = match self.inner() { InnerTokio::Server(s) => (s as _, true), InnerTokio::Client(c) => (c as _, false), }; if let Some(recv_mode) = recv_mode { dbst.field("recv_mode", &recv_mode); } if let Some(send_mode) = send_mode { dbst.field("send_mode", &send_mode); } dbst.field("tokio_object", tokio_object).field("is_server", &is_server) } } impl Debug for PipeStream { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut dbst = f.debug_struct("PipeStream"); self.raw.fill_fields(&mut dbst, Rm::MODE, Sm::MODE); if Sm::MODE.is_some() { dbst.field("flusher", &self.flusher); } dbst.finish() } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/handle.rs000064400000000000000000000070201046102023000245340ustar 00000000000000use { super::*, crate::os::windows::NeedsFlushVal, std::mem::ManuallyDrop, windows_sys::Win32::System::Pipes::{PIPE_SERVER_END, PIPE_TYPE_MESSAGE}, }; impl AsHandle for InnerTokio { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { same_clsrv!(x in self => x.as_handle()) } } derive_asraw!(InnerTokio); impl AsHandle for RawPipeStream { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { self.inner().as_handle() } } derive_asraw!(RawPipeStream); impl RawPipeStream { fn try_from_handle_given_flags( handle: OwnedHandle, flags: u32, ) -> Result { let rh = handle.as_raw_handle(); let handle = ManuallyDrop::new(handle); let tkresult = unsafe { match flags & PIPE_SERVER_END != 0 { true => TokioNPServer::from_raw_handle(rh).map(InnerTokio::Server), false => TokioNPClient::from_raw_handle(rh).map(InnerTokio::Client), } }; match tkresult { Ok(s) => Ok(Self::new(s, NeedsFlushVal::Once)), Err(e) => Err(FromHandleError { details: FromHandleErrorKind::TokioError, cause: Some(e), source: Some(ManuallyDrop::into_inner(handle)), }), } } } fn is_server_check_failed_error(cause: io::Error, handle: OwnedHandle) -> FromHandleError { FromHandleError { details: FromHandleErrorKind::IsServerCheckFailed, cause: Some(cause), source: Some(handle), } } impl TryFrom for RawPipeStream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { match c_wrappers::get_flags(handle.as_handle()) { Ok(flags) => Self::try_from_handle_given_flags(handle, flags), Err(e) => Err(is_server_check_failed_error(e, handle)), } } } impl AsHandle for PipeStream { fn as_handle(&self) -> BorrowedHandle<'_> { self.raw.as_handle() } } /// Attempts to wrap the given handle into the high-level pipe stream type. If the underlying pipe /// type is wrong or trying to figure out whether it's wrong or not caused a system call error, the /// corresponding error condition is returned. /// /// For more on why this can fail, see [`FromHandleError`]. Most notably, server-side send-only /// pipes will cause "access denied" errors because they lack permissions to check whether it's a /// server-side pipe and whether it has message boundaries. impl TryFrom for PipeStream { type Error = FromHandleError; fn try_from(handle: OwnedHandle) -> Result { let flags = match c_wrappers::get_flags(handle.as_handle()) { Ok(f) => f, Err(e) => return Err(is_server_check_failed_error(e, handle)), }; // If the wrapper type tries to receive incoming data as messages, that might break if // the underlying pipe has no message boundaries. Let's check for that. if Rm::MODE == Some(PipeMode::Messages) && flags & PIPE_TYPE_MESSAGE == 0 { return Err(FromHandleError { details: FromHandleErrorKind::NoMessageBoundaries, cause: None, source: Some(handle), }); } let raw = RawPipeStream::try_from_handle_given_flags(handle, flags)?; Ok(Self::new(raw)) } } derive_asraw!({Rm: PipeModeTag, Sm: PipeModeTag} PipeStream, windows); interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/recv_bytes.rs000064400000000000000000000023401046102023000254460ustar 00000000000000use { super::*, crate::os::windows::downgrade_eof, tokio::io::{AsyncRead, ReadBuf}, }; impl RawPipeStream { fn poll_read_readbuf( &self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { loop { match downgrade_eof(same_clsrv!(x in self.inner() => x.try_read_buf(buf))) { Ok(..) => return Poll::Ready(Ok(())), Err(e) if e.kind() == io::ErrorKind::WouldBlock => {} Err(e) => return Poll::Ready(Err(e)), } ready!(same_clsrv!(x in self.inner() => x.poll_read_ready(cx)))?; } } } impl AsyncRead for &PipeStream { #[inline(always)] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { self.get_mut().raw.poll_read_readbuf(cx, buf) } } impl AsyncRead for PipeStream { #[inline(always)] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { AsyncRead::poll_read(Pin::new(&mut &*self), cx, buf) } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/recv_msg.rs000064400000000000000000000146041046102023000251140ustar 00000000000000// MESSAGE READING DISABLED use {super::*, std::mem::MaybeUninit}; impl RawPipeStream { fn poll_read_uninit( &self, cx: &mut Context<'_>, buf: &mut [MaybeUninit], ) -> Poll> { let mut readbuf = ReadBuf::uninit(buf); ready!(self.poll_read_readbuf(cx, &mut readbuf).map(downgrade_eof))?; Poll::Ready(Ok(readbuf.filled().len())) } fn poll_discard_msg(&self, cx: &mut Context<'_>) -> Poll> { let mut buf = [MaybeUninit::uninit(); DISCARD_BUF_SIZE]; Poll::Ready(loop { match decode_eof(ready!(self.poll_read_uninit(cx, &mut buf))) { Ok(..) => break Ok(()), Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break Ok(()), Err(e) if e.raw_os_error() == Some(ERROR_MORE_DATA as _) => {} Err(e) => break Err(e), } }) } // TODO clarify in recvmsg that using different buffers across different polls of this function // that return Pending makes for unexpected behavior fn poll_recv_msg( &self, cx: &mut Context<'_>, buf: &mut MsgBuf<'_>, lock: Option>, ) -> Poll> { let mut mode = 0; match decode_eof(get_named_pipe_handle_state( self.as_handle(), Some(&mut mode), None, None, None, None, )) { Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { return Poll::Ready(Ok(RecvResult::EndOfStream)) } els => els, }?; eprintln!("DBG mode {:#x}", mode); let mut state = lock.unwrap_or_else(|| self.recv_msg_state.lock().unwrap()); match &mut *state { RecvMsgState::NotRecving => { buf.set_fill(0); buf.has_msg = false; *state = RecvMsgState::Looping { spilled: false, partial: false }; self.poll_recv_msg(cx, buf, Some(state)) } RecvMsgState::Looping { spilled, partial } => { let mut more_data = true; while more_data { let slice = buf.unfilled_part(); if slice.is_empty() { match buf.grow() { Ok(()) => { *spilled = true; debug_assert!(!buf.unfilled_part().is_empty()); } Err(e) => { let qer = Ok(RecvResult::QuotaExceeded(e)); if more_data { // A partially successful partial receive must result in the // rest of the message being discarded. *state = RecvMsgState::Discarding { result: qer }; return self.poll_recv_msg(cx, buf, Some(state)); } else { *state = RecvMsgState::NotRecving; return Poll::Ready(qer); } } } continue; } let mut rslt = ready!(self.poll_read_uninit(cx, slice)); more_data = false; if matches!(&rslt, Ok(0)) { // FIXME(2.3.0) Mio sometimes does broken pipe thunking (this is a bug that // breaks zero-sized messages) rslt = Err(io::Error::from(io::ErrorKind::BrokenPipe)); } let incr = match decode_eof(rslt) { Ok(incr) => incr, Err(e) if e.raw_os_error() == Some(ERROR_MORE_DATA as _) => { more_data = true; *partial = true; slice.len() } Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { buf.set_fill(0); return Poll::Ready(Ok(RecvResult::EndOfStream)); } Err(e) => { return if *partial { // This is irrelevant to normal operation of downstream // programs, but still makes them easier to debug. *state = RecvMsgState::Discarding { result: Err(e) }; self.poll_recv_msg(cx, buf, Some(state)) } else { Poll::Ready(Err(e)) }; } }; unsafe { // SAFETY: this one is on Tokio buf.advance_init_and_set_fill(buf.len_filled() + incr) }; } let ret = if *spilled { RecvResult::Spilled } else { RecvResult::Fit }; *state = RecvMsgState::NotRecving; Poll::Ready(Ok(ret)) } RecvMsgState::Discarding { result } => { let _ = ready!(self.poll_discard_msg(cx)); let r = replace(result, Ok(RecvResult::EndOfStream)); // Silly little sentinel... *state = RecvMsgState::NotRecving; // ...gone, so very young. Poll::Ready(r) } } } } impl AsyncRecvMsg for &PipeStream { type Error = io::Error; type AddrBuf = NoAddrBuf; #[inline] fn poll_recv_msg( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut MsgBuf<'_>, _: Option<&mut NoAddrBuf>, ) -> Poll> { self.raw.poll_recv_msg(cx, buf, None) } } impl AsyncRecvMsg for PipeStream { type Error = io::Error; type AddrBuf = NoAddrBuf; #[inline] fn poll_recv_msg( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut MsgBuf<'_>, _: Option<&mut NoAddrBuf>, ) -> Poll> { AsyncRecvMsg::poll_recv_msg((&mut &*self).pin(), cx, buf, None) } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/send.rs000064400000000000000000000102071046102023000242330ustar 00000000000000use { super::*, crate::{ os::windows::{named_pipe::PmtNotNone, winprelude::*}, UnpinExt, }, tokio::io::AsyncWrite, }; impl RawPipeStream { fn poll_write(&self, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { loop { ready!(same_clsrv!(x in self.inner() => x.poll_write_ready(cx)))?; match same_clsrv!(x in self.inner() => x.try_write(buf)) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => continue, els => { self.needs_flush.mark_dirty(); return Poll::Ready(els); } } } } } impl PipeStream { /// Flushes the stream, waiting until the send buffer is empty (has been received by the other /// end in its entirety). /// /// Only available on streams that have a send mode. #[inline] pub async fn flush(&self) -> io::Result<()> { self.flusher.flush_atomic(self.as_handle(), &self.raw.needs_flush).await } /// Polls the future of `.flush()`. #[inline] pub fn poll_flush(&self, cx: &mut Context<'_>) -> Poll> { self.flusher.poll_flush_atomic(self.as_handle(), &self.raw.needs_flush, cx) } /// Marks the stream as unflushed, preventing elision of the next flush operation (which /// includes limbo). #[inline] pub fn mark_dirty(&self) { self.raw.needs_flush.mark_dirty(); } /// Assumes that the other side has consumed everything that's been written so far. This will /// turn the next flush into a no-op, but will cause the send buffer to be cleared when the /// stream is closed, since it won't be sent to limbo. /// /// If there's already an outstanding `.flush()` operation, it won't be affected by this call. #[inline] pub fn assume_flushed(&self) { self.raw.needs_flush.take(); } /// Drops the stream without sending it to limbo. This is the same as calling `assume_flushed()` /// right before dropping it. /// /// If there's already an outstanding `.flush()` operation, it won't be affected by this call. #[inline] pub fn evade_limbo(self) { self.assume_flushed(); } } impl PipeStream { /// Sends a message into the pipe, returning how many bytes were successfully sent (typically /// equal to the size of what was requested to be sent). #[inline] pub async fn send(&self, buf: &[u8]) -> io::Result { struct Write<'a>(&'a RawPipeStream, &'a [u8]); impl Future for Write<'_> { type Output = io::Result; #[inline] fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let slf = self.get_mut(); slf.0.poll_write(cx, slf.1) } } Write(&self.raw, buf).await } } impl AsyncWrite for &PipeStream { #[inline(always)] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.get_mut().raw.poll_write(cx, buf) } #[inline(always)] fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.get_mut().poll_flush(cx) } #[inline(always)] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { AsyncWrite::poll_flush(self, cx) } } impl AsyncWrite for PipeStream { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { AsyncWrite::poll_write((&mut &*self).pin(), cx, buf) } #[inline] fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { AsyncWrite::poll_flush((&mut &*self).pin(), cx) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { AsyncWrite::poll_shutdown((&mut &*self).pin(), cx) } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl/send_off.rs000064400000000000000000000007161046102023000250710ustar 00000000000000use { super::*, crate::os::windows::limbo::{ tokio::{send_off, Corpse}, LIMBO_ERR, REBURY_ERR, }, }; impl RawPipeStream { pub(super) fn inner(&self) -> &InnerTokio { self.inner.as_ref().expect(LIMBO_ERR) } } impl Drop for RawPipeStream { fn drop(&mut self) { let corpse = self.inner.take().map(Corpse::from).expect(REBURY_ERR); if self.needs_flush.get_mut() { send_off(corpse); } } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream/impl.rs000064400000000000000000000066171046102023000233140ustar 00000000000000//! Methods and trait implementations for `PipeStream`. macro_rules! same_clsrv { ($nm:ident in $var:expr => $e:expr) => { match $var { InnerTokio::Server($nm) => $e, InnerTokio::Client($nm) => $e, } }; } mod ctor; mod debug; mod handle; mod recv_bytes; mod send; mod send_off; use { super::*, crate::os::windows::{ named_pipe::{ c_wrappers::{self, hget}, PipeMode, }, winprelude::*, }, std::{ future::Future, pin::Pin, task::{ready, Context, Poll}, }, tokio::net::windows::named_pipe::{ NamedPipeClient as TokioNPClient, NamedPipeServer as TokioNPServer, }, windows_sys::Win32::System::Pipes, }; impl PipeStream { /// Splits the pipe stream by value, returning a receive half and a send half. The stream is /// closed when both are dropped, kind of like an `Arc` (which is how it's implemented under the /// hood). pub fn split(mut self) -> (RecvPipeStream, SendPipeStream) { let (raw_ac, raw_a) = (self.raw.refclone(), self.raw); (RecvPipeStream { raw: raw_a, flusher: (), _phantom: PhantomData }, SendPipeStream { raw: raw_ac, flusher: self.flusher, _phantom: PhantomData, }) } /// Attempts to reunite a receive half with a send half to yield the original stream back, /// returning both halves as an error if they belong to different streams (or when using /// this method on streams that were never split to begin with). pub fn reunite(rh: RecvPipeStream, sh: SendPipeStream) -> ReuniteResult { if !MaybeArc::ptr_eq(&rh.raw, &sh.raw) { return Err(ReuniteError { rh, sh }); } let PipeStream { mut raw, flusher, .. } = sh; drop(rh); raw.try_make_owned(); Ok(PipeStream { raw, flusher, _phantom: PhantomData }) } /// Retrieves the process identifier of the client side of the named pipe connection. #[inline] pub fn client_process_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeClientProcessId) } } /// Retrieves the session identifier of the client side of the named pipe connection. #[inline] pub fn client_session_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeClientSessionId) } } /// Retrieves the process identifier of the server side of the named pipe connection. #[inline] pub fn server_process_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeServerProcessId) } } /// Retrieves the session identifier of the server side of the named pipe connection. #[inline] pub fn server_session_id(&self) -> io::Result { unsafe { hget(self.as_handle(), Pipes::GetNamedPipeServerSessionId) } } /// Returns `true` if the stream was created by a listener (server-side), `false` if it was /// created by connecting to a server (server-side). #[inline] pub fn is_server(&self) -> bool { matches!(self.raw.inner(), &InnerTokio::Server(..)) } /// Returns `true` if the stream was created by connecting to a server (client-side), `false` if /// it was created by a listener (server-side). #[inline] pub fn is_client(&self) -> bool { !self.is_server() } } interprocess-2.2.3/src/os/windows/named_pipe/tokio/stream.rs000064400000000000000000000061641046102023000223500ustar 00000000000000// TODO(2.x.0) message reading disabled due to a lack of support in Mio; we should try to figure // something out, they need to add first-class message pipe support and handling of ERROR_MORE_DATA mod error; pub use error::*; mod r#impl; use { crate::os::windows::{ limbo::tokio::Corpse, named_pipe::{ stream::{pipe_mode, PipeModeTag}, MaybeArc, }, NeedsFlush, }, std::{io, marker::PhantomData}, tokio::net::windows::named_pipe::{ NamedPipeClient as TokioNPClient, NamedPipeServer as TokioNPServer, }, }; /// Tokio-based named pipe stream, created by a server-side listener or by connecting to a server. /// /// This type combines in itself all possible combinations of receive modes and send modes, plugged /// into it using the `Rm` and `Sm` generic parameters respectively. /// /// Pipe streams can be split by reference and by value for concurrent receive and send operations. /// Splitting by reference is ephemeral and can be achieved by simply borrowing the stream, since /// both `PipeStream` and `&PipeStream` implement the I/O traits. Splitting by value is done using /// the [`.split()`](Self::split) method, producing a receive half and a send half, and can be /// reverted via [`.reunite()`](Self::reunite). /// /// # Examples /// /// ## Basic bytestream client /// ```no_run #[doc = doctest_file::include_doctest!("examples/named_pipe/sync/stream/bytes.rs")] /// ``` pub struct PipeStream { raw: MaybeArc, // This specializes to TokioFlusher for non-None send modes and to () for receive-only // streams, reducing the size of read halves. flusher: Sm::TokioFlusher, _phantom: PhantomData<(Rm, Sm)>, } /// Type alias for a Tokio-based pipe stream with the same receive mode and send mode. pub type DuplexPipeStream = PipeStream; /// Type alias for a pipe stream with a receive mode but no send mode. /// /// This can be produced by the listener, by connecting, or by splitting. pub type RecvPipeStream = PipeStream; /// Type alias for a pipe stream with a send mode but no receive mode. /// /// This can be produced by the listener, by connecting, or by splitting. pub type SendPipeStream = PipeStream; pub(crate) struct RawPipeStream { inner: Option, // Cleared by the generic pipes rather than by the raw pipe stream, unlike in sync land. needs_flush: NeedsFlush, // MESSAGE READING DISABLED //recv_msg_state: Mutex, } enum InnerTokio { Server(TokioNPServer), Client(TokioNPClient), } impl From for Corpse { fn from(it: InnerTokio) -> Self { match it { InnerTokio::Server(o) => Corpse::NpServer(o), InnerTokio::Client(o) => Corpse::NpClient(o), } } } /* MESSAGE READING DISABLED #[derive(Debug, Default)] #[repr(u8)] enum RecvMsgState { #[default] NotRecving, Looping { spilled: bool, partial: bool, }, Discarding { result: io::Result, }, } unsafe impl ReprU8 for RecvMsgState {} */ interprocess-2.2.3/src/os/windows/named_pipe/wait_timeout.rs000064400000000000000000000022271046102023000224360ustar 00000000000000/// A [named pipe wait timeout][npw]. /// /// [npw]: https://learn.microsoft.com/en-nz/windows/win32/api/namedpipeapi/nf-namedpipeapi-waitnamedpipew #[repr(transparent)] // #[repr(u32)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct WaitTimeout(u32); impl WaitTimeout { /// Default wait timeout. /// /// If specified on the client, uses the default wait timeout specified by the server. If /// the server also specifies this value, Windows defaults to **50 milliseconds**. pub const DEFAULT: Self = Self(0x00000000); /// Wait indefinitely. pub const FOREVER: Self = Self(0xffffffff); /// Constructs from a raw value (given in milliseconds). /// /// See [`DEFAULT`](Self::DEFAULT) and [`FOREVER`](Self::FOREVER). #[inline(always)] pub const fn from_raw(raw: u32) -> Self { Self(raw) } /// Returns the contained raw value (given in milliseconds). /// /// See [`DEFAULT`](Self::DEFAULT) and [`FOREVER`](Self::FOREVER). #[inline(always)] pub const fn to_raw(self) -> u32 { self.0 } } impl From for u32 { #[inline(always)] fn from(x: WaitTimeout) -> Self { x.to_raw() } } interprocess-2.2.3/src/os/windows/named_pipe.rs000064400000000000000000000075431046102023000177320ustar 00000000000000//! Support for named pipes on Windows. //! //! # Those are not Unix named pipes //! The term "named pipe" refers to completely different things in Unix and Windows. For this //! reason, Unix named pipes are referred to as "FIFO files" to avoid confusion with the more //! powerful Windows named pipes. In fact, the only common features for those two is that they both //! can be located using filesystem paths and they both use a stream interface. The differences can //! be summed up like this: //! - Windows named pipes are located on a separate filesystem (NPFS – **N**amed **P**ipe //! **F**ile**s**ystem), while Unix FIFO files live in the shared filesystem tree together with //! all other files //! - On Linux, the implementation of Unix domain sockets exposes a similar feature: by setting //! the first byte in the socket file path to 0, the socket is placed into a separate //! namespace instead of being placed on the filesystem; this is a non-standard extension to //! POSIX and is not available on other Unix systems //! // TODO check in rustdoc //! - Windows named pipes have a server and an arbitrary number of clients, meaning that the //! separate processes connecting to a named pipe have separate connections to the server, while //! Unix FIFO files don't have the notion of a server or client and thus mix all data written //! into one sink from which the data is received by one process //! - Windows named pipes can be used over the network, while a Unix FIFO file is still local even //! if created in a directory which is a mounted network filesystem //! - Windows named pipes can maintain datagram boundaries, allowing both sides of the connection //! to operate on separate messages rather than on a byte stream, while FIFO files, like any //! other type of file, expose only a byte stream interface //! //! If you carefully read through this list, you'd notice how Windows named pipes are similar to //! Unix domain sockets. For this reason, the implementation of "local sockets" in the //! `local_socket` module of this crate uses named pipes on Windows and Unix-domain sockets on Unix. //! //! # Semantic peculiarities //! Methods and I/O trait implementations on types presented in this module do not exactly map 1:1 //! to Windows API system calls. [`PipeStream`] and [`PipeListener`], together with their async //! counterparts, list important behavior implemented by Interprocess in their item-level //! documentation. // TODO(2.3.0) improve docs and add examples // TODO(2.3.0) raw instance functionality // TODO(2.3.0) transactions mod enums; mod listener; mod stream; mod wait_timeout; pub use {enums::*, listener::*, stream::*, wait_timeout::*}; /// Local sockets implemented using Windows named pipes. pub mod local_socket { mod listener; mod stream; pub use {listener::*, stream::*}; /// Async local sockets for Tokio implemented using named pipes. #[cfg(feature = "tokio")] pub mod tokio { mod listener; mod stream; pub use {listener::*, stream::*}; } } mod c_wrappers; mod maybe_arc; use maybe_arc::*; /// Asynchronous named pipes which work with the Tokio runtime and event loop. /// /// The Tokio integration allows the named pipe streams and listeners to be notified by the OS /// kernel whenever they're ready to be received from or sent to, instead of spawning threads just /// to put them in a wait state of blocking on the I/O. /// /// Types from this module will *not* work with other async runtimes, such as `async-std` or `smol`, /// since the Tokio types' methods will panic whenever they're called outside of a Tokio runtime /// context. Open an issue if you'd like to see other runtimes supported as well. #[cfg(feature = "tokio")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "tokio")))] pub mod tokio { mod listener; mod stream; pub use {listener::*, stream::*}; } interprocess-2.2.3/src/os/windows/needs_flush.rs000064400000000000000000000030441046102023000201200ustar 00000000000000use { crate::{AtomicEnum, ReprU8}, std::sync::atomic::Ordering::{self, *}, }; #[derive(Debug)] pub(crate) struct NeedsFlush(AtomicEnum); impl NeedsFlush { #[inline] pub(crate) fn mark_dirty(&self) { let _ = self.0.compare_exchange( NeedsFlushVal::No, NeedsFlushVal::Once, AcqRel, Relaxed, // We do not care about the loaded value ); } #[inline] pub(crate) fn on_clone(&self) { self.0.store(NeedsFlushVal::Always, Release); } #[inline] pub(crate) fn take(&self) -> bool { match self.0.compare_exchange(NeedsFlushVal::Once, NeedsFlushVal::No, AcqRel, Acquire) { Ok(..) => true, Err(NeedsFlushVal::Always) => true, Err(.. /* NeedsFlushVal::No */) => false, } } #[inline] pub(crate) fn clear(&self) { let _ = self.0.compare_exchange(NeedsFlushVal::Once, NeedsFlushVal::No, AcqRel, Relaxed); } #[inline] pub(crate) fn get(&self, ordering: Ordering) -> bool { matches!(self.0.load(ordering), NeedsFlushVal::Once | NeedsFlushVal::Always) } #[inline] pub(crate) fn get_mut(&mut self) -> bool { matches!(self.0.get_mut(), NeedsFlushVal::Once | NeedsFlushVal::Always) } } impl From for NeedsFlush { #[inline] fn from(val: NeedsFlushVal) -> Self { Self(AtomicEnum::new(val)) } } #[derive(Debug, PartialEq, Eq)] #[repr(u8)] pub(crate) enum NeedsFlushVal { No, Once, Always, } unsafe impl ReprU8 for NeedsFlushVal {} interprocess-2.2.3/src/os/windows/path_conversion.rs000064400000000000000000000120111046102023000210140ustar 00000000000000use { crate::NumExt, std::{ borrow::Cow, ffi::{OsStr, OsString}, io, num::Saturating, os::windows::ffi::OsStrExt, path::{Path, PathBuf}, }, widestring::{ error::{ContainsNul, NulError}, U16CStr, U16CString, }, }; /// Conversion to WTF-16, the native string encoding of Windows NT. pub trait ToWtf16<'a>: Sized { /// Encode to, or borrow as, WTF-16. /// /// Borrowed string types may entail allocation and thus return [`Cow::Owned`] if an in-place /// checked cast fails. /// /// # Errors /// If there are interior nuls. fn to_wtf_16(self) -> Result, ContainsNul>; } pub(crate) static EXPECT_WTF16: &str = "failed to convert to WTF-16"; pub(crate) fn to_io_error(err: ContainsNul) -> io::Error { io::Error::new(io::ErrorKind::InvalidInput, err) } /// Trivial and infallible. impl<'enc> ToWtf16<'enc> for &'enc U16CStr { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { Ok(Cow::Borrowed(self)) } } /// Trivial and infallible. impl<'enc> ToWtf16<'enc> for U16CString { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { Ok(Cow::Owned(self)) } } /// Will allocate if the slice isn't nul-terminated. impl<'enc> ToWtf16<'enc> for &'enc [u16] { fn to_wtf_16(self) -> Result, ContainsNul> { match U16CStr::from_slice(self) { Ok(borrow) => Ok(Cow::Borrowed(borrow)), Err(NulError::MissingNulTerminator(..)) => Ok(self.to_owned().to_wtf_16()?), Err(NulError::ContainsNul(cn)) => Err(cn), } } } /// Will `.push(0)` if the slice isn't nul-terminated, which may entail a memory allocation if the /// `Vec` is at capacity. impl<'enc> ToWtf16<'enc> for Vec { fn to_wtf_16(mut self) -> Result, ContainsNul> { if self.last() != Some(&0) { self.push(0); } Ok(Cow::Owned(U16CString::from_vec(self)?)) } } /// Always reallocates, because `OsStr` is WTF-8. impl<'enc> ToWtf16<'enc> for &OsStr { fn to_wtf_16(self) -> Result, ContainsNul> { Ok(Cow::Owned(U16CString::from_os_str(self)?)) } } /// Always reallocates, because `OsString` is WTF-8. impl<'enc> ToWtf16<'enc> for OsString { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { self.as_os_str().to_wtf_16() } } /// Always reallocates, because `Path` is WTF-8. impl<'enc> ToWtf16<'enc> for &Path { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { self.as_os_str().to_wtf_16() } } /// Always reallocates, because `PathBuf` is WTF-8. impl<'enc> ToWtf16<'enc> for PathBuf { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { self.into_os_string().to_wtf_16() } } /// Always reallocates, because `str` is UTF-8. impl<'enc> ToWtf16<'enc> for &str { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { Ok(Cow::Owned(U16CString::from_str(self)?)) } } /// Always reallocates, because `String` is UTF-8. impl<'enc> ToWtf16<'enc> for String { #[inline] fn to_wtf_16(self) -> Result, ContainsNul> { self.as_str().to_wtf_16() } } impl<'enc, T: ?Sized, O> ToWtf16<'enc> for Cow<'enc, T> where T: ToOwned, &'enc T: ToWtf16<'enc>, O: ToWtf16<'enc>, { fn to_wtf_16(self) -> Result, ContainsNul> { match self { Cow::Borrowed(b) => b.to_wtf_16(), Cow::Owned(o) => o.to_wtf_16(), } } } fn pathcvt<'a>( pipe_name: &'a OsStr, hostname: Option<&'a OsStr>, ) -> (impl Iterator, usize) { const PREFIX_LITERAL: &str = r"\\"; const PIPEFS_LITERAL: &str = r"\pipe\"; const LOCAL_HOSTNAME: &str = "."; const BASE_LEN: Saturating = Saturating(PREFIX_LITERAL.len() + PIPEFS_LITERAL.len()); let hostname = hostname.unwrap_or_else(|| OsStr::new(LOCAL_HOSTNAME)); let components = [OsStr::new(PREFIX_LITERAL), hostname, OsStr::new(PIPEFS_LITERAL), pipe_name]; let userlen = hostname.len().saturate() + pipe_name.len().saturate(); (components.into_iter(), (BASE_LEN + userlen).0) } pub(crate) fn convert_and_encode_path( pipename: &OsStr, hostname: Option<&OsStr>, ) -> io::Result { let (i, cap) = pathcvt(pipename, hostname); let mut path = Vec::with_capacity((cap.saturate() + 1.saturate()).0); i.for_each(|c| path.extend(c.encode_wide())); path.push(0); // Don't forget the nul terminator! U16CString::from_vec(path).map_err(contains_nul_error_to_io) } pub(crate) fn convert_osstr(str: &OsStr) -> io::Result { U16CString::from_os_str(str).map_err(contains_nul_error_to_io) } fn contains_nul_error_to_io(e: ContainsNul) -> io::Error { io::Error::new(io::ErrorKind::InvalidInput, format!("invalid named pipe path: {}", e)) } interprocess-2.2.3/src/os/windows/security_descriptor/as_security_descriptor.rs000064400000000000000000000032401046102023000265140ustar 00000000000000use std::ffi::c_void; /// Common interface for safe access to [security descriptors][sd]. /// /// # Safety /// The following safety constraints must be upheld by all instances of types implementing this /// trait (ideally by marking the appropriate constructors as unsafe): /// /// - The `SECURITY_DESCRIPTOR` structure includes pointer fields which Windows later /// dereferences. Having those pointers point to garbage, uninitialized memory or /// non-dereferencable regions constitutes undefined behavior. /// - The pointers contained inside must not be aliased by mutable references. They are only to be /// accessed using Windows API functions such as `SetEntriesInAcl()`. /// - `IsValidSecurityDescriptor()` must return `true` for the given value. /// /// Code that consumes types implementing `AsSecurityDescriptor` can rely on those things being /// true. /// /// [sd]: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor pub unsafe trait AsSecurityDescriptor { /// Returns a pointer to a security descriptor as accepted by functions of the Windows API. /// /// It is assumed that this pointer is not mutably aliased and cannot be used for modification. fn as_sd(&self) -> *const c_void; } /// Like [`AsSecurityDescriptor`], but allows mutation. /// /// # Safety /// See [`AsSecurityDescriptor`](AsSecurityDescriptor#safety). pub unsafe trait AsSecurityDescriptorMut: AsSecurityDescriptor { /// Returns a pointer to a security descriptor as accepted by functions of the Windows API. /// /// It is assumed that this pointer isn't aliased and can be used for modification. fn as_sd_mut(&mut self) -> *mut c_void; } interprocess-2.2.3/src/os/windows/security_descriptor/borrowed.rs000064400000000000000000000100271046102023000235500ustar 00000000000000use { super::{validate, AsSecurityDescriptor, AsSecurityDescriptorMut}, crate::AsPtr, std::{ffi::c_void, marker::PhantomData, ptr::NonNull}, windows_sys::Win32::Security::SECURITY_DESCRIPTOR, }; /// Pointer to a [security descriptor][sd] with reference-like guarantees which doesn't allow /// mutation. /// /// The pointee is known to be valid (and not mutably aliased) for the duration of the given /// lifetime, just like with regular Rust references. /// /// Unlike [`MutBorrowedSecurityDescriptor`] and the owned /// [`SecurityDescriptor`](super::SecurityDescriptor), this type does not require the security /// descriptor to be absolute. /// /// [sd]: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor #[repr(transparent)] #[derive(Copy, Clone, Debug)] pub struct BorrowedSecurityDescriptor<'a>(NonNull, PhantomData<&'a SECURITY_DESCRIPTOR>); /// Mutability is not provided through [`BorrowedSecurityDescriptor`]. unsafe impl Sync for BorrowedSecurityDescriptor<'_> {} unsafe impl Send for BorrowedSecurityDescriptor<'_> {} unsafe impl AsSecurityDescriptor for BorrowedSecurityDescriptor<'_> { #[inline(always)] fn as_sd(&self) -> *const c_void { self.0.as_ptr().cast_const() } } /// Constructors. impl<'a> BorrowedSecurityDescriptor<'a> { /// Borrows the given security descriptor. /// /// # Safety /// The [safety constraints](AsSecurityDescriptor#safety-constraints) must be upheld. #[inline] pub unsafe fn from_ref(r: &'a SECURITY_DESCRIPTOR) -> Self { unsafe { Self::from_ptr(r.as_ptr().cast()) } } /// Wraps the given raw pointer to a security descriptor. /// /// # Safety /// - The pointer must be non-null, well-aligned and dereferencable. /// - The [safety constraints](AsSecurityDescriptor#safety-constraints) must be upheld. #[inline] pub unsafe fn from_ptr(p: *const c_void) -> Self { let p = p.cast_mut(); unsafe { debug_assert!(!p.is_null(), "null pointer to security descriptor"); validate(p); Self(NonNull::new(p).unwrap_unchecked(), PhantomData) } } } /// Pointer to a [security descriptor][sd] with reference-like guarantees which allows mutation. /// /// The pointee is known to be valid (and not mutably aliased) for the duration of the given /// lifetime, just like with regular Rust references. /// /// [sd]: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor #[repr(transparent)] #[derive(Debug)] pub struct MutBorrowedSecurityDescriptor<'a>( NonNull, PhantomData<&'a mut SECURITY_DESCRIPTOR>, ); /// Interior mutability is not provided through [`BorrowedSecurityDescriptor`]. unsafe impl Sync for MutBorrowedSecurityDescriptor<'_> {} unsafe impl Send for MutBorrowedSecurityDescriptor<'_> {} unsafe impl AsSecurityDescriptor for MutBorrowedSecurityDescriptor<'_> { #[inline(always)] fn as_sd(&self) -> *const c_void { self.0.as_ptr().cast() } } unsafe impl AsSecurityDescriptorMut for MutBorrowedSecurityDescriptor<'_> { #[inline(always)] fn as_sd_mut(&mut self) -> *mut c_void { self.as_sd().cast_mut() } } /// Constructors. impl<'a> MutBorrowedSecurityDescriptor<'a> { /// Borrows the given security descriptor. /// /// # Safety /// The [safety constraints](AsSecurityDescriptor#safety-constraints) must be upheld. #[inline] pub unsafe fn from_ref(r: &'a mut SECURITY_DESCRIPTOR) -> Self { unsafe { Self::from_ptr(r) } } /// Wraps the given raw pointer to a security descriptor. /// /// # Safety /// - The pointer must be non-null, well-aligned and dereferencable. /// - The [safety constraints](AsSecurityDescriptor#safety-constraints) must be upheld. #[inline] pub unsafe fn from_ptr(p: *mut SECURITY_DESCRIPTOR) -> Self { unsafe { debug_assert!(!p.is_null(), "null pointer to security descriptor"); validate(p.cast()); Self(NonNull::new(p).unwrap_unchecked(), PhantomData) } } } interprocess-2.2.3/src/os/windows/security_descriptor/c_wrappers.rs000064400000000000000000000107631046102023000241010ustar 00000000000000use { super::LocalBox, crate::{BoolExt, OrErrno, SubUsizeExt}, std::{ffi::c_void, io, ptr}, widestring::U16CStr, windows_sys::Win32::{ Foundation::{LocalFree, BOOL, PSID}, Security::{ Authorization::{ ConvertSecurityDescriptorToStringSecurityDescriptorW, ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, }, FreeSid, GetSecurityDescriptorControl, SetSecurityDescriptorControl, ACL, SECURITY_DESCRIPTOR_CONTROL, }, }, }; pub(super) unsafe fn control_and_revision( sd: *const c_void, ) -> io::Result<(SECURITY_DESCRIPTOR_CONTROL, u32)> { let mut control = SECURITY_DESCRIPTOR_CONTROL::default(); let mut revision = 0; unsafe { GetSecurityDescriptorControl(sd.cast_mut(), &mut control, &mut revision) } .true_val_or_errno((control, revision)) } pub(super) unsafe fn acl( sd: *const c_void, f: unsafe extern "system" fn(*mut c_void, *mut BOOL, *mut *mut ACL, *mut BOOL) -> BOOL, ) -> io::Result> { let mut exists = 0; let mut pacl = ptr::null_mut(); let mut defaulted = 0; unsafe { f(sd.cast_mut(), &mut exists, &mut pacl, &mut defaulted) }.true_or_errno(|| { if exists != 0 { Some((pacl.cast_const(), defaulted != 0)) } else { None } }) } pub(super) unsafe fn sid( sd: *const c_void, f: unsafe extern "system" fn(*mut c_void, *mut PSID, *mut BOOL) -> BOOL, ) -> io::Result<(*const c_void, bool)> { let mut psid = ptr::null_mut(); let mut defaulted = 1; unsafe { f(sd.cast_mut(), &mut psid, &mut defaulted) } .true_or_errno(|| (psid.cast_const(), defaulted != 0)) } pub(super) unsafe fn set_acl( sd: *const c_void, acl: Option<*mut ACL>, defaulted: bool, f: unsafe extern "system" fn(*mut c_void, BOOL, *const ACL, BOOL) -> BOOL, ) -> io::Result<()> { let has_acl = acl.is_some().to_i32(); // Note that the null ACL is a valid value that does not represent the lack of an ACL. The null // pointer this defaults to will be ignored by Windows because has_acl == false. let acl = acl.unwrap_or(ptr::null_mut()); unsafe { f(sd.cast_mut(), has_acl, acl, defaulted.to_i32()) }.true_val_or_errno(()) } pub(super) unsafe fn set_sid( sd: *const c_void, sid: *mut c_void, defaulted: bool, f: unsafe extern "system" fn(*mut c_void, PSID, BOOL) -> BOOL, ) -> io::Result<()> { unsafe { f(sd.cast_mut(), sid, defaulted.to_i32()) }.true_val_or_errno(()) } pub(super) unsafe fn set_control( sd: *const c_void, mask: SECURITY_DESCRIPTOR_CONTROL, value: SECURITY_DESCRIPTOR_CONTROL, ) -> io::Result<()> { unsafe { SetSecurityDescriptorControl(sd.cast_mut(), mask, value) }.true_val_or_errno(()) } pub(super) unsafe fn unset_acl( sd: *const c_void, f: unsafe extern "system" fn(*mut c_void, BOOL, *const ACL, BOOL) -> BOOL, ) -> io::Result<()> { unsafe { set_acl(sd, None, false, f) } } pub(super) unsafe fn unset_sid( sd: *const c_void, f: unsafe extern "system" fn(*mut c_void, PSID, BOOL) -> BOOL, ) -> io::Result<()> { unsafe { set_sid(sd, ptr::null_mut(), false, f) } } pub(super) unsafe fn free_acl(acl: *mut ACL) -> io::Result<()> { unsafe { LocalFree(acl.cast()) }.is_null().true_val_or_errno(()) } pub(super) unsafe fn free_sid(sid: *mut c_void) -> io::Result<()> { if sid.is_null() { return Ok(()); } if unsafe { FreeSid(sid) }.is_null() { Ok(()) } else { Err(io::Error::other("failed to deallocate SID")) } } pub(super) unsafe fn serialize( sd: *const c_void, selector: u32, ) -> io::Result<(LocalBox, usize)> { let mut localboxed_string = ptr::null_mut(); let mut buflen = 0; unsafe { ConvertSecurityDescriptorToStringSecurityDescriptorW( sd.cast_mut(), SDDL_REVISION_1, selector, &mut localboxed_string, &mut buflen, ) } .true_val_or_errno(())?; Ok((unsafe { LocalBox::from_raw(localboxed_string.cast()) }, buflen.to_usize())) } pub(super) fn deserialize(sdsf: &U16CStr) -> io::Result> { let mut srsd = ptr::null_mut(); let mut buflen = 0; unsafe { ConvertStringSecurityDescriptorToSecurityDescriptorW( sdsf.as_ptr(), SDDL_REVISION_1, &mut srsd, &mut buflen, ) } .true_val_or_errno(())?; Ok(unsafe { LocalBox::from_raw(srsd) }) } interprocess-2.2.3/src/os/windows/security_descriptor/ext.rs000064400000000000000000000261351046102023000225340ustar 00000000000000use { super::{c_wrappers, AsSecurityDescriptor, AsSecurityDescriptorMut, SecurityDescriptor}, std::{ffi::c_void, io}, widestring::U16CStr, windows_sys::Win32::Security::{ GetSecurityDescriptorDacl, GetSecurityDescriptorGroup, GetSecurityDescriptorOwner, GetSecurityDescriptorSacl, SetSecurityDescriptorDacl, SetSecurityDescriptorGroup, SetSecurityDescriptorOwner, SetSecurityDescriptorSacl, ACL, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR_CONTROL, }, }; #[rustfmt::skip] macro_rules! indirect_methods { (@ $doc:literal $nm:ident acl $wfn:ident) => { #[doc = concat!("\ Returns a raw pointer to the contained ", $doc, " access control list, as well as the value of its corresponding \"defaulted\" boolean flag used to denote automatically generated ACLs.")] #[inline] fn $nm(&self) -> io::Result> { unsafe { c_wrappers::acl(self.as_sd(), $wfn) } } }; (@ $doc:literal $nm:ident sid $wfn:ident) => { #[doc = concat!("\ Returns a raw pointer to the contained ", $doc, " SID, as well as the value of its corresponding \"defaulted\" boolean flag used to denote SIDs that were chosen automatically.")] #[inline] fn $nm(&self) -> io::Result<(*const c_void, bool)> { unsafe { c_wrappers::sid(self.as_sd(), $wfn) } } }; (@ $doc:literal $nm:ident unset_acl $wfn:ident) => { #[doc = concat!("\ Marks the security descriptor as not containing the ", $doc, " access control list. If one was previously present, its memory is not reclaimed.")] #[inline] fn $nm(&mut self) -> io::Result<()> { unsafe { c_wrappers::unset_acl(self.as_sd(), $wfn) } } }; (@ $doc:literal $nm:ident unset_sid $wfn:ident) => { #[doc = concat!("\ Marks the security descriptor as not containing the ", $doc, " SID. If one was previously present, its memory is not reclaimed.")] #[inline] fn $nm(&mut self) -> io::Result<()> { unsafe { c_wrappers::unset_sid(self.as_sd(), $wfn) } } }; (@ $doc:literal $nm:ident set_acl $wfn:ident) => { #[doc = concat!("\ Sets the ", $doc, " access control list to the specified value, assuming ownership on the [local heap][lh]. If `defaulted` is `true`, the ", $doc, " access control list is marked as having been produced by some default mechanism. This is only used for internal program logic and is not checked by Windows. Note that, for DACLs, a null ACL (`ptr::null_mut()`) is not the same as an unset/absent ACL: it actually provides ***full access*** for every security principal. # Safety The pointer, *if not null*: - must point to a well-initialized ACL; - must not be owned elsewhere; - must be valid for deallocation with `LocalFree()`. [lh]: https://learn.microsoft.com/en-us/windows/win32/memory/global-and-local-functions ")] #[doc(hidden)] #[inline] unsafe fn $nm(&mut self, acl: *mut ACL, defaulted: bool) -> io::Result<()> { unsafe { c_wrappers::set_acl(self.as_sd(), Some(acl), defaulted, $wfn) } } }; (@ $doc:literal $nm:ident set_sid $wfn:ident) => { #[doc = concat!("\ Sets the ", $doc, " SID to the specified value, assuming ownership on the [local heap][lh]. A null pointer is not accepted as a sentinel for the lack of a ", $doc, " SID. See the corresponding unsetter in [`AsSecurityDescriptorMutExt`]. If `defaulted` is `true`, the ", $doc, " SID is marked as having been produced by some default mechanism. This is only used for internal program logic and is not checked by Windows. # Safety The pointer: - must point to a well-initialized SID; - must not be owned elsewhere; - must be valid for deallocation with `LocalFree()`. [lh]: https://learn.microsoft.com/en-us/windows/win32/memory/global-and-local-functions ")] #[inline] unsafe fn $nm(&mut self, sid: *mut c_void, defaulted: bool) -> io::Result<()> { if sid.is_null() { return Err( io::Error::new( io::ErrorKind::InvalidInput, concat!( "set_", $doc, " is the wrong function to use for unsetting the SID – use unset_", $doc, " instead" ), ) ) } unsafe { c_wrappers::set_sid(self.as_sd(), sid, defaulted, $wfn) } } }; (@ $doc:literal $nm:ident remove_acl [$unset:ident] $get:ident) => { #[doc = concat!("\ Deallocates the ", $doc, " access control list and marks it as not present in the security descriptor. The security descriptor remains valid after this operation. If the ", $doc, " access control list is not present, succeeds with no effect. # Errors Same as [`.free_contents()`](Self::free_contents).")] #[inline] fn $nm(&mut self) -> io::Result<()> { let val = self.$get()?; self.$unset()?; if let Some((val, _)) = val { unsafe { c_wrappers::free_acl(val.cast_mut())? }; } Ok(()) } }; (@ $doc:literal $nm:ident remove_sid [$unset:ident] $get:ident) => { #[doc = concat!("\ Deallocates the ", $doc, " SID and marks it as not present in the security descriptor. The security descriptor remains valid after this operation. If the ", $doc, " SID is not present, succeeds with no effect. # Errors Same as [`.free_contents()`](Self::free_contents).")] #[inline] fn $nm(&mut self) -> io::Result<()> { let val = self.$get()?; self.$unset()?; unsafe { c_wrappers::free_sid(val.0.cast_mut())? }; Ok(()) } }; ($($doc:literal $nm:ident $cat:ident $([$unset:ident])? $wfn:ident)+) => {$( indirect_methods!(@ $doc $nm $cat $([$unset])? $wfn); )+}; } /// Methods derived from the interface of [`AsSecurityDescriptor`]. pub trait AsSecurityDescriptorExt: AsSecurityDescriptor { indirect_methods! { "DACL" dacl acl GetSecurityDescriptorDacl "SACL" sacl acl GetSecurityDescriptorSacl "owner" owner sid GetSecurityDescriptorOwner "group" group sid GetSecurityDescriptorGroup } /// Returns the [control bits][cb] of the security descriptor and its revision number. /// /// [cb]: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-control fn control_and_revision(&self) -> io::Result<(SECURITY_DESCRIPTOR_CONTROL, u32)> { unsafe { c_wrappers::control_and_revision(self.as_sd()) } } /// Clones the security descriptor, producing a new [owned one](SecurityDescriptor). /// /// This is aliased to [`TryClone`](crate::TryClone) on [`SecurityDescriptor`] itself. fn to_owned_sd(&self) -> io::Result { unsafe { super::clone(self.as_sd()) } } /// Sets the security descriptor pointer of the given `SECURITY_ATTRIBUTES` structure to the /// security descriptor borrow of `self`. fn write_to_security_attributes(&self, attributes: &mut SECURITY_ATTRIBUTES) { attributes.lpSecurityDescriptor = self.as_sd().cast_mut(); } /// Serializes the security descriptor into [the security descriptor string format][sdsf] for /// debug printing, textual storage and safe interchange. /// /// The [`selector` bitflags][secinfo] determine the information that is serialized. This is /// primarily useful for deserialization, since not all of the information that can be stored /// in the string representation can be set without special permissions. /// /// The result is returned by passing it by-reference to the specified closure. This is because /// the slice is written to a `LocalAlloc()`-allocated buffer. Because there isn't a `LocalBox` /// in the public API of Interprocess for the sake of simplicity, returning the buffer raw would /// be prone to memory leaks. /// /// [sdsf]: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format /// [secinfo]: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-information fn serialize(&self, selector: u32, f: impl FnOnce(&U16CStr) -> R) -> io::Result { let (s, bsz) = unsafe { c_wrappers::serialize(self.as_sd(), selector)? }; let slice = unsafe { U16CStr::from_ptr_truncate(s.as_ptr(), bsz) }.map_err(io::Error::other)?; Ok(f(slice)) } } impl AsSecurityDescriptorExt for T {} /// Methods derived from the interface of [`AsSecurityDescriptorMut`]. pub trait AsSecurityDescriptorMutExt: AsSecurityDescriptorMut { indirect_methods! { "DACL" unset_dacl unset_acl SetSecurityDescriptorDacl "SACL" unset_sacl unset_acl SetSecurityDescriptorSacl "owner" unset_owner unset_sid SetSecurityDescriptorOwner "group" unset_group unset_sid SetSecurityDescriptorGroup "DACL" set_dacl set_acl SetSecurityDescriptorDacl "SACL" set_sacl set_acl SetSecurityDescriptorSacl "owner" set_owner set_sid SetSecurityDescriptorOwner "group" set_group set_sid SetSecurityDescriptorGroup "DACL" remove_dacl remove_acl [unset_dacl] dacl "SACL" remove_sacl remove_acl [unset_sacl] sacl "owner" remove_owner remove_sid [unset_owner] owner "group" remove_group remove_sid [unset_group] group } /// Modifies the [control bits][cb] of the security descriptor. The bits set to 1 in the mask /// are overridden with values from the corresponding places of the `value` argument. /// /// [cb]: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-control fn set_control( &mut self, mask: SECURITY_DESCRIPTOR_CONTROL, value: SECURITY_DESCRIPTOR_CONTROL, ) -> io::Result<()> { unsafe { c_wrappers::set_control(self.as_sd(), mask, value) } } /// Deallocates the DACL and the SACL, marking them as not present in the security descriptor. /// The security descriptor remains valid after this operation. /// /// # Errors /// Same as [`.free_contents()`](Self::free_contents). fn remove_acls(&mut self) -> io::Result<()> { self.remove_dacl()?; self.remove_sacl() } /// Deallocates the owner SID and the group SID, marking them as not present in the security /// descriptor. The security descriptor remains valid after this operation. /// /// # Errors /// Same as [`.free_contents()`](Self::free_contents). fn remove_sids(&mut self) -> io::Result<()> { self.remove_owner()?; self.remove_group() } /// Frees the ACLs and SIDs pointed to by the security descriptor. The security descriptor /// remains valid after this operation. /// /// # Errors /// If an error was returned, memory has not been reclaimed. This indicates a likely bug in the /// program. Since the error is non-critical, it might make sense to log it instead of panicking /// right away. fn free_contents(&mut self) -> io::Result<()> { self.remove_acls()?; self.remove_sids() } } impl AsSecurityDescriptorMutExt for T {} interprocess-2.2.3/src/os/windows/security_descriptor/owned.rs000064400000000000000000000112601046102023000230410ustar 00000000000000use { super::*, crate::{AsMutPtr, AsPtr, DebugExpectExt, OrErrno, TryClone}, std::{ fmt::{self, Debug, Formatter}, mem::MaybeUninit, }, widestring::U16CStr, windows_sys::Win32::{ Security::{InitializeSecurityDescriptor, SECURITY_DESCRIPTOR, SE_SELF_RELATIVE}, System::SystemServices::SECURITY_DESCRIPTOR_REVISION, }, }; /// [Security descriptor][sd] in [absolute format][abs], stored by-value with ownership of all /// contained ACLs and SIDs on the [local heap][lh]. /// /// Consult Microsoft Learn for [an example][ex] of how to correctly create a security descriptor. /// /// [sd]: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor /// [abs]: https://learn.microsoft.com/en-us/windows/win32/secauthz/absolute-and-self-relative-security-descriptors /// [ex]: https://learn.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c-- /// [lh]: https://learn.microsoft.com/en-us/windows/win32/memory/global-and-local-functions #[repr(C)] pub struct SecurityDescriptor(SECURITY_DESCRIPTOR); /// Interior mutability is not provided through [`SecurityDescriptor`]. unsafe impl Sync for SecurityDescriptor {} unsafe impl Send for SecurityDescriptor {} unsafe impl AsSecurityDescriptor for SecurityDescriptor { #[inline(always)] fn as_sd(&self) -> *const c_void { self.as_ptr().cast() } } unsafe impl AsSecurityDescriptorMut for SecurityDescriptor { #[inline(always)] fn as_sd_mut(&mut self) -> *mut c_void { self.as_sd().cast_mut() } } /// Constructors. impl SecurityDescriptor { /// Creates a [default](Default) security descriptor. pub fn new() -> io::Result { let mut sd = MaybeUninit::::uninit(); unsafe { InitializeSecurityDescriptor(sd.as_mut_ptr().cast(), SECURITY_DESCRIPTOR_REVISION) .true_or_errno(|| // SAFETY: InitializeSecurityDescriptor() creates a well-initialized absolute SD Self::from_owned(sd.assume_init())) } } /// Deserializes a security descriptor from the [security descriptor string format][sdsf]. /// /// [sdsf]: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format pub fn deserialize(sdsf: &U16CStr) -> io::Result { let srsd = c_wrappers::deserialize(sdsf)?; unsafe { BorrowedSecurityDescriptor::from_ptr(srsd.as_ptr()) }.to_owned_sd() } /// Wraps the given security descriptor, assuming ownership. /// /// # Safety /// - The security descriptor must be [absolute][abs], not self-relative. /// - The security descriptor must *own* all of its contents. /// - The [safety constraints](AsSecurityDescriptor#safety-constraints) must be upheld. /// /// [abs]: https://learn.microsoft.com/en-us/windows/win32/secauthz/absolute-and-self-relative-security-descriptors #[inline(always)] pub unsafe fn from_owned(mut sd: SECURITY_DESCRIPTOR) -> Self { debug_assert!( unsafe { c_wrappers::control_and_revision(sd.as_ptr().cast()) .expect("failed to verify that security descriptor is not self-relative") .0 & SE_SELF_RELATIVE == 0 }, "self-relative security descriptor not allowed here" ); unsafe { validate(sd.as_mut_ptr().cast()); } Self(sd) } } impl Default for SecurityDescriptor { fn default() -> Self { Self::new().expect("could not default-initialize security descriptor") } } impl TryClone for SecurityDescriptor { #[inline] fn try_clone(&self) -> io::Result { unsafe { super::clone(self.as_sd()) } } } /// Borrowing. impl SecurityDescriptor { /// Borrows immutably. The returned type is also a safe wrapper around security descriptors. #[inline(always)] pub fn borrow(&self) -> BorrowedSecurityDescriptor<'_> { unsafe { BorrowedSecurityDescriptor::from_ptr(self.as_ptr().cast()) } } /// Borrows mutably. The returned type is also a safe wrapper around security descriptors. #[inline(always)] pub fn borrow_mut(&mut self) -> MutBorrowedSecurityDescriptor<'_> { unsafe { MutBorrowedSecurityDescriptor::from_ptr(self.as_mut_ptr().cast()) } } } impl Debug for SecurityDescriptor { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_tuple("SecurityDescriptor").field(&(self.as_ptr())).finish() } } impl Drop for SecurityDescriptor { fn drop(&mut self) { self.free_contents().debug_expect("failed to free memory owned by security descriptor"); } } interprocess-2.2.3/src/os/windows/security_descriptor/try_clone.rs000064400000000000000000000144321046102023000237270ustar 00000000000000use { super::*, crate::{AsMutPtr, OrErrno}, std::{ marker::PhantomData, mem::{size_of, size_of_val, zeroed, ManuallyDrop}, ptr::{self, NonNull}, }, windows_sys::Win32::{ Foundation::LocalFree, Security::{ AclRevisionInformation, AclSizeInformation, AddAce, CopySid, GetAce, GetAclInformation, GetSidLengthRequired, GetSidSubAuthorityCount, InitializeAcl, IsValidSid, ACE_HEADER, ACL, ACL_INFORMATION_CLASS, ACL_REVISION_INFORMATION, ACL_SIZE_INFORMATION, SECURITY_DESCRIPTOR_CONTROL, SE_DACL_PROTECTED, SE_SACL_PROTECTED, }, System::{ Memory::{LocalAlloc, LMEM_FIXED}, SystemServices::MAXDWORD, }, }, }; pub(super) unsafe fn clone(sd: *const c_void) -> io::Result { // Those are the only ones that can be set with SetSecurityDescriptorControl(). const CONTROL_MASK: SECURITY_DESCRIPTOR_CONTROL = SE_DACL_PROTECTED | SE_SACL_PROTECTED; let mut new_sd = SecurityDescriptor::new()?; let old_sd = unsafe { // SAFETY: as per contract BorrowedSecurityDescriptor::from_ptr(sd) }; let clnsid = |(sid, dfl)| { io::Result::<(Option>, bool)>::Ok((unsafe { clone_sid(sid)? }, dfl)) }; let dacl = old_sd.dacl()?; let sacl = old_sd.sacl()?; let owner = clnsid(old_sd.owner()?)?; let group = clnsid(old_sd.group()?)?; if let Some((acl, dfl)) = dacl { if acl.is_null() { unsafe { new_sd.set_dacl(ptr::null_mut(), dfl)? }; } else { let mut acl = ManuallyDrop::new(unsafe { clone_acl(acl)? }); unsafe { new_sd.set_dacl((*acl).as_mut_ptr(), dfl)? }; } } if let Some((acl, dfl)) = sacl { if acl.is_null() { unsafe { new_sd.set_sacl(ptr::null_mut(), dfl)? }; } else { let mut acl = ManuallyDrop::new(unsafe { clone_acl(acl)? }); unsafe { new_sd.set_sacl((*acl).as_mut_ptr(), dfl)? }; } } let assid = |sid: &mut LocalBox| sid.as_mut_ptr(); let (mut owner, odfl) = (ManuallyDrop::new(owner.0), owner.1); if let Some(owner) = owner.as_mut().map(assid) { unsafe { new_sd.set_owner(owner, odfl)? }; } let (mut group, gdfl) = (ManuallyDrop::new(group.0), group.1); if let Some(group) = group.as_mut().map(assid) { unsafe { new_sd.set_owner(group, gdfl)? }; } let control = old_sd.control_and_revision()?.0; new_sd.set_control(CONTROL_MASK, control & CONTROL_MASK)?; Ok(new_sd) } pub(crate) struct LocalBox(NonNull, PhantomData); impl LocalBox { #[allow(clippy::unwrap_used, clippy::unwrap_in_result)] pub fn allocate(sz: u32) -> io::Result { // Unwrap note: this code isn't supposed to compile on Win16. let allocation = unsafe { LocalAlloc(LMEM_FIXED, sz.try_into().unwrap()) }; (allocation.is_null()).false_or_errno(|| unsafe { Self(NonNull::new_unchecked(allocation.cast()), PhantomData) }) } #[inline] pub fn as_ptr(&self) -> *const T { self.0.as_ptr().cast_const() } #[inline] pub fn as_mut_ptr(&mut self) -> *mut T { self.0.as_ptr() } #[inline] pub unsafe fn from_raw(raw: *mut T) -> Self { unsafe { Self(NonNull::new_unchecked(raw), PhantomData) } } } impl Drop for LocalBox { fn drop(&mut self) { unsafe { LocalFree(self.as_mut_ptr().cast()) } .is_null() .true_val_or_errno(()) .expect("LocalFree() failed") } } /// Wraps `GetAclInformation()`. /// /// # Safety /// - `zeroed::()` must be POD, i.e. all bit patterns of `T`'s size must constitute /// well-initialized instances of `T`. /// - `T` must be the correct size for `information_class`. unsafe fn get_acl_info( acl: *const ACL, information_class: ACL_INFORMATION_CLASS, ) -> io::Result { let mut info = unsafe { zeroed::() }; unsafe { GetAclInformation( acl.cast_mut(), info.as_mut_ptr().cast(), size_of_val(&info).try_into().unwrap(), information_class, ) .true_val_or_errno(info) } } #[allow(clippy::unwrap_used, clippy::as_conversions)] fn create_acl(sz: u32, rev: u32) -> io::Result> { const ALIGN: u32 = size_of::() as u32; // 100₂ const ALIGN_MASK: u32 = ALIGN - 1; // 011₂ let sz = if sz & ALIGN_MASK != 0 { // It's not possible for the allocated size of an ACL to exceed DWORD::MAX, and it's also // not possible for the upward-aligned bytes-in-use figure to exceed the allocated size. sz.checked_add(1).unwrap() } else { sz }; let mut acl = LocalBox::allocate(sz)?; unsafe { InitializeAcl(acl.as_mut_ptr(), sz, rev) }.true_val_or_errno(acl) } unsafe fn clone_acl(acl: *const ACL) -> io::Result> { let (sz_info, rev) = unsafe { let sz_info = get_acl_info::(acl, AclSizeInformation)?; let rev = get_acl_info::(acl, AclRevisionInformation)?.AclRevision; (sz_info, rev) }; let mut new_acl = create_acl(sz_info.AclBytesInUse, rev)?; unsafe { let mut ace = ptr::null_mut(); for i in 0..sz_info.AceCount { GetAce(acl, i, &mut ace).true_val_or_errno(())?; AddAce( new_acl.as_mut_ptr(), rev, MAXDWORD, ace.cast_const(), (*ace.cast_const().cast::()).AceSize.into(), ) .true_val_or_errno(())?; } } Ok(new_acl) } unsafe fn clone_sid(sid: *const c_void) -> io::Result>> { if sid.is_null() { // Unlike with ACLs, a null PSID is a sentinel for the lack of a SID. By analogy with // `None.clone() == None`, we return the same value. return Ok(None); } let sid = sid.cast_mut(); unsafe { IsValidSid(sid) }.true_val_or_errno(())?; let num_subauths = unsafe { *GetSidSubAuthorityCount(sid) }; let sz = unsafe { GetSidLengthRequired(num_subauths) }; let mut new_sid = LocalBox::allocate(sz)?; unsafe { CopySid(sz, new_sid.as_mut_ptr(), sid) }.true_val_or_errno(Some(new_sid)) } interprocess-2.2.3/src/os/windows/security_descriptor.rs000064400000000000000000000033311046102023000217250ustar 00000000000000//! Lightweight safety layer for working with security descriptors. //! //! Constructing Windows security descriptors can get complicated, and is non-trivial on a //! conceptual level, making it largely outside the scope of Interprocess. To help you roll your own //! security descriptor handling or get help from a different crate, this module provides security //! descriptor primitives that have an emphasis on composability. The most complicated facility is //! perhaps the implementation of [`TryClone`](crate::TryClone) for [`SecurityDescriptor`], and even //! that is mostly boilerplate written in accordance to official Windows documentation. mod as_security_descriptor; mod borrowed; mod c_wrappers; mod ext; mod owned; mod try_clone; #[allow(unused_imports)] // this is literally a false positive pub(crate) use try_clone::LocalBox; use { crate::BoolExt, std::{ffi::c_void, io}, try_clone::clone, windows_sys::Win32::Security::{IsValidSecurityDescriptor, SECURITY_ATTRIBUTES}, }; pub use {as_security_descriptor::*, borrowed::*, ext::*, owned::*}; unsafe fn validate(ptr: *mut c_void) { unsafe { debug_assert!( IsValidSecurityDescriptor(ptr) == 1, "invalid security descriptor: {}", io::Error::last_os_error(), ); } } pub(super) fn create_security_attributes( sd: Option>, inheritable: bool, ) -> SECURITY_ATTRIBUTES { let mut attrs = unsafe { std::mem::zeroed::() }; if let Some(sd) = sd { sd.write_to_security_attributes(&mut attrs); } attrs.nLength = std::mem::size_of::().try_into().unwrap(); attrs.bInheritHandle = inheritable.to_i32(); attrs } interprocess-2.2.3/src/os/windows/share_handle.rs000064400000000000000000000040101046102023000202300ustar 00000000000000use { super::{c_wrappers, winprelude::*}, std::io, }; /// Objects which own handles which can be shared with other processes. /// /// On Windows, like on most other operating systems, handles belong to specific processes. You /// shouldn't just send the value of a handle to another process (with a named pipe, for example) /// and expect it to work on the other side. For this to work, you need /// [`DuplicateHandle`](windows_sys::Win32::Foundation::DuplicateHandle) – the Win32 API function /// which duplicates a handle into the handle table of the specified process (the receiver is /// referred to by its handle). This trait exposes the `DuplicateHandle` functionality in a safe /// manner. /// /// Note that the resulting handle is expected not to be inheritable. It is a logic error to have /// the output of `.share()` be inheritable, but it is not UB. /// /// **Implemented for all types inside this crate which implement [`AsHandle`] and are supposed to /// be shared between processes.** pub trait ShareHandle: AsHandle { /// Duplicates the handle to make it accessible in the specified process (taken as a handle to /// that process) and returns the raw value of the handle which can then be sent via some form /// of IPC, typically named pipes. This is the only way to use any form of IPC other than named /// pipes to communicate between two processes which do not have a parent-child relationship or /// if the handle wasn't created as inheritable. /// /// Backed by [`DuplicateHandle`](windows_sys::Win32::Foundation::DuplicateHandle). Doesn't /// require unsafe code since `DuplicateHandle` never leads to undefined behavior if the /// `lpTargetHandle` parameter is a valid pointer, only creates an error. fn share(&self, receiver: BorrowedHandle<'_>) -> io::Result { c_wrappers::duplicate_handle_to_foreign(self.as_handle(), receiver).map(HANDLE::to_std) } } impl ShareHandle for crate::unnamed_pipe::Recver {} impl ShareHandle for crate::unnamed_pipe::Sender {} interprocess-2.2.3/src/os/windows/tokio_flusher.rs000064400000000000000000000060331046102023000204770ustar 00000000000000use { crate::{ os::windows::{winprelude::*, FileHandle, NeedsFlush}, UnpinExt, LOCK_POISON, }, std::{ future::{self, Future}, io, sync::{atomic::Ordering::*, Mutex}, task::{ready, Context, Poll}, }, tokio::task::JoinHandle, }; type FlushJH = JoinHandle>; /// Wraps `FlushFileBuffers()` ran in a `spawn_blocking()` task into a poll interface. #[derive(Debug)] pub struct TokioFlusher { join_handle: Mutex>, } impl TokioFlusher { pub(crate) const fn new() -> Self { Self { join_handle: Mutex::new(None) } } #[inline] pub(crate) async fn flush_atomic( &self, file_handle: BorrowedHandle<'_>, needs_flush: &NeedsFlush, ) -> io::Result<()> { future::poll_fn(|cx| self.poll_flush_atomic(file_handle, needs_flush, cx)).await } pub(crate) fn poll_flush_atomic( &self, file_handle: BorrowedHandle<'_>, needs_flush: &NeedsFlush, cx: &mut Context<'_>, ) -> Poll> { if !needs_flush.get(Acquire) { // Idempotency optimization — don't flush unless there have been unflushed writes return Poll::Ready(Ok(())); } let mut flush = self.join_handle.lock().expect(LOCK_POISON); // The mutex is an acquire fence, so this load can safely be relaxed (it can actually be // non-atomic in practice, but there's hardly a performance benefit to that) if !needs_flush.get(Relaxed) { // Lock losering – don't flush if a different thread beat us to the lock return Poll::Ready(Ok(())); } let jh = Self::ensure_flush_start(&mut flush, file_handle); let rslt = ready!(jh.pin().poll(cx)).unwrap(); if rslt.is_ok() { needs_flush.clear(); } *flush = None; Poll::Ready(rslt) } pub(crate) fn poll_flush_mut( &self, file_handle: BorrowedHandle<'_>, needs_flush: &mut bool, cx: &mut Context<'_>, ) -> Poll> { if !*needs_flush { // Idempotency optimization — don't flush unless there have been unflushed writes return Poll::Ready(Ok(())); } let mut flush = self.join_handle.lock().expect(LOCK_POISON); let jh = Self::ensure_flush_start(&mut flush, file_handle); let rslt = ready!(jh.pin().poll(cx)).unwrap(); if rslt.is_ok() { *needs_flush = false; } *flush = None; Poll::Ready(rslt) } fn ensure_flush_start<'opt>( join_handle: &'opt mut Option, file_handle: BorrowedHandle<'_>, ) -> &'opt mut FlushJH { if let Some(jh) = join_handle { return jh; } let handle = file_handle.as_int_handle(); let task = tokio::task::spawn_blocking(move || FileHandle::flush_hndl(handle)); join_handle.insert(task) } } impl Default for TokioFlusher { #[inline] fn default() -> Self { Self::new() } } interprocess-2.2.3/src/os/windows/unnamed_pipe/tokio.rs000064400000000000000000000106431046102023000214150ustar 00000000000000//! Windows-specific functionality for Tokio-based unnamed pipes. use { crate::{ os::windows::{ limbo::{ tokio::{send_off, Corpse}, LIMBO_ERR, REBURY_ERR, }, tokio_flusher::TokioFlusher, unnamed_pipe::CreationOptions, winprelude::*, }, unnamed_pipe::{ tokio::{Recver as PubRecver, Sender as PubSender}, Recver as SyncRecver, Sender as SyncSender, }, Sealed, UnpinExt, }, std::{ io, mem::ManuallyDrop, pin::Pin, task::{ready, Context, Poll}, }, tokio::{fs::File, io::AsyncWrite}, }; static INFLIGHT_ERR: &str = "cannot deregister unnamed pipe from the Tokio runtime with in-flight operations"; fn pair2pair((tx, rx): (SyncSender, SyncRecver)) -> io::Result<(PubSender, PubRecver)> { Ok((PubSender(tx.try_into()?), PubRecver(rx.try_into()?))) } #[inline] pub(crate) fn pipe_impl() -> io::Result<(PubSender, PubRecver)> { pair2pair(super::pipe_impl()?) } /// Tokio-specific extensions to [`CreationOptions`]. #[allow(private_bounds)] pub trait CreationOptionsExt: Sealed { /// Creates a Tokio-based unnamed pipe and returns its sending and receiving ends, or an error /// if one occurred. fn create_tokio(self) -> io::Result<(PubSender, PubRecver)>; } impl CreationOptionsExt for CreationOptions<'_> { #[inline] fn create_tokio(self) -> io::Result<(PubSender, PubRecver)> { pair2pair(self.create()?) } } #[derive(Debug)] pub(crate) struct Recver(File); impl TryFrom for Recver { type Error = io::Error; #[inline] fn try_from(rx: SyncRecver) -> io::Result { Self::try_from(OwnedHandle::from(rx.0)) } } impl TryFrom for OwnedHandle { type Error = io::Error; fn try_from(rx: Recver) -> io::Result { rx.0.try_into_std() .map(OwnedHandle::from) .map_err(|_| io::Error::other(INFLIGHT_ERR)) } } impl TryFrom for Recver { type Error = io::Error; fn try_from(handle: OwnedHandle) -> io::Result { Ok(Self(File::from_std(handle.into()))) } } multimacro! { Recver, pinproj_for_unpin(File), forward_tokio_read, forward_as_handle, } #[derive(Debug)] pub(crate) struct Sender { io: Option, flusher: TokioFlusher, needs_flush: bool, } impl AsyncWrite for Sender { #[inline] fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.needs_flush = true; let rslt = ready!(self.io.as_mut().expect(LIMBO_ERR).pin().poll_write(cx, buf)); if rslt.is_err() { self.needs_flush = false; } Poll::Ready(rslt) } #[inline] fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { // Unnamed pipes on Unix can't be flushed Poll::Ready(Ok(())) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // For limbo elision given cooperative downstream let slf = self.get_mut(); slf.flusher.poll_flush_mut( slf.io.as_ref().expect(LIMBO_ERR).as_handle(), &mut slf.needs_flush, cx, ) } } impl Drop for Sender { fn drop(&mut self) { let corpse = Corpse::Unnamed(self.io.take().expect(REBURY_ERR)); if self.needs_flush { send_off(corpse); } } } impl TryFrom for Sender { type Error = io::Error; #[inline] fn try_from(rx: SyncSender) -> io::Result { Self::try_from(OwnedHandle::from(rx.0)) } } impl TryFrom for OwnedHandle { type Error = io::Error; fn try_from(tx: Sender) -> io::Result { ManuallyDrop::new(tx) .io .take() .expect(LIMBO_ERR) .try_into_std() .map(OwnedHandle::from) .map_err(|_| io::Error::other(INFLIGHT_ERR)) } } impl TryFrom for Sender { type Error = io::Error; fn try_from(handle: OwnedHandle) -> io::Result { Ok(Self { io: Some(File::from_std(handle.into())), flusher: TokioFlusher::new(), needs_flush: true, }) } } impl AsHandle for Sender { fn as_handle(&self) -> BorrowedHandle<'_> { self.io.as_ref().expect(LIMBO_ERR).as_handle() } } interprocess-2.2.3/src/os/windows/unnamed_pipe.rs000064400000000000000000000137051046102023000202720ustar 00000000000000//! Windows-specific functionality for unnamed pipes. #[cfg(feature = "tokio")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "tokio")))] pub mod tokio; use { crate::{ os::windows::{ limbo::{ sync::{send_off, Corpse}, LIMBO_ERR, REBURY_ERR, }, security_descriptor::*, winprelude::*, FileHandle, }, unnamed_pipe::{Recver as PubRecver, Sender as PubSender}, weaken_buf_init_mut, AsPtr, Sealed, TryClone, }, std::{ fmt::{self, Debug, Formatter}, io::{self, Read, Write}, mem::ManuallyDrop, num::NonZeroUsize, }, windows_sys::Win32::System::Pipes::CreatePipe, }; /// Builder used to create unnamed pipes while supplying additional options. /// /// You can use this instead of the simple [`pipe` function](crate::unnamed_pipe::pipe) to supply /// additional Windows-specific parameters to a pipe. #[non_exhaustive] #[derive(Clone, Debug)] pub struct CreationOptions<'sd> { /// Security descriptor for the pipe. pub security_descriptor: Option>, /// Specifies whether the resulting pipe can be inherited by child processes. /// /// The default value is `true`. pub inheritable: bool, /// Hint on the buffer size for the pipe. There is no way to ensure or check that the system /// actually uses this exact size, since it's only a hint. Set to `None` to disable the hint /// and rely entirely on the system's default buffer size. pub buffer_size_hint: Option, } impl Sealed for CreationOptions<'_> {} impl<'sd> CreationOptions<'sd> { /// Starts with the default parameters for the pipe. Identical to `Default::default()`. pub const fn new() -> Self { Self { inheritable: false, security_descriptor: None, buffer_size_hint: None } } builder_setters! { /// Specifies the pointer to the security descriptor for the pipe. /// /// See the [associated field](#structfield.security_descriptor) for more. security_descriptor: Option>, /// Specifies whether the resulting pipe can be inherited by child processes. /// /// See the [associated field](#structfield.inheritable) for more. inheritable: bool, /// Provides Windows with a hint for the buffer size for the pipe. /// /// See the [associated field](#structfield.buffer_size_hint) for more. buffer_size_hint: Option, } /// Creates the pipe and returns its sending and receiving ends, or an error if one occurred. pub fn create(self) -> io::Result<(PubSender, PubRecver)> { let hint_raw = match self.buffer_size_hint { Some(num) => num.get(), None => 0, } .try_into() .unwrap(); let sd = create_security_attributes(self.security_descriptor, self.inheritable); let [mut w, mut r] = [INVALID_HANDLE_VALUE; 2]; let success = unsafe { CreatePipe(&mut r, &mut w, sd.as_ptr().cast_mut().cast(), hint_raw) } != 0; if success { let (w, r) = unsafe { // SAFETY: we just created those handles which means that we own them let w = OwnedHandle::from_raw_handle(w.to_std()); let r = OwnedHandle::from_raw_handle(r.to_std()); (w, r) }; let w = PubSender(Sender { io: Some(FileHandle::from(w)), needs_flush: false }); let r = PubRecver(Recver(FileHandle::from(r))); Ok((w, r)) } else { Err(io::Error::last_os_error()) } } /// Synonymous with [`.create()`](Self::create). #[inline] pub fn build(self) -> io::Result<(PubSender, PubRecver)> { self.create() } } impl Default for CreationOptions<'_> { fn default() -> Self { Self::new() } } pub(crate) fn pipe_impl() -> io::Result<(PubSender, PubRecver)> { CreationOptions::default().build() } pub(crate) struct Recver(FileHandle); impl Read for Recver { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(weaken_buf_init_mut(buf)) } } impl Debug for Recver { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_tuple("Recver").field(&self.0.as_raw_handle()).finish() } } multimacro! { Recver, forward_handle, forward_try_clone, } #[derive(Debug)] pub(crate) struct Sender { io: Option, needs_flush: bool, } impl Write for Sender { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { let rslt = self.io.as_mut().expect(LIMBO_ERR).write(buf); if rslt.is_ok() { self.needs_flush = true; } rslt } #[inline] fn flush(&mut self) -> io::Result<()> { if self.needs_flush { let rslt = self.io.as_mut().expect(LIMBO_ERR).flush(); if rslt.is_ok() { self.needs_flush = false; } rslt } else { Ok(()) } } } impl Drop for Sender { fn drop(&mut self) { let corpse = Corpse { handle: self.io.take().expect(REBURY_ERR), is_server: false }; if self.needs_flush { send_off(corpse); } } } impl TryClone for Sender { fn try_clone(&self) -> io::Result { Ok(Self { io: self.io.as_ref().map(TryClone::try_clone).transpose()?, needs_flush: self.needs_flush, }) } } impl AsHandle for Sender { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { self.io.as_ref().map(AsHandle::as_handle).expect(LIMBO_ERR) } } impl From for Sender { #[inline] fn from(handle: OwnedHandle) -> Self { Self { io: Some(handle.into()), needs_flush: true } } } impl From for OwnedHandle { #[inline] fn from(tx: Sender) -> Self { ManuallyDrop::new(tx).io.take().expect(LIMBO_ERR).into() } } interprocess-2.2.3/src/os/windows.rs000064400000000000000000000015551046102023000156260ustar 00000000000000//! Windows-specific functionality for various interprocess communication primitives, as well as //! Windows-specific ones. pub mod local_socket; pub mod named_pipe; pub mod security_descriptor; pub mod unnamed_pipe; //pub mod mailslot; mod impersonation_guard; mod path_conversion; mod share_handle; pub use {impersonation_guard::*, path_conversion::*, share_handle::*}; mod file_handle; mod limbo_pool; pub(crate) mod misc; mod needs_flush; #[cfg(feature = "tokio")] mod tokio_flusher; mod limbo { pub(super) mod sync; #[cfg(feature = "tokio")] pub(super) mod tokio; pub(crate) static LIMBO_ERR: &str = "attempt to perform operation on pipe stream which has been sent off to limbo"; pub(crate) static REBURY_ERR: &str = "attempt to bury same pipe stream twice"; } pub(crate) use {file_handle::*, misc::*, needs_flush::*}; mod c_wrappers; interprocess-2.2.3/src/platform_check.rs000064400000000000000000000011621046102023000164660ustar 00000000000000#[cfg(any(not(any(windows, unix)), target_os = "emscripten"))] compile_error!( "Your target operating system is not supported by interprocess – check if yours is in the list \ of supported systems, and if not, please open an issue on the GitHub repository if you think that \ it should be included" ); #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] compile_error!( "Platforms with exotic pointer widths (neither 32-bit nor 64-bit) are not supported by \ interprocess – if you think that your specific case needs to be accounted for, please open an \ issue on the GitHub repository" ); interprocess-2.2.3/src/try_clone.rs000064400000000000000000000011411046102023000155000ustar 00000000000000/// Fallible OS object cloning. /// /// The `DuplicateHandle`/`dup` system calls can fail for a variety of reasons, most of them being /// related to system resource exhaustion. This trait is implemented by types in Interprocess which /// wrap OS objects (which is to say, the majority of types here) to enable handle/file descriptor /// duplication functionality on them. pub trait TryClone: Sized { /// Clones `self`, possibly returning an error. fn try_clone(&self) -> std::io::Result; } impl TryClone for T { fn try_clone(&self) -> std::io::Result { Ok(self.clone()) } } interprocess-2.2.3/src/unnamed_pipe/tokio.rs000064400000000000000000000050461046102023000173030ustar 00000000000000//! Tokio-based asynchronous unnamed pipes. //! //! See the [parent-level documentation](super) for more. //! //! # Examples //! See [`pipe()`]. impmod! {unnamed_pipe::tokio, Recver as RecverImpl, Sender as SenderImpl, pipe_impl, } use std::io; /// Creates a new pipe with the default creation settings and returns Tokio-based handles to its /// sending end and receiving end. /// /// The platform-specific builders in the `os` module of the crate might be more helpful if extra /// configuration for the pipe is needed. /// /// # Examples /// ## Basic communication /// In a parent process, within a Tokio runtime: /// ```no_run #[doc = doctest_file::include_doctest!("examples/unnamed_pipe/sync/side_a.rs")] /// ``` /// In a child process, within a Tokio runtime: /// ```no_run #[doc = doctest_file::include_doctest!("examples/unnamed_pipe/sync/side_b.rs")] /// ``` #[inline] pub fn pipe() -> io::Result<(Sender, Recver)> { pipe_impl() } /// Tokio-based handle to the receiving end of an unnamed pipe, created by the [`pipe()`] function /// together with the [sending end](Sender). /// /// The core functionality is exposed via the [`AsyncRead`](tokio::io::AsyncRead) trait. The type /// is convertible to and from handles/file descriptors and allows its internal handle/FD to be /// borrowed. On Windows, the `ShareHandle` trait is also implemented. /// /// The handle/file descriptor is inheritable. See [module-level documentation](self) for more on /// how this can be used. // field is pub(crate) to allow platform builders to create the public-facing pipe types pub struct Recver(pub(crate) RecverImpl); multimacro! { Recver, pinproj_for_unpin(RecverImpl), forward_tokio_read, forward_as_handle, forward_try_handle(io::Error), forward_debug, derive_asraw, } /// Handle to the sending end of an unnamed pipe, created by the [`pipe()`] function together with /// the [receiving end](Recver). /// /// The core functionality is exposed via the [`AsyncWrite`](tokio::io::AsyncWrite) trait. The /// type is convertible to and from handles/file descriptors and allows its internal handle/FD to /// be borrowed. On Windows, the `ShareHandle` trait is also implemented. /// /// The handle/file descriptor is inheritable. See [module-level documentation](self) for more on /// how this can be used. pub struct Sender(pub(crate) SenderImpl); multimacro! { Sender, pinproj_for_unpin(SenderImpl), forward_rbv(SenderImpl, &), forward_tokio_write, forward_as_handle, forward_try_handle(io::Error), forward_debug, derive_asraw, } interprocess-2.2.3/src/unnamed_pipe.rs000064400000000000000000000101641046102023000161530ustar 00000000000000//! Creation and usage of unnamed pipes. //! //! Unlike named pipes, unnamed pipes are only accessible through their handles – once an endpoint //! is closed, its corresponding end of the pipe is no longer accessible. Unnamed pipes typically //! work best when communicating with child processes. //! //! The handles and file descriptors are inheritable by default. The `AsRawHandle` and `AsRawFd` //! traits can be used to get a numeric handle value which can then be communicated to a child //! process using a command-line argument, environment variable or some other program startup IPC //! method. The numeric value can then be reconstructed into an I/O object using //! `FromRawHandle`/`FromRawFd`. Interprocess does not concern itself with how this is done. //! //! Note //! [the standard library's support for piping `stdin`, `stdout` and `stderr`](std::process::Stdio), //! which can be used in simple cases instead of unnamed pipes. Making use of that feature is //! advisable if the program of the child process can be modified to communicate with its parent //! via standard I/O streams. //! //! # Examples //! See [`pipe()`]. #[cfg(feature = "tokio")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "tokio")))] pub mod tokio; impmod! {unnamed_pipe, Recver as RecverImpl, Sender as SenderImpl, pipe_impl, } use {crate::Sealed, std::io}; /// Creates a new pipe with the default creation settings and returns the handles to its sending end /// and receiving end. /// /// The platform-specific builders in the `os` module of the crate might be more helpful if extra /// configuration for the pipe is needed. /// /// # Examples /// ## Basic communication /// In a parent process: /// ```no_run #[doc = doctest_file::include_doctest!("examples/unnamed_pipe/sync/side_a.rs")] /// ``` /// In a child process: /// ```no_run #[doc = doctest_file::include_doctest!("examples/unnamed_pipe/sync/side_b.rs")] /// ``` #[inline] pub fn pipe() -> io::Result<(Sender, Recver)> { pipe_impl() } /// Handle to the receiving end of an unnamed pipe, created by the [`pipe()`] function together /// with the [sending end](Sender). /// /// The core functionality is exposed via the [`Read`](io::Read) trait. The type is convertible to /// and from handles/file descriptors and allows its internal handle/FD to be borrowed. On /// Windows, the `ShareHandle` trait is also implemented. /// /// The handle/file descriptor is inheritable. See [module-level documentation](self) for more on /// how this can be used. // field is pub(crate) to allow platform builders to create the public-facing pipe types pub struct Recver(pub(crate) RecverImpl); impl Sealed for Recver {} multimacro! { Recver, forward_sync_read, forward_handle, forward_debug, derive_raw, } /// Handle to the sending end of an unnamed pipe, created by the [`pipe()`] function together with /// the [receiving end](Recver). /// /// The core functionality is exposed via the [`Write`](io::Write) trait. The type is convertible /// to and from handles/file descriptors and allows its internal handle/FD to be borrowed. On /// Windows, the `ShareHandle` trait is also implemented. /// /// The handle/file descriptor is inheritable. See [module-level documentation](self) for more on /// how this can be used. /// /// # Limbo /// On Windows, much like named pipes, unnamed pipes are subject to limbo, meaning that dropping /// an unnamed pipe does not immediately discard the contents of the send buffer. See the /// documentation on `named_pipe::PipeStream` for more. /// /// [ARH]: https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html /// [IRH]: https://doc.rust-lang.org/std/os/windows/io/trait.IntoRawHandle.html /// [`FromRawHandle`]: https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html /// [ARF]: https://doc.rust-lang.org/std/os/unix/io/trait.AsRawFd.html /// [IRF]: https://doc.rust-lang.org/std/os/unix/io/trait.IntoRawFd.html /// [`FromRawFd`]: https://doc.rust-lang.org/std/os/unix/io/trait.FromRawFd.html pub struct Sender(pub(crate) SenderImpl); impl Sealed for Sender {} multimacro! { Sender, forward_sync_write, forward_handle, forward_debug, derive_raw, } interprocess-2.2.3/tests/index.rs000064400000000000000000000006741046102023000151760ustar 00000000000000#[path = "util/mod.rs"] #[macro_use] mod util; mod os { #[cfg(unix)] mod unix { mod local_socket_fake_ns; mod local_socket_mode; } #[cfg(windows)] mod windows { mod local_socket_security_descriptor; mod named_pipe; mod tokio_named_pipe; } } mod local_socket; #[cfg(feature = "tokio")] mod tokio_local_socket; #[cfg(feature = "tokio")] mod tokio_unnamed_pipe; mod unnamed_pipe; interprocess-2.2.3/tests/local_socket/no_client.rs000064400000000000000000000020641046102023000204760ustar 00000000000000//! Tests what happens when a server attempts to listen for clients that never come. use { crate::{ local_socket::{prelude::*, ListenerNonblockingMode, ListenerOptions, Stream}, tests::util::*, }, color_eyre::eyre::{bail, ensure}, std::io, }; pub fn run_and_verify_error(id: &str, path: bool) -> TestResult { use io::ErrorKind::*; let err = match server(id, path) { Err(e) => e, Ok(c) => bail!("server successfully listened for a nonexistent client: {c:?}"), }; ensure!( matches!(err.kind(), WouldBlock), "expected error to be 'would block', received '{}'", err ); Ok(()) } fn server(id: &str, path: bool) -> io::Result { let listener = listen_and_pick_name(&mut namegen_local_socket(id, path), |nm| { ListenerOptions::new() .name(nm.borrow()) .nonblocking(ListenerNonblockingMode::Accept) .create_sync() }) .map_err(|e| e.downcast::().unwrap_or_else(io::Error::other))? .1; listener.accept() } interprocess-2.2.3/tests/local_socket/no_server.rs000064400000000000000000000015021046102023000205220ustar 00000000000000//! Tests what happens when a client attempts to connect to a local socket that doesn't exist. use { crate::{ local_socket::{prelude::*, Stream}, tests::util::*, }, color_eyre::eyre::{bail, ensure}, std::io, }; pub fn run_and_verify_error(id: &str, path: bool) -> TestResult { use io::ErrorKind::*; let err = match client(id, path) { Err(e) => e, Ok(()) => bail!("client successfully connected to nonexistent server"), }; ensure!( matches!(err.kind(), NotFound | ConnectionRefused), "expected error to be 'not found' or 'connection refused', received '{}'", err ); Ok(()) } fn client(id: &str, path: bool) -> io::Result<()> { let nm = namegen_local_socket(id, path).next().unwrap(); Stream::connect(nm?.borrow())?; Ok(()) } interprocess-2.2.3/tests/local_socket/stream.rs000064400000000000000000000042141046102023000200160ustar 00000000000000use { crate::{ local_socket::{prelude::*, ListenerOptions, Name, Stream}, tests::util::*, BoolExt, SubUsizeExt, }, color_eyre::eyre::WrapErr, std::{ io::{BufRead, BufReader, Write}, str, sync::{mpsc::Sender, Arc}, }, }; fn msg(server: bool, nts: bool) -> Box { message(None, server, Some(['\n', '\0'][nts.to_usize()])) } pub fn server( id: &str, handle_client: fn(Stream) -> TestResult, name_sender: Sender>>, num_clients: u32, path: bool, ) -> TestResult { let (name, listener) = listen_and_pick_name(&mut namegen_local_socket(id, path), |nm| { ListenerOptions::new().name(nm.borrow()).create_sync() })?; let _ = name_sender.send(name); listener .incoming() .take(num_clients.try_into().unwrap()) .try_for_each(|conn| handle_client(conn.opname("accept")?)) } pub fn handle_client(conn: Stream) -> TestResult { let mut conn = BufReader::new(conn); recv(&mut conn, &msg(false, false), 0)?; send(conn.get_mut(), &msg(true, false), 0)?; recv(&mut conn, &msg(false, true), 1)?; send(conn.get_mut(), &msg(true, true), 1) } pub fn client(name: &Name<'_>) -> TestResult { let mut conn = Stream::connect(name.borrow()).opname("connect").map(BufReader::new)?; send(conn.get_mut(), &msg(false, false), 0)?; recv(&mut conn, &msg(true, false), 0)?; send(conn.get_mut(), &msg(false, true), 1)?; recv(&mut conn, &msg(true, true), 1) } fn recv(conn: &mut dyn BufRead, exp: &str, nr: u8) -> TestResult { let term = *exp.as_bytes().last().unwrap(); let fs = ["first", "second"][nr.to_usize()]; let mut buffer = Vec::with_capacity(exp.len()); conn.read_until(term, &mut buffer).wrap_err_with(|| format!("{} receive failed", fs))?; ensure_eq!( str::from_utf8(&buffer).with_context(|| format!("{} receive wasn't valid UTF-8", fs))?, exp, ); Ok(()) } fn send(conn: &mut dyn Write, msg: &str, nr: u8) -> TestResult { let fs = ["first", "second"][nr.to_usize()]; conn.write_all(msg.as_bytes()).with_context(|| format!("{} socket send failed", fs)) } interprocess-2.2.3/tests/local_socket.rs000064400000000000000000000017011046102023000165210ustar 00000000000000// TODO(2.3.0) test various error conditions mod no_client; mod no_server; mod stream; use crate::tests::util::*; fn test_stream(id: &'static str, path: bool) -> TestResult { use stream::*; let scl = |s, n| server(id, handle_client, s, n, path); drive_server_and_multiple_clients(scl, client)?; Ok(()) } use { no_client::run_and_verify_error as test_no_client, no_server::run_and_verify_error as test_no_server, }; macro_rules! tests { ($fn:ident $nm:ident $path:ident) => { #[test] fn $nm() -> TestResult { test_wrapper(|| { $fn(make_id!(), $path) }) } }; ($fn:ident $($nm:ident $path:ident)+) => { $(tests!($fn $nm $path);)+ }; } tests! {test_stream stream_file true stream_namespaced false } tests! {test_no_server no_server_file true no_server_namespaced false } tests! {test_no_client no_client_file true no_client_namespaced false } interprocess-2.2.3/tests/os/unix/local_socket_fake_ns.rs000064400000000000000000000017471046102023000216250ustar 00000000000000use { crate::{ local_socket::{prelude::*, ListenerOptions, Stream}, os::unix::local_socket::SpecialDirUdSocket, tests::util::*, }, std::sync::Arc, }; fn test_inner(iter: u32) -> TestResult { let mut namegen = NameGen::new(&format!("{}{}", make_id!(), iter), |rnum| { format!("interprocess test {:08x}/fake ns/test.sock", rnum) .to_ns_name::() .map(Arc::new) }); let (name, _listener) = listen_and_pick_name(&mut namegen, |nm| { ListenerOptions::new().name(nm.borrow()).create_sync() })?; let name = Arc::try_unwrap(name).unwrap(); let _ = Stream::connect(name.borrow()).opname("client connect")?; Ok(()) } #[test] fn local_socket_fake_ns() -> TestResult { test_wrapper(|| { // fucking macOS let iterations = if cfg!(target_os = "macos") { 444 } else { 6 }; for i in 0..iterations { test_inner(i)?; } Ok(()) }) } interprocess-2.2.3/tests/os/unix/local_socket_mode.rs000064400000000000000000000040321046102023000211310ustar 00000000000000use { crate::{ local_socket::{traits::Stream as _, Listener, ListenerOptions, Name, NameInner, Stream}, os::unix::local_socket::ListenerOptionsExt, tests::util::*, OrErrno, }, libc::mode_t, std::{ ffi::{CString, OsStr}, mem::zeroed, os::unix::prelude::*, sync::Arc, }, }; fn get_file_mode(fname: &OsStr) -> TestResult { let mut cfname = fname.as_bytes().to_owned(); cfname.push(0); let fname = CString::from_vec_with_nul(cfname)?; let mut stat = unsafe { zeroed::() }; unsafe { libc::stat(fname.as_ptr(), &mut stat) != -1 } .true_val_or_errno(()) .opname("stat")?; Ok(stat.st_mode & 0o777) } fn get_fd_mode(fd: BorrowedFd<'_>) -> TestResult { let mut stat = unsafe { zeroed::() }; unsafe { libc::fstat(fd.as_raw_fd(), &mut stat) != -1 } .true_val_or_errno(()) .opname("stat")?; Ok(stat.st_mode & 0o777) } fn test_inner(path: bool) -> TestResult { const MODE: libc::mode_t = 0o600; let (name, listener) = listen_and_pick_name(&mut namegen_local_socket(make_id!(), path), |nm| { ListenerOptions::new().name(nm.borrow()).mode(MODE).create_sync() })?; let name = Arc::try_unwrap(name).unwrap(); let _ = Stream::connect(name.borrow()).opname("client connect")?; let actual_mode = if let Name(NameInner::UdSocketPath(path)) = name { get_file_mode(&path) } else { let fd = match &listener { Listener::UdSocket(l) => l.as_fd(), }; get_fd_mode(fd) } .opname("get mode")?; if actual_mode != 0 { // FreeBSD refuses to fstat sockets for reasons I cannot even begin to fathom ensure_eq!(actual_mode, MODE); } Ok(()) } #[test] fn local_socket_file_mode() -> TestResult { test_wrapper(|| test_inner(true)) } #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn local_socket_namespaced_mode() -> TestResult { test_wrapper(|| test_inner(false)) } interprocess-2.2.3/tests/os/windows/local_socket_security_descriptor/null_dacl.rs000064400000000000000000000016221046102023000267600ustar 00000000000000use { crate::{ local_socket::{prelude::*, ListenerOptions, Stream}, os::windows::{ local_socket::ListenerOptionsExt, security_descriptor::{AsSecurityDescriptorMutExt, SecurityDescriptor}, }, tests::util::*, TryClone, }, std::{ptr, sync::Arc}, }; pub(super) fn test_main() -> TestResult { let mut sd = SecurityDescriptor::new().opname("security descriptor creation")?; unsafe { sd.set_dacl(ptr::null_mut(), false).opname("DACL setter")?; } let (name, _listener) = listen_and_pick_name(&mut namegen_local_socket(make_id!(), false), |nm| { ListenerOptions::new() .name(nm.borrow()) .security_descriptor(sd.try_clone()?) .create_sync() })?; let _ = Stream::connect(Arc::try_unwrap(name).unwrap()).opname("client connect")?; Ok(()) } interprocess-2.2.3/tests/os/windows/local_socket_security_descriptor/sd_graft.rs000064400000000000000000000111061046102023000266120ustar 00000000000000#![cfg(not(ci))] use { crate::{ local_socket::{prelude::*, Listener, ListenerOptions, Stream}, os::windows::{ local_socket::ListenerOptionsExt, security_descriptor::{ AsSecurityDescriptorExt, BorrowedSecurityDescriptor, LocalBox, SecurityDescriptor, }, AsRawHandleExt as _, }, tests::util::*, OrErrno, SubUsizeExt, TryClone, }, std::{ ffi::OsString, fs::File, io, mem::MaybeUninit, os::windows::prelude::*, ptr, sync::Arc, }, widestring::{U16CStr, U16Str}, windows_sys::Win32::{ Foundation::{MAX_PATH, STATUS_SUCCESS}, Security::{ Authorization::{GetSecurityInfo, SE_KERNEL_OBJECT, SE_OBJECT_TYPE}, DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, OWNER_SECURITY_INFORMATION, }, System::LibraryLoader::GetModuleFileNameW, }, }; const SECINFO: u32 = DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION; fn get_sd(handle: BorrowedHandle<'_>, ot: SE_OBJECT_TYPE) -> TestResult { let mut sdptr = ptr::null_mut(); let errno = unsafe { GetSecurityInfo( handle.as_int_handle(), ot, SECINFO, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), &mut sdptr, ) }; let errno = { #[allow(clippy::as_conversions)] { errno as i32 } }; (errno == STATUS_SUCCESS) .then_some(()) .ok_or_else(|| io::Error::from_raw_os_error(errno)) .opname("GetSecurityInfo")?; let sdbx = unsafe { LocalBox::from_raw(sdptr) }; unsafe { BorrowedSecurityDescriptor::from_ptr(sdbx.as_ptr()) } .to_owned_sd() .opname("security descriptor clone") } fn count_opening_parentheses(s: &U16Str) -> u32 { let mut cpa = 0; for c in s.as_slice().iter().copied() { if c == b'('.into() { cpa += 1; } } cpa } fn ensure_equal_number_of_opening_parentheses(a: &U16Str, b: &U16Str) -> TestResult { ensure_eq!(count_opening_parentheses(a), count_opening_parentheses(b)); Ok(()) } fn ensure_equal_non_acl_part(a: &U16CStr, b: &U16CStr) -> TestResult { let mut idx = 0; for (i, (ca, cb)) in a.as_slice().iter().copied().zip(b.as_slice().iter().copied()).enumerate() { idx = i; if ca == b'D'.into() { break; } ensure_eq!(ca, cb); } Ok(idx) } fn get_self_exe(obuf: &mut [MaybeUninit]) -> io::Result<&U16CStr> { if obuf.is_empty() { return Ok(Default::default()); } let base = obuf.as_mut_ptr().cast(); let cap = obuf.len().try_into().unwrap_or(u32::MAX); unsafe { GetModuleFileNameW(0, base, cap) != 0 }.true_val_or_errno(()).and_then(|()| unsafe { U16CStr::from_ptr_truncate(base.cast_const(), cap.to_usize()).map_err(io::Error::other) }) } #[allow(clippy::as_conversions)] pub(super) fn test_main() -> TestResult { let sd = { let mut pathbuf = [MaybeUninit::uninit(); MAX_PATH as _]; let path: OsString = get_self_exe(&mut pathbuf).opname("query of path to own executable")?.into(); let file = File::open(path).opname("own executable open")?; get_sd(file.as_handle(), SE_KERNEL_OBJECT) .opname("query of own executable's security descriptor")? }; sd.serialize(SECINFO, |s| { eprintln!("SDDL of the running executable: {}", s.display()); }) .opname("serialize")?; let (name, listener) = listen_and_pick_name(&mut namegen_local_socket(make_id!(), false), |nm| { ListenerOptions::new() .name(nm.borrow()) .security_descriptor(sd.try_clone()?) .create_sync() })?; let _ = Stream::connect(Arc::try_unwrap(name).unwrap()).opname("client connect")?; let listener_handle = match listener { Listener::NamedPipe(l) => OwnedHandle::from(l), }; let listener_sd = get_sd(listener_handle.as_handle(), SE_KERNEL_OBJECT).opname("get listener SD")?; sd.serialize(SECINFO, |old_s| { listener_sd.serialize(SECINFO, |new_s| { eprintln!("SDDL of the local socket listener: {}", new_s.display()); let start = ensure_equal_non_acl_part(old_s, new_s)?; ensure_equal_number_of_opening_parentheses(&old_s[start..], &new_s[start..])?; TestResult::Ok(()) }) }) .opname("serialize and check")???; Ok(()) } interprocess-2.2.3/tests/os/windows/local_socket_security_descriptor.rs000064400000000000000000000003751046102023000250270ustar 00000000000000#![allow(unexpected_cfgs)] mod null_dacl; mod sd_graft; use crate::tests::util::*; #[cfg(not(ci))] #[test] fn sd_graft() -> TestResult { test_wrapper(sd_graft::test_main) } #[test] fn null_dacl() -> TestResult { test_wrapper(null_dacl::test_main) } interprocess-2.2.3/tests/os/windows/named_pipe/bytes.rs000064400000000000000000000057761046102023000214410ustar 00000000000000use { super::drive_server, crate::{ os::windows::named_pipe::{ pipe_mode, DuplexPipeStream, PipeListener, RecvPipeStream, SendPipeStream, }, tests::util::*, }, std::{ io::{prelude::*, BufReader}, sync::{mpsc::Sender, Arc}, }, }; fn msg(server: bool) -> Box { message(None, server, Some('\n')) } fn handle_conn_duplex( listener: &mut PipeListener, ) -> TestResult { let (mut recver, mut sender) = listener.accept().opname("accept")?.split(); recv(&mut recver, msg(false))?; send(&mut sender, msg(true))?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } fn handle_conn_cts(listener: &mut PipeListener) -> TestResult { let mut recver = listener.accept().opname("accept")?; recv(&mut recver, msg(false)) } fn handle_conn_stc(listener: &mut PipeListener) -> TestResult { let mut sender = listener.accept().opname("accept")?; send(&mut sender, msg(true)) } pub fn server_duplex(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_duplex::(), handle_conn_duplex, ) } pub fn server_cts(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_recv_only::(), handle_conn_cts, ) } pub fn server_stc(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_send_only::(), handle_conn_stc, ) } pub fn client_duplex(name: &str) -> TestResult { let (mut recver, mut sender) = DuplexPipeStream::::connect_by_path(name).opname("connect")?.split(); send(&mut sender, msg(false))?; recv(&mut recver, msg(true))?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } pub fn client_cts(name: &str) -> TestResult { let mut sender = SendPipeStream::::connect_by_path(name).opname("connect")?; send(&mut sender, msg(false)) } pub fn client_stc(name: &str) -> TestResult { let mut recver = RecvPipeStream::::connect_by_path(name).opname("connect")?; recv(&mut recver, msg(true)) } fn recv(conn: &mut RecvPipeStream, exp: impl AsRef) -> TestResult { let mut conn = BufReader::new(conn); let exp_ = exp.as_ref(); let mut buf = String::with_capacity(exp_.len()); conn.read_line(&mut buf).opname("receive")?; ensure_eq!(buf, exp_); Ok(()) } fn send(conn: &mut SendPipeStream, msg: impl AsRef) -> TestResult { conn.write_all(msg.as_ref().as_bytes()).opname("send")?; conn.flush().opname("flush") } interprocess-2.2.3/tests/os/windows/named_pipe/msg.rs000064400000000000000000000106471046102023000210720ustar 00000000000000use { super::drive_server, crate::{ os::windows::named_pipe::{ pipe_mode, DuplexPipeStream, PipeListener, PipeMode, RecvPipeStream, SendPipeStream, }, tests::util::*, SubUsizeExt, }, color_eyre::eyre::{ensure, WrapErr}, recvmsg::{MsgBuf, RecvMsg, RecvResult}, std::{ str, sync::{mpsc::Sender, Arc}, }, }; fn msgs(server: bool) -> [Box; 2] { [ message(Some(format_args!("First")), server, None), message(Some(format_args!("Second")), server, None), ] } fn futf8(m: &[u8]) -> TestResult<&str> { str::from_utf8(m).context("received message was not valid UTF-8") } fn handle_conn_duplex( listener: &mut PipeListener, ) -> TestResult { let (mut recver, mut sender) = listener.accept().opname("accept")?.split(); let [msg1, msg2] = msgs(false); recv(&mut recver, msg1, 0)?; recv(&mut recver, msg2, 1)?; let [msg1, msg2] = msgs(true); send(&mut sender, msg1, 0)?; send(&mut sender, msg2, 1)?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } fn handle_conn_cts( listener: &mut PipeListener, ) -> TestResult { let mut recver = listener.accept().opname("accept")?; let [msg1, msg2] = msgs(false); recv(&mut recver, msg1, 0)?; recv(&mut recver, msg2, 1) } fn handle_conn_stc( listener: &mut PipeListener, ) -> TestResult { let mut sender = listener.accept().opname("accept")?; let [msg1, msg2] = msgs(true); send(&mut sender, msg1, 0)?; send(&mut sender, msg2, 1) } pub fn server_duplex(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.mode(PipeMode::Messages).create_duplex::(), handle_conn_duplex, ) } pub fn server_cts(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.mode(PipeMode::Messages).create_recv_only::(), handle_conn_cts, ) } pub fn server_stc(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.mode(PipeMode::Messages).create_send_only::(), handle_conn_stc, ) } pub fn client_duplex(name: &str) -> TestResult { let (mut recver, mut sender) = DuplexPipeStream::::connect_by_path(name).opname("connect")?.split(); let [msg1, msg2] = msgs(false); send(&mut sender, msg1, 0)?; send(&mut sender, msg2, 1)?; let [msg1, msg2] = msgs(true); recv(&mut recver, msg1, 0)?; recv(&mut recver, msg2, 1)?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } pub fn client_cts(name: &str) -> TestResult { let mut sender = SendPipeStream::::connect_by_path(name).opname("connect")?; let [msg1, msg2] = msgs(false); send(&mut sender, msg1, 0)?; send(&mut sender, msg2, 1) } pub fn client_stc(name: &str) -> TestResult { let mut recver = RecvPipeStream::::connect_by_path(name).opname("connect")?; let [msg1, msg2] = msgs(true); recv(&mut recver, msg1, 0)?; recv(&mut recver, msg2, 1) } fn recv( conn: &mut RecvPipeStream, exp: impl AsRef, nr: u8, ) -> TestResult { let fs = ["first", "second"][nr.to_usize()]; let exp_ = exp.as_ref(); let mut len = exp_.len(); if nr == 2 { len -= 1; // tests spill } let mut buf = MsgBuf::from(Vec::with_capacity(len)); let rslt = conn.recv_msg(&mut buf, None).with_context(|| format!("{} receive failed", fs))?; ensure_eq!(futf8(buf.filled_part())?, exp_); if nr == 2 { ensure!(matches!(rslt, RecvResult::Spilled)); } else { ensure!(matches!(rslt, RecvResult::Fit)); } Ok(()) } fn send( conn: &mut SendPipeStream, msg: impl AsRef, nr: u8, ) -> TestResult { let msg_ = msg.as_ref(); let fs = ["first", "second"][nr.to_usize()]; let sent = conn.send(msg_.as_bytes()).wrap_err_with(|| format!("{} send failed", fs))?; ensure_eq!(sent, msg_.len()); Ok(()) } interprocess-2.2.3/tests/os/windows/named_pipe.rs000064400000000000000000000031441046102023000202760ustar 00000000000000#![cfg(windows)] mod bytes; mod msg; use { crate::{os::windows::named_pipe::PipeListenerOptions, tests::util::*}, std::{ fmt::Debug, io, path::Path, sync::{mpsc::Sender, Arc}, }, }; macro_rules! matrix { (@dir_s duplex) => {server_duplex}; (@dir_s stc) => {server_stc}; (@dir_s cts) => {server_cts}; (@dir_c duplex) => {client_duplex}; (@dir_c stc) => {client_stc}; (@dir_c cts) => {client_cts}; ($($mod:ident $ty:ident $nm:ident)+) => {$( #[test] fn $nm() -> TestResult { use $mod::*; test_wrapper(|| { let server = matrix!(@dir_s $ty); drive_server_and_multiple_clients( |ns, nc| server(make_id!(), ns, nc), matrix!(@dir_c $ty), ) }) } )+}; } matrix! { bytes duplex bytes_bidir bytes cts bytes_unidir_client_to_server bytes stc bytes_unidir_server_to_client msg duplex msg_bidir msg cts msg_unidir_client_to_server msg stc msg_unidir_server_to_client } fn drive_server( id: &str, name_sender: Sender>, num_clients: u32, mut createfn: impl (FnMut(PipeListenerOptions<'_>) -> io::Result), mut acceptfn: impl FnMut(&mut L) -> TestResult, ) -> TestResult { let (name, mut listener) = listen_and_pick_name(&mut namegen_named_pipe(id), |nm| { createfn(PipeListenerOptions::new().path(Path::new(nm))) })?; let _ = name_sender.send(name); for _ in 0..num_clients { acceptfn(&mut listener)?; } Ok(()) } interprocess-2.2.3/tests/os/windows/tokio_named_pipe/bytes.rs000064400000000000000000000066231046102023000226360ustar 00000000000000use { super::drive_server, crate::{ os::windows::named_pipe::{ pipe_mode, tokio::{ DuplexPipeStream, PipeListener, PipeListenerOptionsExt, RecvPipeStream, SendPipeStream, }, }, tests::util::{message, TestResult, WrapErrExt}, }, std::sync::Arc, tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, sync::oneshot::Sender, try_join, }, }; fn msg(server: bool) -> Box { message(None, server, Some('\n')) } pub async fn server_duplex( id: &str, name_sender: Sender>, num_clients: u32, ) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_tokio_duplex::(), handle_conn_duplex, ) .await } pub async fn server_cts(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_tokio_recv_only::(), handle_conn_cts, ) .await } pub async fn server_stc(id: &str, name_sender: Sender>, num_clients: u32) -> TestResult { drive_server( id, name_sender, num_clients, |plo| plo.create_tokio_send_only::(), handle_conn_stc, ) .await } async fn handle_conn_duplex( listener: Arc>, ) -> TestResult { let (mut recver, mut sender) = listener.accept().await.opname("accept")?.split(); try_join!(recv(&mut recver, msg(false)), send(&mut sender, msg(true)))?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } async fn handle_conn_cts( listener: Arc>, ) -> TestResult { let mut recver = listener.accept().await.opname("accept")?; recv(&mut recver, msg(false)).await } async fn handle_conn_stc( listener: Arc>, ) -> TestResult { let mut sender = listener.accept().await.opname("accept")?; send(&mut sender, msg(true)).await } pub async fn client_duplex(name: Arc) -> TestResult { let (mut recver, mut sender) = DuplexPipeStream::::connect_by_path(&*name) .await .opname("connect")? .split(); try_join!(recv(&mut recver, msg(true)), send(&mut sender, msg(false)))?; DuplexPipeStream::reunite(recver, sender).opname("reunite")?; Ok(()) } pub async fn client_cts(name: Arc) -> TestResult { let mut sender = SendPipeStream::::connect_by_path(&*name).await.opname("connect")?; send(&mut sender, msg(false)).await } pub async fn client_stc(name: Arc) -> TestResult { let mut recver = RecvPipeStream::::connect_by_path(&*name).await.opname("connect")?; recv(&mut recver, msg(true)).await } async fn recv(recver: &mut RecvPipeStream, exp: impl AsRef) -> TestResult { let mut buffer = String::with_capacity(128); let mut recver = BufReader::new(recver); recver.read_line(&mut buffer).await.opname("receive")?; ensure_eq!(buffer, exp.as_ref()); Ok(()) } async fn send(sender: &mut SendPipeStream, snd: impl AsRef) -> TestResult { sender.write_all(snd.as_ref().as_bytes()).await.opname("send") } interprocess-2.2.3/tests/os/windows/tokio_named_pipe.rs000064400000000000000000000040501046102023000215000ustar 00000000000000#![cfg(all(windows, feature = "tokio"))] mod bytes; use { crate::{ os::windows::named_pipe::PipeListenerOptions, tests::util::{ listen_and_pick_name, namegen_named_pipe, tokio::{drive_server_and_multiple_clients, test_wrapper}, TestResult, }, }, color_eyre::eyre::WrapErr, std::{fmt::Debug, future::Future, io, path::Path, sync::Arc}, tokio::{sync::oneshot::Sender, task}, }; macro_rules! matrix { (@dir_s duplex) => {server_duplex}; (@dir_s stc) => {server_stc}; (@dir_s cts) => {server_cts}; (@dir_c duplex) => {client_duplex}; (@dir_c stc) => {client_stc}; (@dir_c cts) => {client_cts}; ($($mod:ident $ty:ident $nm:ident)+) => {$( #[test] fn $nm() -> TestResult { use $mod::*; test_wrapper(async { let server = matrix!(@dir_s $ty); drive_server_and_multiple_clients( move |ns, nc| server(make_id!(), ns, nc), matrix!(@dir_c $ty), ).await }) } )+}; } matrix! { bytes duplex bytes_bidir bytes cts bytes_unidir_client_to_server bytes stc bytes_unidir_server_to_client } async fn drive_server + Send + 'static>( id: &str, name_sender: Sender>, num_clients: u32, mut createfn: impl (FnMut(PipeListenerOptions<'_>) -> io::Result), mut acceptfut: impl FnMut(Arc) -> T, ) -> TestResult { let (name, listener) = listen_and_pick_name(&mut namegen_named_pipe(id), |nm| { createfn(PipeListenerOptions::new().path(Path::new(nm))).map(Arc::new) })?; let _ = name_sender.send(name); let mut tasks = Vec::with_capacity(num_clients.try_into().unwrap()); for _ in 0..num_clients { tasks.push(task::spawn(acceptfut(Arc::clone(&listener)))); } for task in tasks { task.await .wrap_err("server task panicked")? .wrap_err("server task returned early with error")?; } Ok(()) } interprocess-2.2.3/tests/tokio_local_socket/no_server.rs000064400000000000000000000015211046102023000217300ustar 00000000000000//! Tests what happens when a client attempts to connect to a local socket that doesn't exist. use { crate::{ local_socket::tokio::{prelude::*, Stream}, tests::util::*, }, color_eyre::eyre::{bail, ensure}, std::io, }; pub async fn run_and_verify_error(path: bool) -> TestResult { use io::ErrorKind::*; let err = match client(path).await { Err(e) => e, Ok(()) => bail!("client successfully connected to nonexistent server"), }; ensure!( matches!(err.kind(), NotFound | ConnectionRefused), "expected error to be 'not found' or 'connection refused', received '{}'", err ); Ok(()) } async fn client(path: bool) -> io::Result<()> { let nm = namegen_local_socket(make_id!(), path).next().unwrap(); Stream::connect(nm?.borrow()).await?; Ok(()) } interprocess-2.2.3/tests/tokio_local_socket/stream.rs000064400000000000000000000107701046102023000212270ustar 00000000000000use { crate::{ local_socket::{ tokio::{prelude::*, Stream}, ListenerOptions, Name, }, tests::util::*, BoolExt, SubUsizeExt, }, ::tokio::{ io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader}, sync::oneshot::Sender, task, try_join, }, color_eyre::eyre::WrapErr, std::{future::Future, str, sync::Arc}, }; fn msg(server: bool, nts: bool) -> Box { message(None, server, Some(['\n', '\0'][nts.to_usize()])) } pub async fn server + Send + 'static>( id: &str, mut handle_client: impl FnMut(Stream) -> HCF, name_sender: Sender>>, num_clients: u32, path: bool, ) -> TestResult { let (name, listener) = listen_and_pick_name(&mut namegen_local_socket(id, path), |nm| { ListenerOptions::new().name(nm.borrow()).create_tokio() })?; let _ = name_sender.send(name); let mut tasks = Vec::with_capacity(num_clients.try_into().unwrap()); for _ in 0..num_clients { let conn = listener.accept().await.opname("accept")?; tasks.push(task::spawn(handle_client(conn))); } for task in tasks { task.await .context("server task panicked")? .context("server task returned early with error")?; } Ok(()) } pub async fn handle_client_nosplit(conn: Stream) -> TestResult { let (mut recver, mut sender) = (BufReader::new(&conn), &conn); let recv = async { recv(&mut recver, &msg(false, false), 0).await?; recv(&mut recver, &msg(false, true), 1).await }; let send = async { send(&mut sender, &msg(true, false), 0).await?; send(&mut sender, &msg(true, true), 1).await }; try_join!(recv, send).map(|((), ())| ()) } pub async fn handle_client_split(conn: Stream) -> TestResult { let (recver, sender) = conn.split(); let recv = task::spawn(async move { let mut recver = BufReader::new(recver); recv(&mut recver, &msg(true, false), 0).await?; recv(&mut recver, &msg(true, true), 1).await?; TestResult::<_>::Ok(recver.into_inner()) }); let send = task::spawn(async move { let mut sender = sender; send(&mut sender, &msg(false, false), 0).await?; send(&mut sender, &msg(false, true), 1).await?; TestResult::<_>::Ok(sender) }); let (recver, sender) = try_join!(recv, send)?; Stream::reunite(recver?, sender?).opname("reunite")?; Ok(()) } pub async fn client_nosplit(nm: Arc>) -> TestResult { let conn = Stream::connect(nm.borrow()).await.opname("connect")?; let (mut recver, mut sender) = (BufReader::new(&conn), &conn); let recv = async { recv(&mut recver, &msg(true, false), 0).await?; recv(&mut recver, &msg(true, true), 1).await }; let send = async { send(&mut sender, &msg(false, false), 0).await?; send(&mut sender, &msg(false, true), 1).await }; try_join!(recv, send).map(|((), ())| ()) } pub async fn client_split(name: Arc>) -> TestResult { let (recver, sender) = Stream::connect(name.borrow()).await.opname("connect")?.split(); let recv = task::spawn(async move { let mut recver = BufReader::new(recver); recv(&mut recver, &msg(false, false), 0).await?; recv(&mut recver, &msg(false, true), 1).await?; TestResult::<_>::Ok(recver.into_inner()) }); let send = task::spawn(async move { let mut sender = sender; send(&mut sender, &msg(true, false), 0).await?; send(&mut sender, &msg(true, true), 1).await?; TestResult::<_>::Ok(sender) }); let (recver, sender) = try_join!(recv, send)?; Stream::reunite(recver?, sender?).opname("reunite")?; Ok(()) } async fn recv(conn: &mut (dyn AsyncBufRead + Unpin + Send), exp: &str, nr: u8) -> TestResult { let term = *exp.as_bytes().last().unwrap(); let fs = ["first", "second"][nr.to_usize()]; let mut buffer = Vec::with_capacity(exp.len()); conn.read_until(term, &mut buffer) .await .wrap_err_with(|| format!("{} receive failed", fs))?; ensure_eq!( str::from_utf8(&buffer).with_context(|| format!("{} receive wasn't valid UTF-8", fs))?, exp, ); Ok(()) } async fn send(conn: &mut (dyn AsyncWrite + Unpin + Send), msg: &str, nr: u8) -> TestResult { let fs = ["first", "second"][nr.to_usize()]; conn.write_all(msg.as_bytes()).await.with_context(|| format!("{} socket send failed", fs)) } interprocess-2.2.3/tests/tokio_local_socket.rs000064400000000000000000000035461046102023000177370ustar 00000000000000// TODO(2.3.0) test various error conditions mod no_server; mod stream; use { crate::{ local_socket::{tokio::Stream, Name}, tests::util::{self, tokio::test_wrapper, TestResult}, }, std::{future::Future, pin::Pin, sync::Arc}, }; #[allow(clippy::type_complexity)] async fn test_stream(id: &'static str, split: bool, path: bool) -> TestResult { use stream::*; type Fut = Pin + Send + 'static>>; type F = Box Fut + Send + Sync>; let hcl: F = if split { Box::new(|conn| Box::pin(handle_client_split(conn))) } else { Box::new(|conn| Box::pin(handle_client_nosplit(conn))) }; let client: F>> = if split { Box::new(|conn| Box::pin(client_split(conn))) } else { Box::new(|conn| Box::pin(client_nosplit(conn))) }; util::tokio::drive_server_and_multiple_clients( move |s, n| server(id, hcl, s, n, path), client, ) .await } macro_rules! matrix { (@body $split:ident $path:ident) => { test_wrapper(test_stream(make_id!(), $split, $path)) }; ($nm:ident false $path:ident) => { #[test] fn $nm() -> TestResult { matrix!(@body false $path) } }; ($nm:ident true $path:ident) => { #[test] #[cfg(not(windows))] fn $nm() -> TestResult { matrix!(@body true $path) } }; ($($nm:ident $split:ident $path:ident)+) => { $(matrix!($nm $split $path);)+ }; } matrix! { stream_file_nosplit false true stream_file_split true true stream_namespaced_nosplit false false stream_namespaced_split true false } #[test] fn no_server_file() -> TestResult { test_wrapper(no_server::run_and_verify_error(true)) } #[test] fn no_server_namespaced() -> TestResult { test_wrapper(no_server::run_and_verify_error(false)) } interprocess-2.2.3/tests/tokio_unnamed_pipe/basic.rs000064400000000000000000000017151046102023000210160ustar 00000000000000use { crate::{ tests::util::{TestResult, WrapErrExt}, unnamed_pipe::tokio::pipe, }, tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, sync::mpsc, task, }, }; static MSG: &str = "Message from sender to receiver\n"; pub(super) async fn main() -> TestResult { let (mut tx, rx) = pipe().opname("pipe creation")?; let (notify, mut wait) = mpsc::channel(1); let jh = task::spawn(async move { tx.write_all(MSG.as_bytes()).await.opname("send")?; drop(tx); // Test buffer retention on drop notify.send(()).await.opname("notify")?; TestResult::Ok(()) }); wait.recv().await.unwrap(); // Sender is guaranteed to be in limbo by this point (Windows only) let mut buf = String::with_capacity(MSG.len()); let mut rx = BufReader::new(rx); rx.read_line(&mut buf).await.opname("receive")?; ensure_eq!(buf, MSG); jh.await??; Ok(()) } interprocess-2.2.3/tests/tokio_unnamed_pipe.rs000064400000000000000000000001671046102023000177350ustar 00000000000000mod basic; use super::util::{tokio::*, TestResult}; #[test] fn basic() -> TestResult { test_wrapper(basic::main()) } interprocess-2.2.3/tests/unnamed_pipe/basic.rs000064400000000000000000000016701046102023000176110ustar 00000000000000use { crate::{tests::util::*, unnamed_pipe::pipe}, std::{ io::{BufRead, BufReader, Write}, sync::mpsc, thread, }, }; static MSG: &str = "Message from sender to receiver\n"; pub(super) fn main() -> TestResult { let (mut tx, rx) = pipe().opname("pipe creation")?; thread::scope(|scope| { let (notify, wait) = mpsc::channel(); scope.spawn(move || { tx.write_all(MSG.as_bytes()).opname("send")?; drop(tx); // Test buffer retention on drop notify.send(()).opname("notify")?; TestResult::Ok(()) }); wait.recv().opname("wait for drop")?; // Sender is guaranteed to be in limbo by this point (Windows only) let mut buf = String::with_capacity(MSG.len()); let mut rx = BufReader::new(rx); rx.read_line(&mut buf).opname("receive")?; ensure_eq!(buf, MSG); Ok(()) }) } interprocess-2.2.3/tests/unnamed_pipe.rs000064400000000000000000000001401046102023000165170ustar 00000000000000mod basic; use super::util::*; #[test] fn basic() -> TestResult { test_wrapper(basic::main) } interprocess-2.2.3/tests/util/choke.rs000064400000000000000000000027641046102023000161370ustar 00000000000000use std::sync::{Arc, Condvar, Mutex, Weak}; /// Choke – a rate-limiting semaphore that does not protect any concurrently accessed resource. #[derive(Debug)] pub struct Choke(Arc); impl Choke { pub fn new(limit: u32) -> Self { let inner = ChokeInner { count: Mutex::new(0), limit, condvar: Condvar::new() }; Self(Arc::new(inner)) } pub fn take(&self) -> ChokeGuard { let mut lock = Some(self.0.count.lock().unwrap()); loop { let mut c_lock = lock.take().unwrap(); if *c_lock < self.0.limit { *c_lock += 1; return self.make_guard(); } else { let c_lock = self.0.condvar.wait(c_lock).unwrap(); lock = Some(c_lock); } } } fn make_guard(&self) -> ChokeGuard { ChokeGuard(Arc::downgrade(&self.0)) } } impl Clone for Choke { fn clone(&self) -> Self { Self(self.0.clone()) } } #[derive(Debug)] struct ChokeInner { count: Mutex, limit: u32, condvar: Condvar, } impl ChokeInner { fn decrement(&self) { let mut count = self.count.lock().unwrap(); *count = count.checked_sub(1).expect("choke counter underflow"); self.condvar.notify_one(); } } /// Guard for `Choke` that owns one unit towards the limit. pub struct ChokeGuard(Weak); impl Drop for ChokeGuard { fn drop(&mut self) { if let Some(inner) = self.0.upgrade() { inner.decrement(); } } } interprocess-2.2.3/tests/util/drive.rs000064400000000000000000000073771046102023000161640ustar 00000000000000use { super::{choke::*, num_clients, num_concurrent_clients, TestResult}, color_eyre::eyre::{bail, Context}, std::{ borrow::Borrow, io, sync::mpsc::{channel, /*Receiver,*/ Sender}, thread, }, }; /// Waits for the leader closure to reach a point where it sends a message for the follower closure, /// then runs the follower. Captures Eyre errors on both sides and bubbles them up if they occur, /// reporting which side produced the error. pub fn drive_pair( leader: Ld, leader_name: &str, follower: Fl, follower_name: &str, ) -> TestResult where T: Send, Ld: FnOnce(Sender) -> TestResult + Send, Fl: FnOnce(T) -> TestResult, { thread::scope(|scope| { let (sender, receiver) = channel(); let ltname = leader_name.to_lowercase(); let leading_thread = thread::Builder::new() .name(ltname) .spawn_scoped(scope, move || leader(sender)) .with_context(|| format!("{leader_name} thread launch failed"))?; if let Ok(msg) = receiver.recv() { // If the leader reached the send point, proceed with the follower code let rslt = follower(msg); exclude_deadconn(rslt) .with_context(|| format!("{follower_name} exited early with error"))?; } let Ok(rslt) = leading_thread.join() else { bail!("{leader_name} panicked"); }; exclude_deadconn(rslt).with_context(|| format!("{leader_name} exited early with error")) }) } /// Filters errors that have to do with the other side returning an error and not bubbling it up in /// time. #[rustfmt::skip] // oh FUCK OFF fn exclude_deadconn(r: TestResult) -> TestResult { use io::ErrorKind::*; let Err(e) = r else { return r; }; let Some(ioe) = e.root_cause().downcast_ref::() else { return Err(e); }; match ioe.kind() { ConnectionRefused | ConnectionReset | ConnectionAborted | NotConnected | BrokenPipe | WriteZero | UnexpectedEof => Ok(()), _ => Err(e), } } pub fn drive_server_and_multiple_clients(server: Srv, client: Clt) -> TestResult where T: Send + Borrow, B: Send + Sync + ?Sized, Srv: FnOnce(Sender, u32) -> TestResult + Send, Clt: Fn(&B) -> TestResult + Send + Sync, { let choke = Choke::new(num_concurrent_clients()); let num_clients = num_clients(); let client_wrapper = |msg: T| { thread::scope(|scope| { let mut client_threads = Vec::with_capacity(usize::try_from(num_clients).unwrap()); for n in 1..=num_clients { let tname = format!("client {n}"); let choke_guard = choke.take(); let (bclient, bmsg) = (&client, msg.borrow()); let jhndl = thread::Builder::new() .name(tname.clone()) .spawn_scoped(scope, move || { // Has to use move to send to other thread to drop when client finishes let _cg = choke_guard; bclient(bmsg) }) .with_context(|| format!("{tname} thread launch failed"))?; client_threads.push(jhndl); } for client in client_threads { let Ok(rslt) = client.join() else { bail!("client thread panicked"); }; rslt?; // Early-return the first error; context not necessary as drive_pair does it } Ok(()) }) }; let server_wrapper = move |sender: Sender| server(sender, num_clients); drive_pair(server_wrapper, "server", client_wrapper, "client") } interprocess-2.2.3/tests/util/eyre.rs000064400000000000000000000024171046102023000160050ustar 00000000000000use { color_eyre::eyre::{self, WrapErr}, std::sync::Mutex, }; pub type TestResult = eyre::Result; static COLOR_EYRE_INSTALLED: Mutex = Mutex::new(false); pub(super) fn install() { let mut lock = COLOR_EYRE_INSTALLED.lock().unwrap(); if !*lock { let _ = color_eyre::install(); *lock = true; } } macro_rules! ensure_eq { ($left:expr, $right:expr $(,)?) => { match (&$left, &$right) { (left_val, right_val) => { ::color_eyre::eyre::ensure!((left_val == right_val), r#"assertion failed: `(left == right)` left: `{:?}`, right: `{:?}`"#, left_val, right_val); } } }; ($left:expr, $right:expr, $($arg:tt)+) => { match (&$left, &$right) { (left_val, right_val) => { ::color_eyre::eyre::ensure!((left_val == right_val), r#"assertion failed: `(left == right)` left: `{:?}`, right: `{:?}`: {}"#, left_val, right_val, ::core::format_args!($($arg)+)); } } }; } pub trait WrapErrExt: WrapErr + Sized { fn opname(self, loc: &str) -> eyre::Result { self.wrap_err_with(|| format!("{loc} failed")) } } impl> WrapErrExt for WE {} interprocess-2.2.3/tests/util/mod.rs000064400000000000000000000044011046102023000156130ustar 00000000000000//! Test utilities for allocating an address for the server and then spawning clients to connect to //! it. #![allow(dead_code, unused_macros)] #[macro_use] mod eyre; #[macro_use] mod namegen; mod choke; mod drive; mod wdt; mod xorshift; #[allow(unused_imports)] pub use {drive::*, eyre::*, namegen::*, xorshift::*}; #[cfg(feature = "tokio")] pub mod tokio; use { color_eyre::eyre::WrapErr, std::{ fmt::{Arguments, Debug}, io, sync::Arc, }, }; fn intvar(nam: &str) -> Option { let val = std::env::var(nam).ok()?; val.trim().parse().ok() } pub fn num_clients() -> u32 { intvar("INTERPROCESS_TEST_NUM_CLIENTS").filter(|n| *n > 0).unwrap_or(80) } pub fn num_concurrent_clients() -> u32 { intvar("INTERPROCESS_TEST_NUM_CONCURRENT_CLIENTS").filter(|n| *n > 0).unwrap_or(6) } pub fn test_wrapper(f: impl (FnOnce() -> TestResult) + Send + 'static) -> TestResult { eyre::install(); self::wdt::run_under_wachdog(f) } pub fn message(msg: Option>, server: bool, terminator: Option) -> Box { let msg = msg.unwrap_or_else(|| format_args!("Message")); let sc = if server { "server" } else { "client" }; let mut msg = format!("{msg} from {sc}!"); if let Some(t) = terminator { msg.push(t); } msg.into() } pub fn listen_and_pick_name NameResult>( namegen: &mut NameGen, mut bindfn: impl FnMut(&N) -> io::Result, ) -> TestResult<(Arc, L)> { use std::io::ErrorKind::*; let listener = namegen .find_map(|nm| { eprintln!("Trying name {nm:?}..."); let nm = match nm { Ok(ok) => ok, Err(e) => return Some(Err(e)), }; let l = match bindfn(&nm) { Ok(l) => l, Err(e) if matches!(e.kind(), AddrInUse | PermissionDenied) => { eprintln!("\"{}\", skipping", e.kind()); return None; } Err(e) => return Some(Err(e)), }; Some(Ok((nm, l))) }) .unwrap() // Infinite iterator .context("listener bind failed")?; eprintln!("Listener successfully created: {listener:#?}"); Ok(listener) } interprocess-2.2.3/tests/util/namegen.rs000064400000000000000000000033671046102023000164600ustar 00000000000000use { super::Xorshift32, crate::local_socket::{GenericFilePath, GenericNamespaced, Name, ToFsName, ToNsName}, std::{io, sync::Arc}, }; #[derive(Copy, Clone, Debug)] pub struct NameGen NameResult> { rng: Xorshift32, name_fn: F, } impl NameResult> NameGen { pub fn new(id: &str, name_fn: F) -> Self { Self { rng: Xorshift32::from_id(id), name_fn } } } impl NameResult> Iterator for NameGen { type Item = NameResult; fn next(&mut self) -> Option { Some((self.name_fn)(self.rng.next())) } } pub type NameResult = io::Result>; pub fn namegen_local_socket( id: &str, path: bool, ) -> NameGen, impl FnMut(u32) -> io::Result>>> { NameGen::new(id, move |rn| if path { next_fs(rn) } else { next_ns(rn) }.map(Arc::new)) } fn next_fs(rn: u32) -> io::Result> { if cfg!(windows) { windows_path(rn) } else if cfg!(unix) { unix_path(rn) } else { unreachable!() } .to_fs_name::() } fn next_ns(rn: u32) -> io::Result> { format!("@interprocess-test-{:08x}", rn).to_ns_name::() } pub fn namegen_named_pipe(id: &str) -> NameGen NameResult> { NameGen::new(id, move |rn| Ok(windows_path(rn).into())) } fn windows_path(rn: u32) -> String { format!(r"\\.\pipe\interprocess-test-{rn:08x}") } fn unix_path(rn: u32) -> String { let tmpdir = std::env::var("TMPDIR").ok(); format!("{}/interprocess-test-{rn:08x}.sock", tmpdir.as_deref().unwrap_or("/tmp")) } macro_rules! make_id { () => { concat!(file!(), line!(), column!()) }; } interprocess-2.2.3/tests/util/tokio.rs000064400000000000000000000060361046102023000161670ustar 00000000000000use { super::{num_clients, num_concurrent_clients, TestResult, WrapErrExt}, color_eyre::eyre::{bail, Context}, std::{future::Future, sync::Arc}, tokio::{ sync::{ oneshot::{channel, Sender}, Semaphore, }, task, try_join, }, }; pub fn test_wrapper(f: impl Future + Send + 'static) -> TestResult { super::test_wrapper(|| { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .opname("Tokio runtime spawn")?; rt.block_on(f) }) } /// Waits for the leader closure to reach a point where it sends a message for the follower closure, /// then runs the follower. Captures Eyre errors on both sides and panics if any occur, reporting /// which side produced the error. pub async fn drive_pair( leader: Ld, leader_name: &str, follower: Fl, follower_name: &str, ) -> TestResult where Ld: FnOnce(Sender) -> Ldf, Ldf: Future, Fl: FnOnce(T) -> Flf, Flf: Future, { let (sender, receiver) = channel(); let leading_task = async { leader(sender).await.with_context(|| format!("{leader_name} exited early with error")) }; let following_task = async { let msg = receiver.await?; follower(msg).await.with_context(|| format!("{follower_name} exited early with error")) }; try_join!(leading_task, following_task).map(|((), ())| ()) } pub async fn drive_server_and_multiple_clients( server: Srv, client: Clt, ) -> TestResult where T: Send + Sync + ?Sized + 'static, Srv: FnOnce(Sender>, u32) -> Srvf + Send + 'static, Srvf: Future, Clt: Fn(Arc) -> Cltf + Send + Sync + 'static, Cltf: Future + Send, { let num_clients = num_clients(); let client_wrapper = |msg| async move { let client = Arc::new(client); let choke = Arc::new(Semaphore::new(num_concurrent_clients().try_into().unwrap())); let mut client_tasks = Vec::with_capacity(num_clients.try_into().unwrap()); for _ in 0..num_clients { let permit = Arc::clone(&choke).acquire_owned().await.unwrap(); let clientc = Arc::clone(&client); let msgc = Arc::clone(&msg); let jhndl = task::spawn(async move { let _prm = permit; // Send to other thread to drop when client finishes clientc(msgc).await }); client_tasks.push(jhndl); } for client in client_tasks { let Ok(rslt) = client.await else { bail!("client task panicked"); }; rslt?; // Early-return the first error; context not necessary as drive_pair does it } Ok::<(), color_eyre::eyre::Error>(()) }; let server_wrapper = move |sender: Sender>| server(sender, num_clients); drive_pair(server_wrapper, "server", client_wrapper, "client").await } interprocess-2.2.3/tests/util/wdt.rs000064400000000000000000000013661046102023000156410ustar 00000000000000use { super::{TestResult, WrapErrExt}, color_eyre::eyre::bail, std::{sync::mpsc, thread, time::Duration}, }; const TIMEOUT: Duration = Duration::from_secs(2); pub fn run_under_wachdog(f: impl (FnOnce() -> TestResult) + Send + 'static) -> TestResult { let (killswitch, timeout_joiner) = mpsc::channel(); let joiner = thread::Builder::new() .name("test main (under watchdog)".to_owned()) .spawn(move || { let ret = f(); let _ = killswitch.send(()); ret }) .opname("watchdog scrutinee thread spawn")?; if let Err(mpsc::RecvTimeoutError::Timeout) = timeout_joiner.recv_timeout(TIMEOUT) { bail!("watchdog timer has run out"); } joiner.join().unwrap() } interprocess-2.2.3/tests/util/xorshift.rs000064400000000000000000000015731046102023000167110ustar 00000000000000use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; /// The 32-bit variant of the Xorshift PRNG algorithm. /// /// Didn't feel like pulling in the `rand` crate, so have this here beauty instead. #[repr(transparent)] #[derive(Copy, Clone, Debug)] pub struct Xorshift32(pub u32); impl Xorshift32 { pub fn from_id(id: &str) -> Self { let mut hasher = DefaultHasher::new(); id.hash(&mut hasher); let hash64 = hasher.finish(); let hash32 = ((hash64 & 0xFFFF_FFFF_0000_0000) >> 32) ^ (hash64 & 0xFFFF_FFFF); Self(hash32.try_into().unwrap()) } pub fn next(&mut self) -> u32 { self.0 ^= self.0 << 13; self.0 ^= self.0 >> 17; self.0 ^= self.0 << 5; self.0 } } impl Iterator for Xorshift32 { type Item = u32; fn next(&mut self) -> Option { Some(self.next()) } }