From fd4f217544f8036ac1b0a80ea06281de0ad26108 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 7 May 2021 17:37:59 +0200 Subject: [PATCH 01/58] net: Provide a raw NamedPipe implementation and builders --- examples/Cargo.toml | 12 +- examples/named-pipe-multi-client.rs | 73 ++++ examples/named-pipe.rs | 45 ++ tokio/Cargo.toml | 4 + tokio/src/macros/cfg.rs | 10 + tokio/src/net/mod.rs | 6 + tokio/src/net/windows/mod.rs | 4 + tokio/src/net/windows/named_pipe.rs | 644 ++++++++++++++++++++++++++++ tokio/tests/named_pipe.rs | 256 +++++++++++ 9 files changed, 1053 insertions(+), 1 deletion(-) create mode 100644 examples/named-pipe-multi-client.rs create mode 100644 examples/named-pipe.rs create mode 100644 tokio/src/net/windows/mod.rs create mode 100644 tokio/src/net/windows/named_pipe.rs create mode 100644 tokio/tests/named_pipe.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index cd27533412a..8083485c6f2 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # If you copy one of the examples into a new project, you should be using # [dependencies] instead. [dev-dependencies] -tokio = { version = "1.0.0", features = ["full", "tracing"] } +tokio = { path = "../tokio", version = "1.0.0", features = ["full", "tracing"] } tokio-util = { version = "0.6.3", features = ["full"] } tokio-stream = { version = "0.1" } @@ -23,6 +23,8 @@ httparse = "1.0" time = "0.1" once_cell = "1.5.2" +[target.'cfg(windows)'.dev-dependencies.winapi] +version = "0.3.8" [[example]] name = "chat" @@ -76,3 +78,11 @@ path = "custom-executor.rs" [[example]] name = "custom-executor-tokio-context" path = "custom-executor-tokio-context.rs" + +[[example]] +name = "named-pipe" +path = "named-pipe.rs" + +[[example]] +name = "named-pipe-multi-client" +path = "named-pipe-multi-client.rs" diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs new file mode 100644 index 00000000000..e9769528817 --- /dev/null +++ b/examples/named-pipe-multi-client.rs @@ -0,0 +1,73 @@ +use std::io; +use tokio::io::AsyncWriteExt as _; +use tokio::io::{AsyncBufReadExt as _, BufReader}; +use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; +use winapi::shared::winerror; + +const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; +const N: usize = 10; + +#[tokio::main] +async fn main() -> io::Result<()> { + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = tokio::spawn(async move { + for _ in 0..N { + let server = server_builder.create()?; + // Wait for client to connect. + server.connect().await?; + let mut server = BufReader::new(server); + + let _ = tokio::spawn(async move { + let mut buf = String::new(); + server.read_line(&mut buf).await?; + server.write_all(b"pong\n").await?; + server.flush().await?; + Ok::<_, io::Error>(()) + }); + } + + Ok::<_, io::Error>(()) + }); + + let mut clients = Vec::new(); + + for _ in 0..N { + let client_builder = client_builder.clone(); + + clients.push(tokio::spawn(async move { + let client = loop { + match client_builder.create() { + Ok(client) => break client, + Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), + Err(e) if e.kind() == io::ErrorKind::NotFound => (), + Err(e) => return Err(e), + } + + // This showcases a generic connect loop. + // + // We immediately try to create a client, if it's not found or + // the pipe is busy we use the specialized wait function on the + // client builder. + client_builder.wait(None).await?; + }; + + let mut client = BufReader::new(client); + + let mut buf = String::new(); + client.write_all(b"ping\n").await?; + client.flush().await?; + client.read_line(&mut buf).await?; + Ok::<_, io::Error>(buf) + })); + } + + for client in clients { + let result = client.await?; + assert_eq!(result?, "pong\n"); + } + + server.await??; + Ok(()) +} diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs new file mode 100644 index 00000000000..70c832101c5 --- /dev/null +++ b/examples/named-pipe.rs @@ -0,0 +1,45 @@ +use std::io; +use tokio::io::AsyncWriteExt as _; +use tokio::io::{AsyncBufReadExt as _, BufReader}; +use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + +const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; + +#[tokio::main] +async fn main() -> io::Result<()> { + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + + let server = tokio::spawn(async move { + // Note: we wait for a client to connect. + server.connect().await?; + + let mut server = BufReader::new(server); + + let mut buf = String::new(); + server.read_line(&mut buf).await?; + server.write_all(b"pong\n").await?; + Ok::<_, io::Error>(buf) + }); + + let client = tokio::spawn(async move { + client_builder.wait(None).await?; + let client = client_builder.create()?; + + let mut client = BufReader::new(client); + + let mut buf = String::new(); + client.write_all(b"ping\n").await?; + client.read_line(&mut buf).await?; + Ok::<_, io::Error>(buf) + }); + + let (server, client) = tokio::try_join!(server, client)?; + + assert_eq!(server?, "ping\n"); + assert_eq!(client?, "pong\n"); + + Ok(()) +} diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index fad01e1593b..f92ea419295 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -54,6 +54,7 @@ net = [ "mio/tcp", "mio/udp", "mio/uds", + "winapi/namedpipeapi", ] process = [ "bytes", @@ -115,6 +116,9 @@ version = "0.3.8" default-features = false optional = true +[target.'cfg(windows)'.dev-dependencies.ntapi] +version = "0.3.6" + [dev-dependencies] tokio-test = { version = "0.4.0", path = "../tokio-test" } tokio-stream = { version = "0.1", path = "../tokio-stream" } diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 3442612938b..b5154ed590a 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -183,6 +183,16 @@ macro_rules! cfg_net_unix { } } +macro_rules! cfg_net_windows { + ($($item:item)*) => { + $( + #[cfg(all(windows, feature = "net"))] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + $item + )* + } +} + macro_rules! cfg_process { ($($item:item)*) => { $( diff --git a/tokio/src/net/mod.rs b/tokio/src/net/mod.rs index 2f17f9eab5e..654fbf3e2ca 100644 --- a/tokio/src/net/mod.rs +++ b/tokio/src/net/mod.rs @@ -46,3 +46,9 @@ cfg_net_unix! { pub use unix::listener::UnixListener; pub use unix::stream::UnixStream; } + +cfg_net_windows! { + pub mod windows; + // TODO: should we re-export these? + // pub use windows::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder}; +} diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs new file mode 100644 index 00000000000..3d6b456decf --- /dev/null +++ b/tokio/src/net/windows/mod.rs @@ -0,0 +1,4 @@ +//! Windows specific network types. + +mod named_pipe; +pub use self::named_pipe::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs new file mode 100644 index 00000000000..b7b2dbf43ee --- /dev/null +++ b/tokio/src/net/windows/named_pipe.rs @@ -0,0 +1,644 @@ +use std::ffi::OsStr; +use std::fmt; +use std::io; +use std::os::windows::ffi::OsStrExt as _; +use std::os::windows::io::RawHandle; +use std::os::windows::io::{AsRawHandle, FromRawHandle}; +use std::pin::Pin; +use std::ptr; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; +use winapi::shared::minwindef::DWORD; +use winapi::um::fileapi; +use winapi::um::handleapi; +use winapi::um::minwinbase; +use winapi::um::namedpipeapi; +use winapi::um::winbase; +use winapi::um::winnt; + +use crate::fs::asyncify; +use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; + +// Interned constant to wait forever. Not available in winapi. +const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; + +/// A [Windows named pipe]. +/// +/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes +/// +/// Constructed using [NamedPipeClientBuilder::create] for clients, or +/// [NamedPipeBuilder::create] for servers. See their corresponding +/// documentation for examples. +#[derive(Debug)] +pub struct NamedPipe { + io: PollEvented, +} + +impl NamedPipe { + /// Fallibly construct a new named pipe from the specified raw handle. + /// + /// This function will consume ownership of the handle given, passing + /// responsibility for closing the handle to the returned object. + /// + /// This function is also unsafe as the primitives currently returned have + /// the contract that they are the sole owner of the file descriptor they + /// are wrapping. Usage of this function could accidentally allow violating + /// this contract which can cause memory unsafety in code that relies on it + /// being true. + /// + /// # Errors + /// + /// This errors if called outside of a [Tokio Runtime] which doesn't have + /// [I/O enabled]. + /// + /// [Tokio Runtime]: crate::runtime::Runtime + /// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io + unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { + let named_pipe = mio::windows::NamedPipe::from_raw_handle(handle); + + Ok(NamedPipe { + io: PollEvented::new(named_pipe)?, + }) + } + + /// Enables a named pipe server process to wait for a client process to + /// connect to an instance of a named pipe. A client process connects by + /// creating a named pipe with the same name. + /// + /// This corresponds to the [`ConnectNamedPipe`] system call. + /// + /// [`ConnectNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let pipe = builder.create()?; + /// + /// // Wait for a client to connect. + /// pipe.connect().await?; + /// + /// // Use the connected client... + /// # Ok(()) } + /// ``` + pub async fn connect(&self) -> io::Result<()> { + loop { + match self.io.connect() { + Ok(()) => break, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + self.io.registration().readiness(Interest::WRITABLE).await?; + } + Err(e) => return Err(e), + } + } + + Ok(()) + } + + /// Disconnects the server end of a named pipe instance from a client + /// process. + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let pipe = builder.create()?; + /// + /// // Wait for a client to connect. + /// pipe.connect().await?; + /// + /// // Use the pipe... + /// + /// // Forcibly disconnect the client (optional). + /// pipe.disconnect()?; + /// # Ok(()) } + /// ``` + pub fn disconnect(&self) -> io::Result<()> { + self.io.disconnect() + } +} + +impl AsyncRead for NamedPipe { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + unsafe { self.io.poll_read(cx, buf) } + } +} + +impl AsyncWrite for NamedPipe { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.io.poll_write(cx, buf) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + self.io.poll_write_vectored(cx, bufs) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) + } +} + +/// Raw handle conversion for [NamedPipe]. +/// +/// # Panics +/// +/// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O +/// enabled]. +/// +/// [Tokio Runtime]: crate::runtime::Runtime +/// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io +impl FromRawHandle for NamedPipe { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + Self::try_from_raw_handle(handle).unwrap() + } +} + +impl AsRawHandle for NamedPipe { + fn as_raw_handle(&self) -> RawHandle { + self.io.as_raw_handle() + } +} + +// Helper to set a boolean flag as a bitfield. +macro_rules! bool_flag { + ($f:expr, $t:expr, $flag:expr) => {{ + let current = $f; + + if $t { + $f = current | $flag; + } else { + $f = current & !$flag; + }; + }}; +} + +/// A builder structure for construct a named pipe with named pipe-specific +/// options. This is required to use for named pipe servers who wants to modify +/// pipe-related options. +/// +/// See [NamedPipeBuilder::create]. +#[derive(Clone)] +pub struct NamedPipeBuilder { + name: Arc<[u16]>, + open_mode: DWORD, + pipe_mode: DWORD, + max_instances: DWORD, + out_buffer_size: DWORD, + in_buffer_size: DWORD, + default_timeout: DWORD, +} + +impl NamedPipeBuilder { + /// Creates a new named pipe builder with the default settings. + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// # fn main() -> std::io::Result<()> { + /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let pipe = builder.create()?; + /// # Ok(()) } + /// ``` + pub fn new(addr: impl AsRef) -> NamedPipeBuilder { + NamedPipeBuilder { + name: addr + .as_ref() + .encode_wide() + .chain(Some(0)) + .collect::>(), + open_mode: winbase::PIPE_ACCESS_DUPLEX | winbase::FILE_FLAG_OVERLAPPED, + pipe_mode: winbase::PIPE_TYPE_BYTE, + max_instances: winbase::PIPE_UNLIMITED_INSTANCES, + out_buffer_size: 65536, + in_buffer_size: 65536, + default_timeout: 0, + } + } + + /// The pipe mode. + /// + /// The default pipe mode is [PipeMode::Byte]. See [PipeMode] for + /// documentation of what each mode means. + /// + /// This corresponding to specifying [dwPipeMode]. + /// + /// [nInBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + pub fn pipe_mode(mut self, pipe_mode: PipeMode) -> Self { + self.pipe_mode = match pipe_mode { + PipeMode::Byte => winbase::PIPE_TYPE_BYTE, + PipeMode::Message => winbase::PIPE_TYPE_MESSAGE, + }; + + self + } + + /// The flow of data in the pipe goes from client to server only. + /// + /// This corresponds to setting [PIPE_ACCESS_INBOUND]. + /// + /// [PIPE_ACCESS_INBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound + pub fn access_inbound(mut self, allowed: bool) -> Self { + bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_INBOUND); + self + } + + /// The flow of data in the pipe goes from server to client only. + /// + /// This corresponds to setting [PIPE_ACCESS_OUTBOUND]. + /// + /// [PIPE_ACCESS_OUTBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound + pub fn access_outbound(mut self, allowed: bool) -> Self { + bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_OUTBOUND); + self + } + + /// If you attempt to create multiple instances of a pipe with this flag, + /// creation of the first instance succeeds, but creation of the next + /// instance fails with [ERROR_ACCESS_DENIED]. + /// + /// This corresponds to setting [FILE_FLAG_FIRST_PIPE_INSTANCE]. + /// + /// [ERROR_ACCESS_DENIED]: winapi::shared::winerror::ERROR_ACCESS_DENIED + /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance + pub fn first_pipe_instance(mut self, first: bool) -> Self { + bool_flag!( + self.open_mode, + first, + winbase::FILE_FLAG_FIRST_PIPE_INSTANCE + ); + self + } + + /// Indicates whether this server can accept remote clients or not. + /// + /// This corresponds to setting [PIPE_REJECT_REMOTE_CLIENTS]. + /// + /// [PIPE_REJECT_REMOTE_CLIENTS]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients + pub fn reject_remote_clients(mut self, reject: bool) -> Self { + bool_flag!(self.pipe_mode, reject, winbase::PIPE_REJECT_REMOTE_CLIENTS); + self + } + + /// The maximum number of instances that can be created for this pipe. The + /// first instance of the pipe can specify this value; the same number must + /// be specified for other instances of the pipe. Acceptable values are in + /// the range 1 through 254. + /// + /// The default value is unlimited. This corresponds to specifying + /// [nMaxInstances]. + /// + /// [nMaxInstances]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [PIPE_UNLIMITED_INSTANCES]: winapi::um::winbase::PIPE_UNLIMITED_INSTANCES + /// + /// # Panics + /// + /// This function will panic if more than 254 instances are specified. If + /// you do not wish to set an instance limit, leave it unspecified. + /// + /// ```should_panic + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe").max_instances(255); + /// # Ok(()) } + /// ``` + pub fn max_instances(mut self, instances: usize) -> Self { + assert!(instances < 255, "cannot specify more than 254 instances"); + self.max_instances = instances as DWORD; + self + } + + /// The number of bytes to reserve for the output buffer. + /// + /// This corresponds to specifying [nOutBufferSize]. + /// + /// [nOutBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + pub fn out_buffer_size(mut self, buffer: u32) -> Self { + self.out_buffer_size = buffer as DWORD; + self + } + + /// The number of bytes to reserve for the input buffer. + /// + /// This corresponds to specifying [nInBufferSize]. + /// + /// [nInBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + pub fn in_buffer_size(mut self, buffer: u32) -> Self { + self.in_buffer_size = buffer as DWORD; + self + } + + /// Create the named pipe identified by the name provided in [new] for use + /// by a server. + /// + /// This function will call the [CreateNamedPipe] function and return the + /// result. + /// + /// [new]: NamedPipeBuilder::new + /// [CreateNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// # fn main() -> std::io::Result<()> { + /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let pipe = builder.create()?; + /// # Ok(()) } + /// ``` + pub fn create(&self) -> io::Result { + // Safety: We're calling create_with_security_attributes w/ a null + // pointer which disables it. + unsafe { self.create_with_security_attributes(ptr::null_mut()) } + } + + /// Create the named pipe identified by the name provided in [new] for use + /// by a server. + /// + /// This is the same as [create][NamedPipeBuilder::create] except that it + /// supports providing security attributes. + /// + /// [new]: NamedPipeBuilder::new + /// + /// # Safety + /// + /// The caller must ensure that `attrs` points to an initialized instance of + /// a [SECURITY_ATTRIBUTES] structure. + /// + /// [SECURITY_ATTRIBUTES]: [winapi::um::minwinbase::SECURITY_ATTRIBUTES] + pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { + let h = namedpipeapi::CreateNamedPipeW( + self.name.as_ptr(), + self.open_mode, + self.pipe_mode, + self.max_instances, + self.out_buffer_size, + self.in_buffer_size, + self.default_timeout, + attrs as *mut minwinbase::SECURITY_ATTRIBUTES, + ); + + if h == handleapi::INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = PollEvented::new(io)?; + + Ok(NamedPipe { io }) + } +} + +impl fmt::Debug for NamedPipeBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = String::from_utf16_lossy(&self.name[..self.name.len() - 1]); + f.debug_struct("NamedPipeBuilder") + .field("name", &name) + .finish() + } +} + +/// A builder suitable for building and interacting with named pipes from the +/// client side. +/// +/// See [NamedPipeClientBuilder::create]. +#[derive(Clone)] +pub struct NamedPipeClientBuilder { + name: Arc<[u16]>, +} + +impl NamedPipeClientBuilder { + /// Creates a new named pipe builder with the default settings. + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeClientBuilder; + /// + /// # fn main() -> std::io::Result<()> { + /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let pipe = builder.create()?; + /// # Ok(()) } + /// ``` + pub fn new(addr: impl AsRef) -> Self { + Self { + name: addr + .as_ref() + .encode_wide() + .chain(Some(0)) + .collect::>(), + } + } + + /// Waits until either a time-out interval elapses or an instance of the + /// specified named pipe is available for connection (that is, the pipe's + /// server process has a pending [connect] operation on the pipe). + /// + /// This corresponds to the [`WaitNamedPipeW`] system call. + /// + /// [`WaitNamedPipeW`]: + /// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea + /// [connect]: NamedPipe::connect + /// + /// ```no_run + /// use tokio::net::windows::NamedPipeClientBuilder; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// + /// // Wait forever until a socket is available to be connected to. + /// builder.wait(None).await?; + /// + /// let pipe = builder.create()?; + /// # Ok(()) } + /// ``` + /// + /// # Panics + /// + /// Panics if the specified duration is larger than `0xffffffff` + /// milliseconds, which is roughly equal to 1193 hours. + /// + /// ```should_panic + /// use std::time::Duration; + /// use tokio::net::windows::NamedPipeClientBuilder; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// + /// builder.wait(Some(Duration::from_millis(0xffffffff))).await?; + /// # Ok(()) } + /// ``` + pub async fn wait(&self, timeout: Option) -> io::Result<()> { + let timeout = match timeout { + Some(timeout) => { + let timeout = timeout.as_millis(); + assert! { + timeout < NMPWAIT_WAIT_FOREVER as u128, + "timeout out of bounds, can wait at most {}ms, but got {}ms", NMPWAIT_WAIT_FOREVER - 1, + timeout + }; + timeout as DWORD + } + None => NMPWAIT_WAIT_FOREVER, + }; + + let name = self.name.clone(); + + // TODO: Is this the right thread pool to use? `WaitNamedPipeW` could + // potentially block for a fairly long time all though it's only + // expected to be used when connecting something which should be fairly + // constrained. + // + // But doing something silly like spawning hundreds of clients trying to + // connect to a named pipe server without a timeout could easily end up + // starving the thread pool. + let task = asyncify(move || { + // Safety: There's nothing unsafe about this. + let result = unsafe { namedpipeapi::WaitNamedPipeW(name.as_ptr(), timeout) }; + + if result == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(()) + }); + + task.await + } + + /// Open the named pipe identified by the name provided in [new]. + /// + /// This constructs the handle using [CreateFile]. + /// + /// [new]: NamedPipeClientBuilder::new + /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// + /// # Errors + /// + /// There are a few errors you should be aware of that you need to take into + /// account when creating a named pipe on the client side. + /// + /// * [ERROR_FILE_NOT_FOUND] - Which can be tested for using + /// [std::io::ErrorKind::NotFound]. This indicates that the named pipe + /// does not exist. Presumably the server is not up. + /// * [ERROR_PIPE_BUSY] - which needs to be tested for through a constant in + /// [winapi]. This error is raised when the named pipe has been created, + /// but the server is not currently waiting for a connection. + /// + /// [ERROR_FILE_NOT_FOUND]: winapi::shared::winerror::ERROR_FILE_NOT_FOUND + /// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY + /// + /// The generic connect loop looks like this. + /// + /// ```no_run + /// use std::io; + /// use std::time::Duration; + /// use tokio::net::windows::NamedPipeClientBuilder; + /// use winapi::shared::winerror; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// + /// let client = loop { + /// match builder.create() { + /// Ok(client) => break client, + /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), + /// Err(e) if e.kind() == io::ErrorKind::NotFound => (), + /// Err(e) => return Err(e), + /// } + /// + /// if builder.wait(Some(Duration::from_secs(5))).await.is_err() { + /// return Err(io::Error::new(io::ErrorKind::Other, "server timed out")); + /// } + /// }; + /// + /// // use the connected client. + /// # Ok(()) } + /// ``` + pub fn create(&self) -> io::Result { + // Safety: We're calling create_with_security_attributes w/ a null + // pointer which disables it. + unsafe { self.create_with_security_attributes(ptr::null_mut()) } + } + + /// Open the named pipe identified by the name provided in [new]. + /// + /// This is the same as [create][NamedPipeClientBuilder::create] except that + /// it supports providing security attributes. + /// + /// [new]: NamedPipeClientBuilder::new + /// + /// # Safety + /// + /// The caller must ensure that `attrs` points to an initialized instance + /// of a [SECURITY_ATTRIBUTES] structure. + /// + /// [SECURITY_ATTRIBUTES]: [winapi::um::minwinbase::SECURITY_ATTRIBUTES] + pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { + // NB: We could use a platform specialized `OpenOptions` here, but since + // we have access to winapi it ultimately doesn't hurt to use + // `CreateFile` explicitly since it allows the use of our already + // well-structured wide `name` to pass into CreateFileW. + let h = fileapi::CreateFileW( + self.name.as_ptr(), + winnt::GENERIC_READ | winnt::GENERIC_WRITE, + 0, + attrs as *mut minwinbase::SECURITY_ATTRIBUTES, + fileapi::OPEN_EXISTING, + winbase::FILE_FLAG_OVERLAPPED | winbase::SECURITY_IDENTIFICATION, + ptr::null_mut(), + ); + + if h == handleapi::INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = PollEvented::new(io)?; + + Ok(NamedPipe { io }) + } +} + +impl fmt::Debug for NamedPipeClientBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = String::from_utf16_lossy(&self.name[..self.name.len() - 1]); + f.debug_struct("NamedPipeClientBuilder") + .field("name", &name) + .finish() + } +} + +/// The pipe mode of a [NamedPipe]. +/// +/// Set through [NamedPipeBuilder::pipe_mode]. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum PipeMode { + /// Data is written to the pipe as a stream of bytes. The pipe does not + /// distinguish bytes written during different write operations. + Byte, + /// Data is written to the pipe as a stream of messages. The pipe treats the + /// bytes written during each write operation as a message unit. Any reading + /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not + /// read completely. + /// + /// [ERROR_MORE_DATA]: winapi::shared::winerror::ERROR_MORE_DATA + Message, +} diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs new file mode 100644 index 00000000000..cf57d01c44d --- /dev/null +++ b/tokio/tests/named_pipe.rs @@ -0,0 +1,256 @@ +use std::io; +use std::mem; +use std::os::windows::io::AsRawHandle; +use tokio::io::AsyncWriteExt as _; +use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; +use winapi::shared::winerror; + +#[tokio::test] +async fn test_named_pipe_client_drop() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let mut server = server_builder.create()?; + assert_eq!(num_instances("test-named-pipe-client-drop")?, 1); + + let client = client_builder.create()?; + + server.connect().await?; + drop(client); + + // instance will be broken because client is gone + match server.write_all(b"ping").await { + Err(e) if e.raw_os_error() == Some(winerror::ERROR_NO_DATA as i32) => (), + x => panic!("{:?}", x), + } + + Ok(()) +} + +// This tests what happens when a client tries to disconnect. +#[tokio::test] +async fn test_named_pipe_client_disconnect() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-disconnect"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + let client = client_builder.create()?; + server.connect().await?; + + let e = client.disconnect().unwrap_err(); + assert_eq!( + e.raw_os_error(), + Some(winerror::ERROR_INVALID_FUNCTION as i32) + ); + Ok(()) +} + +// This tests what happens when a client tries to disconnect. +#[tokio::test] +async fn test_named_pipe_client_connect() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-connect"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + let client = client_builder.create()?; + server.connect().await?; + + let e = client.connect().await.unwrap_err(); + assert_eq!( + e.raw_os_error(), + Some(winerror::ERROR_INVALID_FUNCTION as i32) + ); + Ok(()) +} + +#[tokio::test] +async fn test_named_pipe_single_client() -> io::Result<()> { + use tokio::io::{AsyncBufReadExt as _, BufReader}; + + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-single-client"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + + let server = tokio::spawn(async move { + // Note: we wait for a client to connect. + server.connect().await?; + + let mut server = BufReader::new(server); + + let mut buf = String::new(); + server.read_line(&mut buf).await?; + server.write_all(b"pong\n").await?; + Ok::<_, io::Error>(buf) + }); + + let client = tokio::spawn(async move { + client_builder.wait(None).await?; + + let client = client_builder.create()?; + + let mut client = BufReader::new(client); + + let mut buf = String::new(); + client.write_all(b"ping\n").await?; + client.read_line(&mut buf).await?; + Ok::<_, io::Error>(buf) + }); + + let (server, client) = tokio::try_join!(server, client)?; + + assert_eq!(server?, "ping\n"); + assert_eq!(client?, "pong\n"); + + Ok(()) +} + +#[tokio::test] +async fn test_named_pipe_multi_client() -> io::Result<()> { + use tokio::io::{AsyncBufReadExt as _, BufReader}; + + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-multi-client"; + const N: usize = 10; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = tokio::spawn(async move { + for _ in 0..N { + let server = server_builder.create()?; + // Wait for client to connect. + server.connect().await?; + let mut server = BufReader::new(server); + + let _ = tokio::spawn(async move { + let mut buf = String::new(); + server.read_line(&mut buf).await?; + server.write_all(b"pong\n").await?; + server.flush().await?; + Ok::<_, io::Error>(()) + }); + } + + Ok::<_, io::Error>(()) + }); + + let mut clients = Vec::new(); + + for _ in 0..N { + let client_builder = client_builder.clone(); + + clients.push(tokio::spawn(async move { + // This showcases a generic connect loop. + // + // We immediately try to create a client, if it's not found or the + // pipe is busy we use the specialized wait function on the client + // builder. + let client = loop { + match client_builder.create() { + Ok(client) => break client, + Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), + Err(e) if e.kind() == io::ErrorKind::NotFound => (), + Err(e) => return Err(e), + } + + // Wait for a named pipe to become available. + client_builder.wait(None).await?; + }; + + let mut client = BufReader::new(client); + + let mut buf = String::new(); + client.write_all(b"ping\n").await?; + client.flush().await?; + client.read_line(&mut buf).await?; + Ok::<_, io::Error>(buf) + })); + } + + for client in clients { + let result = client.await?; + assert_eq!(result?, "pong\n"); + } + + server.await??; + Ok(()) +} + +// This tests what happens when a client tries to disconnect. +#[tokio::test] +async fn test_named_pipe_mode_message() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-mode-message"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME).pipe_mode(PipeMode::Message); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + let client = client_builder.create()?; + server.connect().await?; + + let e = client.connect().await.unwrap_err(); + assert_eq!( + e.raw_os_error(), + Some(winerror::ERROR_INVALID_FUNCTION as i32) + ); + Ok(()) +} + +fn num_instances(pipe_name: impl AsRef) -> io::Result { + use ntapi::ntioapi; + use winapi::shared::ntdef; + + let mut name = pipe_name.as_ref().encode_utf16().collect::>(); + let mut name = ntdef::UNICODE_STRING { + Length: (name.len() * mem::size_of::()) as u16, + MaximumLength: (name.len() * mem::size_of::()) as u16, + Buffer: name.as_mut_ptr(), + }; + let root = std::fs::File::open(r"\\.\Pipe\")?; + let mut io_status_block = unsafe { mem::zeroed() }; + let mut file_directory_information = [0_u8; 1024]; + + let status = unsafe { + ntioapi::NtQueryDirectoryFile( + root.as_raw_handle(), + std::ptr::null_mut(), + None, + std::ptr::null_mut(), + &mut io_status_block, + &mut file_directory_information as *mut _ as *mut _, + 1024, + ntioapi::FileDirectoryInformation, + 0, + &mut name, + 0, + ) + }; + + if status as u32 != winerror::NO_ERROR { + return Err(io::Error::last_os_error()); + } + + let info = unsafe { + mem::transmute::<_, &ntioapi::FILE_DIRECTORY_INFORMATION>(&file_directory_information) + }; + let raw_name = unsafe { + std::slice::from_raw_parts( + info.FileName.as_ptr(), + info.FileNameLength as usize / mem::size_of::(), + ) + }; + let name = String::from_utf16(raw_name).unwrap(); + let num_instances = unsafe { *info.EndOfFile.QuadPart() }; + + assert_eq!(name, pipe_name.as_ref()); + + Ok(num_instances as u32) +} From 11b486f76f70e6116b4abc698aed9d9612b40975 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 7 May 2021 20:04:54 +0200 Subject: [PATCH 02/58] rustfmt --- tokio/src/net/windows/named_pipe.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index b7b2dbf43ee..23d9f296a62 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -313,7 +313,7 @@ impl NamedPipeBuilder { /// /// This function will panic if more than 254 instances are specified. If /// you do not wish to set an instance limit, leave it unspecified. - /// + /// /// ```should_panic /// use tokio::net::windows::NamedPipeBuilder; /// @@ -473,7 +473,7 @@ impl NamedPipeClientBuilder { /// /// Panics if the specified duration is larger than `0xffffffff` /// milliseconds, which is roughly equal to 1193 hours. - /// + /// /// ```should_panic /// use std::time::Duration; /// use tokio::net::windows::NamedPipeClientBuilder; From 006f364ac7a555b0572b857d44bfcf679d8b1d1b Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 7 May 2021 20:10:11 +0200 Subject: [PATCH 03/58] only test named pipes on windows --- tokio/tests/named_pipe.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index cf57d01c44d..69e967a6d47 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -1,3 +1,6 @@ +#![cfg(feature = "full")] +#![cfg(all(windows))] + use std::io; use std::mem; use std::os::windows::io::AsRawHandle; From 036a9a3077c12610be9fdf06546af024d40c21b6 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 00:21:36 +0200 Subject: [PATCH 04/58] examples can't use path dependencies --- examples/Cargo.toml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8083485c6f2..4e4ec5822fd 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # If you copy one of the examples into a new project, you should be using # [dependencies] instead. [dev-dependencies] -tokio = { path = "../tokio", version = "1.0.0", features = ["full", "tracing"] } +tokio = { version = "1.0.0", features = ["full", "tracing"] } tokio-util = { version = "0.6.3", features = ["full"] } tokio-stream = { version = "0.1" } @@ -79,10 +79,11 @@ path = "custom-executor.rs" name = "custom-executor-tokio-context" path = "custom-executor-tokio-context.rs" -[[example]] -name = "named-pipe" -path = "named-pipe.rs" +# TODO: uncomment once / if released +# [[example]] +# name = "named-pipe" +# path = "named-pipe.rs" -[[example]] -name = "named-pipe-multi-client" -path = "named-pipe-multi-client.rs" +# [[example]] +# name = "named-pipe-multi-client" +# path = "named-pipe-multi-client.rs" From 97a894218c74e906408e91b5a8b7b86b088aeee3 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 14:36:10 +0200 Subject: [PATCH 05/58] re-enable examples --- examples/Cargo.toml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 757b89367b5..cf5fcbd48e9 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -79,11 +79,10 @@ path = "custom-executor.rs" name = "custom-executor-tokio-context" path = "custom-executor-tokio-context.rs" -# TODO: uncomment once / if released -# [[example]] -# name = "named-pipe" -# path = "named-pipe.rs" - -# [[example]] -# name = "named-pipe-multi-client" -# path = "named-pipe-multi-client.rs" +[[example]] +name = "named-pipe" +path = "named-pipe.rs" + +[[example]] +name = "named-pipe-multi-client" +path = "named-pipe-multi-client.rs" From aa950e7e13f3f710f036698a13b842c8396a7a5d Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 18:10:05 +0200 Subject: [PATCH 06/58] add access controls for client and info fn --- tokio/src/net/windows/mod.rs | 2 +- tokio/src/net/windows/named_pipe.rs | 169 +++++++++++++++++++++++++--- tokio/tests/named_pipe.rs | 116 ++++++++++++++++++- 3 files changed, 266 insertions(+), 21 deletions(-) diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index 3d6b456decf..ad7835a14ac 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -1,4 +1,4 @@ //! Windows specific network types. mod named_pipe; -pub use self::named_pipe::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; +pub use self::named_pipe::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeMode, PipeEnd}; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 23d9f296a62..c4e9366f8f3 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1,6 +1,7 @@ use std::ffi::OsStr; use std::fmt; use std::io; +use std::mem; use std::os::windows::ffi::OsStrExt as _; use std::os::windows::io::RawHandle; use std::os::windows::io::{AsRawHandle, FromRawHandle}; @@ -9,10 +10,9 @@ use std::ptr; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use winapi::shared::minwindef::DWORD; +use winapi::shared::minwindef::{DWORD, FALSE}; use winapi::um::fileapi; use winapi::um::handleapi; -use winapi::um::minwinbase; use winapi::um::namedpipeapi; use winapi::um::winbase; use winapi::um::winnt; @@ -66,15 +66,15 @@ impl NamedPipe { /// connect to an instance of a named pipe. A client process connects by /// creating a named pipe with the same name. /// - /// This corresponds to the [`ConnectNamedPipe`] system call. + /// This corresponds to the [ConnectNamedPipe] system call. /// - /// [`ConnectNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe + /// [ConnectNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe /// /// ```no_run /// use tokio::net::windows::NamedPipeBuilder; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); /// let pipe = builder.create()?; /// /// // Wait for a client to connect. @@ -104,7 +104,7 @@ impl NamedPipe { /// use tokio::net::windows::NamedPipeBuilder; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); /// let pipe = builder.create()?; /// /// // Wait for a client to connect. @@ -119,6 +119,80 @@ impl NamedPipe { pub fn disconnect(&self) -> io::Result<()> { self.io.disconnect() } + + /// Retrieves information about the current named pipe. + /// + /// ```no_run + /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode, PipeEnd}; + /// + /// # fn main() -> std::io::Result<()> { + /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; + /// + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME) + /// .pipe_mode(PipeMode::Message) + /// .max_instances(5); + /// + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// + /// let server = server_builder.create()?; + /// let client = client_builder.create()?; + /// + /// let server_info = server.info()?; + /// let client_info = client.info()?; + /// + /// assert_eq!(server_info.end, PipeEnd::Server); + /// assert_eq!(server_info.mode, PipeMode::Message); + /// assert_eq!(server_info.max_instances, 5); + /// + /// assert_eq!(client_info.end, PipeEnd::Client); + /// assert_eq!(client_info.mode, PipeMode::Message); + /// assert_eq!(client_info.max_instances, 5); + /// # Ok(()) } + /// ``` + pub fn info(&self) -> io::Result { + unsafe { + let mut flags = mem::MaybeUninit::uninit(); + let mut out_buffer_size = mem::MaybeUninit::uninit(); + let mut in_buffer_size = mem::MaybeUninit::uninit(); + let mut max_instances = mem::MaybeUninit::uninit(); + + let result = namedpipeapi::GetNamedPipeInfo( + self.io.as_raw_handle(), + flags.as_mut_ptr(), + out_buffer_size.as_mut_ptr(), + in_buffer_size.as_mut_ptr(), + max_instances.as_mut_ptr(), + ); + + if result == FALSE { + return Err(io::Error::last_os_error()); + } + + let flags = flags.assume_init(); + let out_buffer_size = out_buffer_size.assume_init(); + let in_buffer_size = in_buffer_size.assume_init(); + let max_instances = max_instances.assume_init(); + + let mut end = PipeEnd::Client; + let mut mode = PipeMode::Byte; + + if flags & winbase::PIPE_SERVER_END != 0 { + end = PipeEnd::Server; + } + + if flags & winbase::PIPE_TYPE_MESSAGE != 0 { + mode = PipeMode::Message; + } + + Ok(PipeInfo { + end, + mode, + out_buffer_size, + in_buffer_size, + max_instances, + }) + } + } } impl AsyncRead for NamedPipe { @@ -214,7 +288,7 @@ impl NamedPipeBuilder { /// use tokio::net::windows::NamedPipeBuilder; /// /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); /// let pipe = builder.create()?; /// # Ok(()) } /// ``` @@ -318,7 +392,7 @@ impl NamedPipeBuilder { /// use tokio::net::windows::NamedPipeBuilder; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe").max_instances(255); + /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe").max_instances(255); /// # Ok(()) } /// ``` pub fn max_instances(mut self, instances: usize) -> Self { @@ -360,7 +434,7 @@ impl NamedPipeBuilder { /// use tokio::net::windows::NamedPipeBuilder; /// /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); /// let pipe = builder.create()?; /// # Ok(()) } /// ``` @@ -393,7 +467,7 @@ impl NamedPipeBuilder { self.out_buffer_size, self.in_buffer_size, self.default_timeout, - attrs as *mut minwinbase::SECURITY_ATTRIBUTES, + attrs as *mut _, ); if h == handleapi::INVALID_HANDLE_VALUE { @@ -423,6 +497,7 @@ impl fmt::Debug for NamedPipeBuilder { #[derive(Clone)] pub struct NamedPipeClientBuilder { name: Arc<[u16]>, + desired_access: DWORD, } impl NamedPipeClientBuilder { @@ -432,7 +507,7 @@ impl NamedPipeClientBuilder { /// use tokio::net::windows::NamedPipeClientBuilder; /// /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); /// let pipe = builder.create()?; /// # Ok(()) } /// ``` @@ -443,6 +518,7 @@ impl NamedPipeClientBuilder { .encode_wide() .chain(Some(0)) .collect::>(), + desired_access: winnt::GENERIC_READ | winnt::GENERIC_WRITE, } } @@ -460,7 +536,7 @@ impl NamedPipeClientBuilder { /// use tokio::net::windows::NamedPipeClientBuilder; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); /// /// // Wait forever until a socket is available to be connected to. /// builder.wait(None).await?; @@ -479,7 +555,7 @@ impl NamedPipeClientBuilder { /// use tokio::net::windows::NamedPipeClientBuilder; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); /// /// builder.wait(Some(Duration::from_millis(0xffffffff))).await?; /// # Ok(()) } @@ -512,7 +588,7 @@ impl NamedPipeClientBuilder { // Safety: There's nothing unsafe about this. let result = unsafe { namedpipeapi::WaitNamedPipeW(name.as_ptr(), timeout) }; - if result == 0 { + if result == FALSE { return Err(io::Error::last_os_error()); } @@ -522,6 +598,28 @@ impl NamedPipeClientBuilder { task.await } + /// If the client supports reading data. This is enabled by default. + /// + /// This corresponds to setting [GENERIC_READ] in the call to [CreateFile]. + /// + /// [GENERIC_READ]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights + /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + pub fn read(mut self, allowed: bool) -> Self { + bool_flag!(self.desired_access, allowed, winnt::GENERIC_READ); + self + } + + /// If the created pipe supports writing data. This is enabled by default. + /// + /// This corresponds to setting [GENERIC_WRITE] in the call to [CreateFile]. + /// + /// [GENERIC_WRITE]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights + /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + pub fn write(mut self, allowed: bool) -> Self { + bool_flag!(self.desired_access, allowed, winnt::GENERIC_WRITE); + self + } + /// Open the named pipe identified by the name provided in [new]. /// /// This constructs the handle using [CreateFile]. @@ -553,7 +651,7 @@ impl NamedPipeClientBuilder { /// use winapi::shared::winerror; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new("\\\\.\\pipe\\mynamedpipe"); + /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); /// /// let client = loop { /// match builder.create() { @@ -597,9 +695,9 @@ impl NamedPipeClientBuilder { // well-structured wide `name` to pass into CreateFileW. let h = fileapi::CreateFileW( self.name.as_ptr(), - winnt::GENERIC_READ | winnt::GENERIC_WRITE, + self.desired_access, 0, - attrs as *mut minwinbase::SECURITY_ATTRIBUTES, + attrs as *mut _, fileapi::OPEN_EXISTING, winbase::FILE_FLAG_OVERLAPPED | winbase::SECURITY_IDENTIFICATION, ptr::null_mut(), @@ -628,17 +726,52 @@ impl fmt::Debug for NamedPipeClientBuilder { /// The pipe mode of a [NamedPipe]. /// /// Set through [NamedPipeBuilder::pipe_mode]. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeMode { /// Data is written to the pipe as a stream of bytes. The pipe does not /// distinguish bytes written during different write operations. + /// + /// Corresponds to [PIPE_TYPE_BYTE][winbase::PIPE_TYPE_BYTE]. Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not /// read completely. /// + /// Corresponds to [PIPE_TYPE_MESSAGE][winbase::PIPE_TYPE_MESSAGE]. + /// /// [ERROR_MORE_DATA]: winapi::shared::winerror::ERROR_MORE_DATA Message, } + +/// Indicates the end of a [NamedPipe]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum PipeEnd { + /// The [NamedPipe] refers to the client end of a named pipe instance. + /// + /// Corresponds to [PIPE_CLIENT_END][winbase::PIPE_CLIENT_END]. + Client, + /// The [NamedPipe] refers to the server end of a named pipe instance. + /// + /// Corresponds to [PIPE_SERVER_END][winbase::PIPE_SERVER_END]. + Server, +} + +/// Information about a named pipe. +/// +/// Constructed through [NamedPipe::info]. +#[derive(Debug)] +pub struct PipeInfo { + /// Indicates the mode of a named pipe. + pub mode: PipeMode, + /// Indicates the end of a named pipe. + pub end: PipeEnd, + /// The maximum number of instances that can be created for this pipe. + pub max_instances: u32, + /// The number of bytes to reserve for the output buffer. + pub out_buffer_size: u32, + /// The number of bytes to reserve for the input buffer. + pub in_buffer_size: u32, +} diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 69e967a6d47..c6d7e34b287 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -4,10 +4,122 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; -use tokio::io::AsyncWriteExt as _; -use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; +use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeEnd, PipeMode}; use winapi::shared::winerror; +#[tokio::test] +async fn test_named_pipe_permissions() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-permissions"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME); + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + // Server side prevents connecting by denying inbound access, client errors + // when attempting to create the connection. + { + let server_builder = server_builder.clone().access_inbound(false); + let _server = server_builder.create()?; + + let e = client_builder.create().unwrap_err(); + assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + + // Disabling reading allows a client to connect, but leads to runtime + // error if a read is attempted. + let mut client = client_builder.clone().write(false).create()?; + + let e = client.write(b"ping").await.unwrap_err(); + assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + } + + // Server side prevents connecting by denying outbound access, client errors + // when attempting to create the connection. + { + let server_builder = server_builder.clone().access_outbound(false); + let _server = server_builder.create()?; + + let e = client_builder.create().unwrap_err(); + assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + + // Disabling reading allows a client to connect, but leads to runtime + // error if a read is attempted. + let mut client = client_builder.clone().read(false).create()?; + + let mut buf = [0u8; 4]; + let e = client.read(&mut buf).await.unwrap_err(); + assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + } + + // A functional, unidirectional server-to-client only communication. + { + let mut server = server_builder.clone().access_inbound(false).create()?; + let mut client = client_builder.clone().write(false).create()?; + + let write = server.write_all(b"ping"); + + let mut buf = [0u8; 4]; + let read = client.read_exact(&mut buf); + + let ((), read) = tokio::try_join!(write, read)?; + + assert_eq!(read, 4); + assert_eq!(&buf[..], b"ping"); + } + + // A functional, unidirectional client-to-server only communication. + { + let mut server = server_builder.clone().access_outbound(false).create()?; + let mut client = client_builder.clone().read(false).create()?; + + // TODO: Explain why this test doesn't work without calling connect + // first. + // + // Because I have no idea -- udoprog + server.connect().await?; + + let write = client.write_all(b"ping"); + + let mut buf = [0u8; 4]; + let read = server.read_exact(&mut buf); + + let ((), read) = tokio::try_join!(write, read)?; + + println!("done reading and writing"); + + assert_eq!(read, 4); + assert_eq!(&buf[..], b"ping"); + } + + Ok(()) +} + +#[tokio::test] +async fn test_named_pipe_info() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-info"; + + let server_builder = NamedPipeBuilder::new(PIPE_NAME) + .pipe_mode(PipeMode::Message) + .max_instances(5); + + let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + + let server = server_builder.create()?; + let client = client_builder.create()?; + + let server_info = server.info()?; + let client_info = client.info()?; + + assert_eq!(server_info.end, PipeEnd::Server); + assert_eq!(server_info.mode, PipeMode::Message); + assert_eq!(server_info.max_instances, 5); + + assert_eq!(client_info.end, PipeEnd::Client); + assert_eq!(client_info.mode, PipeMode::Message); + assert_eq!(server_info.max_instances, 5); + + Ok(()) +} + #[tokio::test] async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; From 82a74be15730c0677c30b2d39b0465e21485b0b0 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 18:15:02 +0200 Subject: [PATCH 07/58] new examples are only support on windows --- examples/named-pipe-multi-client.rs | 32 +++++++++++++++++++++-------- examples/named-pipe.rs | 28 +++++++++++++++++++------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index e9769528817..d91dbfe7ca6 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -1,14 +1,15 @@ use std::io; -use tokio::io::AsyncWriteExt as _; -use tokio::io::{AsyncBufReadExt as _, BufReader}; -use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; -use winapi::shared::winerror; -const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; -const N: usize = 10; +#[cfg(windows)] +async fn windows_main() -> io::Result<()> { + use tokio::io::AsyncWriteExt as _; + use tokio::io::{AsyncBufReadExt as _, BufReader}; + use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + use winapi::shared::winerror; + + const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; + const N: usize = 10; -#[tokio::main] -async fn main() -> io::Result<()> { let server_builder = NamedPipeBuilder::new(PIPE_NAME); let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); @@ -71,3 +72,18 @@ async fn main() -> io::Result<()> { server.await??; Ok(()) } + +#[tokio::main] +async fn main() -> io::Result<()> { + #[cfg(windows)] + { + windows_main().await?; + } + + #[cfg(not(windows))] + { + println!("Named pipes are only supported on Windows!"); + } + + Ok(()) +} diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index 70c832101c5..f0a0a1a502e 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -1,12 +1,13 @@ use std::io; -use tokio::io::AsyncWriteExt as _; -use tokio::io::{AsyncBufReadExt as _, BufReader}; -use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; -const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; +#[cfg(windows)] +async fn windows_main() -> io::Result<()> { + use tokio::io::AsyncWriteExt as _; + use tokio::io::{AsyncBufReadExt as _, BufReader}; + use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + + const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; -#[tokio::main] -async fn main() -> io::Result<()> { let server_builder = NamedPipeBuilder::new(PIPE_NAME); let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); @@ -43,3 +44,18 @@ async fn main() -> io::Result<()> { Ok(()) } + +#[tokio::main] +async fn main() -> io::Result<()> { + #[cfg(windows)] + { + windows_main().await?; + } + + #[cfg(not(windows))] + { + println!("Named pipes are only supported on Windows!"); + } + + Ok(()) +} From 8f2c1e93fe75f6e45a713a804c1ea939e661f9a7 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 18:46:20 +0200 Subject: [PATCH 08/58] add local asyncify for tokio::net --- tokio/src/net/mod.rs | 24 ++++++++++++++++++++++-- tokio/src/net/windows/named_pipe.rs | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tokio/src/net/mod.rs b/tokio/src/net/mod.rs index 654fbf3e2ca..6d8150aba4d 100644 --- a/tokio/src/net/mod.rs +++ b/tokio/src/net/mod.rs @@ -49,6 +49,26 @@ cfg_net_unix! { cfg_net_windows! { pub mod windows; - // TODO: should we re-export these? - // pub use windows::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder}; + + use std::io; + + pub(crate) async fn asyncify(f: F) -> io::Result + where + F: FnOnce() -> io::Result + Send + 'static, + T: Send + 'static, + { + match sys::run(f).await { + Ok(res) => res, + Err(_) => Err(io::Error::new( + io::ErrorKind::Other, + "background task failed", + )), + } + } + + /// Types in this module can be mocked out in tests. + mod sys { + // TODO: don't rename + pub(crate) use crate::blocking::spawn_blocking as run; + } } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index c4e9366f8f3..4377f97f495 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -17,8 +17,8 @@ use winapi::um::namedpipeapi; use winapi::um::winbase; use winapi::um::winnt; -use crate::fs::asyncify; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; +use crate::net::asyncify; // Interned constant to wait forever. Not available in winapi. const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; From ff4c6b30d0e431b528354df3770743960a186506 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 19:53:41 +0200 Subject: [PATCH 09/58] rustfmt --- tokio/src/net/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index ad7835a14ac..47a752e4dbb 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -1,4 +1,6 @@ //! Windows specific network types. mod named_pipe; -pub use self::named_pipe::{NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeMode, PipeEnd}; +pub use self::named_pipe::{ + NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeEnd, PipeMode, +}; From 89b05a9017bf3391b4b10215bb87bcf6fbf59c99 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 22:29:27 +0200 Subject: [PATCH 10/58] more documentation, tests, idiomatic listen and connect --- examples/named-pipe-multi-client.rs | 33 ++- examples/named-pipe.rs | 3 +- tokio/src/net/windows/named_pipe.rs | 423 +++++++++++++++++++++++++--- tokio/tests/named_pipe.rs | 165 ++--------- 4 files changed, 425 insertions(+), 199 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index d91dbfe7ca6..19c14162204 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -1,4 +1,6 @@ use std::io; +use std::time::Duration; +use tokio::time; #[cfg(windows)] async fn windows_main() -> io::Result<()> { @@ -7,24 +9,40 @@ async fn windows_main() -> io::Result<()> { use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; use winapi::shared::winerror; - const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; + const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client"; const N: usize = 10; let server_builder = NamedPipeBuilder::new(PIPE_NAME); let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + // The first server needs to be constructed early so that clients can + // be correctly connected. Otherwise a waiting client will error. + // + // Here we also make use of `first_pipe_instance`, which will ensure + // that there are no other servers up and running already. + let mut server = server_builder.clone().first_pipe_instance(true).create()?; + let server = tokio::spawn(async move { + // Artificial workload. + time::sleep(Duration::from_secs(1)).await; + for _ in 0..N { - let server = server_builder.create()?; // Wait for client to connect. server.connect().await?; - let mut server = BufReader::new(server); + let mut inner = BufReader::new(server); + + // Construct the next server to be connected before sending the one + // we already have of onto a task. This ensures that the server + // isn't closed (after it's done in the task) before a new one is + // available. Otherwise the client might error with + // `io::ErrorKind::NotFound`. + server = server_builder.create()?; let _ = tokio::spawn(async move { let mut buf = String::new(); - server.read_line(&mut buf).await?; - server.write_all(b"pong\n").await?; - server.flush().await?; + inner.read_line(&mut buf).await?; + inner.write_all(b"pong\n").await?; + inner.flush().await?; Ok::<_, io::Error>(()) }); } @@ -42,7 +60,6 @@ async fn windows_main() -> io::Result<()> { match client_builder.create() { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), - Err(e) if e.kind() == io::ErrorKind::NotFound => (), Err(e) => return Err(e), } @@ -51,7 +68,7 @@ async fn windows_main() -> io::Result<()> { // We immediately try to create a client, if it's not found or // the pipe is busy we use the specialized wait function on the // client builder. - client_builder.wait(None).await?; + client_builder.wait(Some(Duration::from_secs(5))).await?; }; let mut client = BufReader::new(client); diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index f0a0a1a502e..78ee4dbb7d2 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -2,6 +2,7 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { + use std::time::Duration; use tokio::io::AsyncWriteExt as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; @@ -26,7 +27,7 @@ async fn windows_main() -> io::Result<()> { }); let client = tokio::spawn(async move { - client_builder.wait(None).await?; + client_builder.wait(Some(Duration::from_secs(5))).await?; let client = client_builder.create()?; let mut client = BufReader::new(client); diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 4377f97f495..c9bbae52664 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -25,11 +25,105 @@ const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; /// A [Windows named pipe]. /// -/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes -/// /// Constructed using [NamedPipeClientBuilder::create] for clients, or /// [NamedPipeBuilder::create] for servers. See their corresponding /// documentation for examples. +/// +/// Connecting a client involves a few steps. First we must try to [create], the +/// error typically indicates one of two things: +/// +/// * [std::io::ErrorKind::NotFound] - There is no server available. +/// * [ERROR_PIPE_BUSY] - There is a server available, but it is busy. Use +/// [wait] until it becomes available. +/// +/// So a typical client connect loop will look like the this: +/// +/// ```no_run +/// use std::time::Duration; +/// use tokio::net::windows::NamedPipeClientBuilder; +/// use winapi::shared::winerror; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); +/// +/// let client = loop { +/// match client_builder.create() { +/// Ok(client) => break client, +/// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), +/// Err(e) => return Err(e), +/// } +/// +/// client_builder.wait(Some(Duration::from_secs(5))).await?; +/// }; +/// +/// /* use the connected client */ +/// # Ok(()) } +/// ``` +/// +/// A client will error with [std::io::ErrorKind::NotFound] for most creation +/// oriented operations like [create] or [wait] unless at least once server +/// instance is up and running at all time. This means that the typical listen +/// loop for a server is a bit involved, because we have to ensure that we never +/// drop a server accidentally while a client might want to connect. +/// +/// ```no_run +/// use std::io; +/// use std::sync::Arc; +/// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; +/// use tokio::sync::Notify; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-server"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let server_builder = NamedPipeBuilder::new(PIPE_NAME); +/// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); +/// +/// // The first server needs to be constructed early so that clients can +/// // be correctly connected. Otherwise calling .wait will cause the client to +/// // error. +/// // +/// // Here we also make use of `first_pipe_instance`, which will ensure that +/// // there are no other servers up and running already. +/// let mut server = server_builder.clone().first_pipe_instance(true).create()?; +/// +/// let shutdown = Arc::new(Notify::new()); +/// let shutdown2 = shutdown.clone(); +/// +/// // Spawn the server loop. +/// let server = tokio::spawn(async move { +/// loop { +/// // Wait for a client to connect. +/// let connected = tokio::select! { +/// connected = server.connect() => connected, +/// _ = shutdown2.notified() => break, +/// }; +/// +/// // Construct the next server to be connected before sending the one +/// // we already have of onto a task. This ensures that the server +/// // isn't closed (after it's done in the task) before a new one is +/// // available. Otherwise the client might error with +/// // `io::ErrorKind::NotFound`. +/// server = server_builder.create()?; +/// +/// let client = tokio::spawn(async move { +/// /* use the connected client */ +/// # Ok::<_, std::io::Error>(()) +/// }); +/// } +/// +/// Ok::<_, io::Error>(()) +/// }); +/// # shutdown.notify_one(); +/// # let _ = server.await??; +/// # Ok(()) } +/// ``` +/// +/// [create]: NamedPipeClientBuilder::create +/// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY +/// [wait]: NamedPipeClientBuilder::wait +/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { io: PollEvented, @@ -100,20 +194,30 @@ impl NamedPipe { /// Disconnects the server end of a named pipe instance from a client /// process. /// - /// ```no_run - /// use tokio::net::windows::NamedPipeBuilder; + /// ``` + /// use tokio::io::AsyncWriteExt as _; + /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + /// use winapi::shared::winerror; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-disconnect"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); - /// let pipe = builder.create()?; + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let server = server_builder.create()?; /// - /// // Wait for a client to connect. - /// pipe.connect().await?; + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// let mut client = client_builder.create()?; + /// + /// // Wait for a client to become connected. + /// server.connect().await?; /// - /// // Use the pipe... + /// // Forcibly disconnect the client. + /// server.disconnect()?; /// - /// // Forcibly disconnect the client (optional). - /// pipe.disconnect()?; + /// // Write fails with an OS-specific error after client has been + /// // disconnected. + /// let e = client.write(b"ping").await.unwrap_err(); + /// assert_eq!(e.raw_os_error(), Some(winerror::ERROR_PIPE_NOT_CONNECTED as i32)); /// # Ok(()) } /// ``` pub fn disconnect(&self) -> io::Result<()> { @@ -122,12 +226,12 @@ impl NamedPipe { /// Retrieves information about the current named pipe. /// - /// ```no_run + /// ``` /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode, PipeEnd}; /// - /// # fn main() -> std::io::Result<()> { - /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-info"; /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let server_builder = NamedPipeBuilder::new(PIPE_NAME) /// .pipe_mode(PipeMode::Message) /// .max_instances(5); @@ -146,7 +250,7 @@ impl NamedPipe { /// /// assert_eq!(client_info.end, PipeEnd::Client); /// assert_eq!(client_info.mode, PipeMode::Message); - /// assert_eq!(client_info.max_instances, 5); + /// assert_eq!(server_info.max_instances, 5); /// # Ok(()) } /// ``` pub fn info(&self) -> io::Result { @@ -239,7 +343,7 @@ impl AsyncWrite for NamedPipe { /// enabled]. /// /// [Tokio Runtime]: crate::runtime::Runtime -/// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io +/// [I/O enabled]: crate::runtime::Builder::enable_io impl FromRawHandle for NamedPipe { unsafe fn from_raw_handle(handle: RawHandle) -> Self { Self::try_from_raw_handle(handle).unwrap() @@ -284,12 +388,14 @@ pub struct NamedPipeBuilder { impl NamedPipeBuilder { /// Creates a new named pipe builder with the default settings. /// - /// ```no_run + /// ``` /// use tokio::net::windows::NamedPipeBuilder; /// - /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); - /// let pipe = builder.create()?; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-new"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let server = server_builder.create()?; /// # Ok(()) } /// ``` pub fn new(addr: impl AsRef) -> NamedPipeBuilder { @@ -300,7 +406,7 @@ impl NamedPipeBuilder { .chain(Some(0)) .collect::>(), open_mode: winbase::PIPE_ACCESS_DUPLEX | winbase::FILE_FLAG_OVERLAPPED, - pipe_mode: winbase::PIPE_TYPE_BYTE, + pipe_mode: winbase::PIPE_TYPE_BYTE | winbase::PIPE_REJECT_REMOTE_CLIENTS, max_instances: winbase::PIPE_UNLIMITED_INSTANCES, out_buffer_size: 65536, in_buffer_size: 65536, @@ -315,7 +421,7 @@ impl NamedPipeBuilder { /// /// This corresponding to specifying [dwPipeMode]. /// - /// [nInBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [dwPipeMode]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea pub fn pipe_mode(mut self, pipe_mode: PipeMode) -> Self { self.pipe_mode = match pipe_mode { PipeMode::Byte => winbase::PIPE_TYPE_BYTE, @@ -330,6 +436,54 @@ impl NamedPipeBuilder { /// This corresponds to setting [PIPE_ACCESS_INBOUND]. /// /// [PIPE_ACCESS_INBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound + /// + /// # Examples + /// + /// ``` + /// use std::io; + /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// + /// // Server side prevents connecting by denying inbound access, client errors + /// // when attempting to create the connection. + /// { + /// let server_builder = server_builder.clone().access_inbound(false); + /// let _server = server_builder.create()?; + /// + /// let e = client_builder.create().unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// + /// // Disabling writing allows a client to connect, but leads to runtime + /// // error if a write is attempted. + /// let mut client = client_builder.clone().write(false).create()?; + /// + /// let e = client.write(b"ping").await.unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// } + /// + /// // A functional, unidirectional server-to-client only communication. + /// { + /// let mut server = server_builder.clone().access_inbound(false).create()?; + /// let mut client = client_builder.clone().write(false).create()?; + /// + /// let write = server.write_all(b"ping"); + /// + /// let mut buf = [0u8; 4]; + /// let read = client.read_exact(&mut buf); + /// + /// let ((), read) = tokio::try_join!(write, read)?; + /// + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..], b"ping"); + /// } + /// # Ok(()) } + /// ``` pub fn access_inbound(mut self, allowed: bool) -> Self { bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_INBOUND); self @@ -340,6 +494,63 @@ impl NamedPipeBuilder { /// This corresponds to setting [PIPE_ACCESS_OUTBOUND]. /// /// [PIPE_ACCESS_OUTBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound + /// + /// # Examples + /// + /// ``` + /// use std::io; + /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// + /// // Server side prevents connecting by denying outbound access, client errors + /// // when attempting to create the connection. + /// { + /// let server_builder = server_builder.clone().access_outbound(false); + /// let _server = server_builder.create()?; + /// + /// let e = client_builder.create().unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// + /// // Disabling reading allows a client to connect, but leads to runtime + /// // error if a read is attempted. + /// let mut client = client_builder.clone().read(false).create()?; + /// + /// let mut buf = [0u8; 4]; + /// let e = client.read(&mut buf).await.unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// } + /// + /// // A functional, unidirectional client-to-server only communication. + /// { + /// let mut server = server_builder.clone().access_outbound(false).create()?; + /// let mut client = client_builder.clone().read(false).create()?; + /// + /// // TODO: Explain why this test doesn't work without calling connect + /// // first. + /// // + /// // Because I have no idea -- udoprog + /// server.connect().await?; + /// + /// let write = client.write_all(b"ping"); + /// + /// let mut buf = [0u8; 4]; + /// let read = server.read_exact(&mut buf); + /// + /// let ((), read) = tokio::try_join!(write, read)?; + /// + /// println!("done reading and writing"); + /// + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..], b"ping"); + /// } + /// # Ok(()) } + /// ``` pub fn access_outbound(mut self, allowed: bool) -> Self { bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_OUTBOUND); self @@ -353,6 +564,27 @@ impl NamedPipeBuilder { /// /// [ERROR_ACCESS_DENIED]: winapi::shared::winerror::ERROR_ACCESS_DENIED /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance + /// + /// # Examples + /// + /// ``` + /// use std::io; + /// use tokio::net::windows::NamedPipeBuilder; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-first-instance"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let builder = NamedPipeBuilder::new(PIPE_NAME).first_pipe_instance(true); + /// + /// let server = builder.create()?; + /// let e = builder.create().unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// drop(server); + /// + /// // OK: since, we've closed the other instance. + /// let _server2 = builder.create()?; + /// # Ok(()) } + /// ``` pub fn first_pipe_instance(mut self, first: bool) -> Self { bool_flag!( self.open_mode, @@ -362,7 +594,8 @@ impl NamedPipeBuilder { self } - /// Indicates whether this server can accept remote clients or not. + /// Indicates whether this server can accept remote clients or not. This is + /// enabled by default. /// /// This corresponds to setting [PIPE_REJECT_REMOTE_CLIENTS]. /// @@ -375,10 +608,9 @@ impl NamedPipeBuilder { /// The maximum number of instances that can be created for this pipe. The /// first instance of the pipe can specify this value; the same number must /// be specified for other instances of the pipe. Acceptable values are in - /// the range 1 through 254. + /// the range 1 through 254. The default value is unlimited. /// - /// The default value is unlimited. This corresponds to specifying - /// [nMaxInstances]. + /// This corresponds to specifying [nMaxInstances]. /// /// [nMaxInstances]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// [PIPE_UNLIMITED_INSTANCES]: winapi::um::winbase::PIPE_UNLIMITED_INSTANCES @@ -430,12 +662,14 @@ impl NamedPipeBuilder { /// [new]: NamedPipeBuilder::new /// [CreateNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// - /// ```no_run + /// ``` /// use tokio::net::windows::NamedPipeBuilder; /// - /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); - /// let pipe = builder.create()?; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-create"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let server = server_builder.create()?; /// # Ok(()) } /// ``` pub fn create(&self) -> io::Result { @@ -503,12 +737,17 @@ pub struct NamedPipeClientBuilder { impl NamedPipeClientBuilder { /// Creates a new named pipe builder with the default settings. /// - /// ```no_run - /// use tokio::net::windows::NamedPipeClientBuilder; + /// ``` + /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; /// - /// # fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); - /// let pipe = builder.create()?; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-new"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// // Server must be created in order for the client creation to succeed. + /// let server = NamedPipeBuilder::new(PIPE_NAME).create()?; + /// + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// let client = client_builder.create()?; /// # Ok(()) } /// ``` pub fn new(addr: impl AsRef) -> Self { @@ -532,16 +771,115 @@ impl NamedPipeClientBuilder { /// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea /// [connect]: NamedPipe::connect /// - /// ```no_run + /// # Errors + /// + /// If a server hasn't already created the named pipe, this will return an + /// error with the kind [std::io::ErrorKind::NotFound]. + /// + /// ``` + /// use std::io; + /// use std::time::Duration; /// use tokio::net::windows::NamedPipeClientBuilder; /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error1"; + /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); /// - /// // Wait forever until a socket is available to be connected to. - /// builder.wait(None).await?; + /// let e = client_builder.wait(Some(Duration::from_secs(1))).await.unwrap_err(); /// - /// let pipe = builder.create()?; + /// // Errors because no server exists. + /// assert_eq!(e.kind(), io::ErrorKind::NotFound); + /// # Ok(()) } + /// ``` + /// + /// Waiting while a server is being closed will first cause it to block, but + /// then error with [std::io::ErrorKind::NotFound]. + /// + /// ``` + /// use std::io; + /// use std::time::Duration; + /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error2"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// + /// let server = server_builder.create()?; + /// + /// // Construct a client that occupies the server so that the next one is + /// // forced to wait. + /// let _client = client_builder.create()?; + /// + /// tokio::spawn(async move { + /// // Drop the server after 100ms, causing the waiting client to err. + /// tokio::time::sleep(Duration::from_millis(100)).await; + /// drop(server); + /// }); + /// + /// let e = client_builder.wait(Some(Duration::from_secs(1))).await.unwrap_err(); + /// + /// assert_eq!(e.kind(), io::ErrorKind::NotFound); + /// # Ok(()) } + /// ``` + /// + /// # Examples + /// + /// ``` + /// use std::io; + /// use std::time::Duration; + /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + /// use winapi::shared::winerror; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); + /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + /// + /// // The first server needs to be constructed early so that clients can + /// // be correctly connected. Otherwise calling .wait will cause the client + /// // to error because the file descriptor doesn't exist. + /// // + /// // Here we also make use of `first_pipe_instance`, which will ensure + /// // that there are no other servers up and running already. + /// let mut server = server_builder.clone().first_pipe_instance(true).create()?; + /// + /// let client = tokio::spawn(async move { + /// // Wait forever until a socket is available to be connected to. + /// let mut client = loop { + /// match client_builder.create() { + /// Ok(client) => break client, + /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), + /// Err(e) => return Err(e), + /// } + /// + /// client_builder.wait(Some(Duration::from_secs(5))).await?; + /// }; + /// + /// let mut buf = [0u8; 4]; + /// client.read_exact(&mut buf[..]).await?; + /// Ok::<_, io::Error>(buf) + /// }); + /// + /// let server = tokio::spawn(async move { + /// tokio::time::sleep(Duration::from_millis(200)).await; + /// + /// // Calling `connect` is necessary for the waiting client to wake up, + /// // even if the server is created after the client. + /// server.connect().await?; + /// + /// server.write_all(b"ping").await?; + /// Ok::<_, io::Error>(()) + /// }); + /// + /// let (client, server) = tokio::try_join!(client, server)?; + /// let payload = client?; + /// assert_eq!(&payload[..], b"ping"); + /// let _ = server?; /// # Ok(()) } /// ``` /// @@ -632,14 +970,12 @@ impl NamedPipeClientBuilder { /// There are a few errors you should be aware of that you need to take into /// account when creating a named pipe on the client side. /// - /// * [ERROR_FILE_NOT_FOUND] - Which can be tested for using - /// [std::io::ErrorKind::NotFound]. This indicates that the named pipe + /// * [std::io::ErrorKind::NotFound] - This indicates that the named pipe /// does not exist. Presumably the server is not up. /// * [ERROR_PIPE_BUSY] - which needs to be tested for through a constant in /// [winapi]. This error is raised when the named pipe has been created, /// but the server is not currently waiting for a connection. /// - /// [ERROR_FILE_NOT_FOUND]: winapi::shared::winerror::ERROR_FILE_NOT_FOUND /// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY /// /// The generic connect loop looks like this. @@ -657,7 +993,6 @@ impl NamedPipeClientBuilder { /// match builder.create() { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), - /// Err(e) if e.kind() == io::ErrorKind::NotFound => (), /// Err(e) => return Err(e), /// } /// diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index c6d7e34b287..a78cada6458 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -4,122 +4,10 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; -use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; -use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeEnd, PipeMode}; +use tokio::io::AsyncWriteExt as _; +use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; use winapi::shared::winerror; -#[tokio::test] -async fn test_named_pipe_permissions() -> io::Result<()> { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-permissions"; - - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - // Server side prevents connecting by denying inbound access, client errors - // when attempting to create the connection. - { - let server_builder = server_builder.clone().access_inbound(false); - let _server = server_builder.create()?; - - let e = client_builder.create().unwrap_err(); - assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - - // Disabling reading allows a client to connect, but leads to runtime - // error if a read is attempted. - let mut client = client_builder.clone().write(false).create()?; - - let e = client.write(b"ping").await.unwrap_err(); - assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - } - - // Server side prevents connecting by denying outbound access, client errors - // when attempting to create the connection. - { - let server_builder = server_builder.clone().access_outbound(false); - let _server = server_builder.create()?; - - let e = client_builder.create().unwrap_err(); - assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - - // Disabling reading allows a client to connect, but leads to runtime - // error if a read is attempted. - let mut client = client_builder.clone().read(false).create()?; - - let mut buf = [0u8; 4]; - let e = client.read(&mut buf).await.unwrap_err(); - assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - } - - // A functional, unidirectional server-to-client only communication. - { - let mut server = server_builder.clone().access_inbound(false).create()?; - let mut client = client_builder.clone().write(false).create()?; - - let write = server.write_all(b"ping"); - - let mut buf = [0u8; 4]; - let read = client.read_exact(&mut buf); - - let ((), read) = tokio::try_join!(write, read)?; - - assert_eq!(read, 4); - assert_eq!(&buf[..], b"ping"); - } - - // A functional, unidirectional client-to-server only communication. - { - let mut server = server_builder.clone().access_outbound(false).create()?; - let mut client = client_builder.clone().read(false).create()?; - - // TODO: Explain why this test doesn't work without calling connect - // first. - // - // Because I have no idea -- udoprog - server.connect().await?; - - let write = client.write_all(b"ping"); - - let mut buf = [0u8; 4]; - let read = server.read_exact(&mut buf); - - let ((), read) = tokio::try_join!(write, read)?; - - println!("done reading and writing"); - - assert_eq!(read, 4); - assert_eq!(&buf[..], b"ping"); - } - - Ok(()) -} - -#[tokio::test] -async fn test_named_pipe_info() -> io::Result<()> { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-info"; - - let server_builder = NamedPipeBuilder::new(PIPE_NAME) - .pipe_mode(PipeMode::Message) - .max_instances(5); - - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - let server = server_builder.create()?; - let client = client_builder.create()?; - - let server_info = server.info()?; - let client_info = client.info()?; - - assert_eq!(server_info.end, PipeEnd::Server); - assert_eq!(server_info.mode, PipeMode::Message); - assert_eq!(server_info.max_instances, 5); - - assert_eq!(client_info.end, PipeEnd::Client); - assert_eq!(client_info.mode, PipeMode::Message); - assert_eq!(server_info.max_instances, 5); - - Ok(()) -} - #[tokio::test] async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; @@ -144,26 +32,6 @@ async fn test_named_pipe_client_drop() -> io::Result<()> { Ok(()) } -// This tests what happens when a client tries to disconnect. -#[tokio::test] -async fn test_named_pipe_client_disconnect() -> io::Result<()> { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-disconnect"; - - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - let server = server_builder.create()?; - let client = client_builder.create()?; - server.connect().await?; - - let e = client.disconnect().unwrap_err(); - assert_eq!( - e.raw_os_error(), - Some(winerror::ERROR_INVALID_FUNCTION as i32) - ); - Ok(()) -} - // This tests what happens when a client tries to disconnect. #[tokio::test] async fn test_named_pipe_client_connect() -> io::Result<()> { @@ -238,18 +106,29 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { let server_builder = NamedPipeBuilder::new(PIPE_NAME); let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + // The first server needs to be constructed early so that clients can + // be correctly connected. Otherwise calling .wait will cause the client to + // error. + let mut server = server_builder.create()?; + let server = tokio::spawn(async move { for _ in 0..N { - let server = server_builder.create()?; // Wait for client to connect. server.connect().await?; - let mut server = BufReader::new(server); + let mut inner = BufReader::new(server); + + // Construct the next server to be connected before sending the one + // we already have of onto a task. This ensures that the server + // isn't closed (after it's done in the task) before a new one is + // available. Otherwise the client might error with + // `io::ErrorKind::NotFound`. + server = server_builder.create()?; let _ = tokio::spawn(async move { let mut buf = String::new(); - server.read_line(&mut buf).await?; - server.write_all(b"pong\n").await?; - server.flush().await?; + inner.read_line(&mut buf).await?; + inner.write_all(b"pong\n").await?; + inner.flush().await?; Ok::<_, io::Error>(()) }); } @@ -308,14 +187,8 @@ async fn test_named_pipe_mode_message() -> io::Result<()> { let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); let server = server_builder.create()?; - let client = client_builder.create()?; + let _ = client_builder.create()?; server.connect().await?; - - let e = client.connect().await.unwrap_err(); - assert_eq!( - e.raw_os_error(), - Some(winerror::ERROR_INVALID_FUNCTION as i32) - ); Ok(()) } From 7c07f797f0d7e7d38b04d21d3a402e595a58da44 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 8 May 2021 22:57:08 +0200 Subject: [PATCH 11/58] fix example --- examples/named-pipe-multi-client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index 19c14162204..ef2666b5240 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -1,12 +1,12 @@ use std::io; -use std::time::Duration; -use tokio::time; #[cfg(windows)] async fn windows_main() -> io::Result<()> { + use std::time::Duration; use tokio::io::AsyncWriteExt as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + use tokio::time; use winapi::shared::winerror; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client"; From ef03f09d26168d8fb51169b40db7a018df5ddb93 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sun, 9 May 2021 00:18:39 +0200 Subject: [PATCH 12/58] enable windows-specific docs to build on non-windows --- tokio/src/macros/cfg.rs | 4 +- tokio/src/net/windows/named_pipe.rs | 117 +++++++++++++++++----------- 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index b5154ed590a..fdb8a5fe1a8 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -186,8 +186,8 @@ macro_rules! cfg_net_unix { macro_rules! cfg_net_windows { ($($item:item)*) => { $( - #[cfg(all(windows, feature = "net"))] - #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[cfg(all(any(doc, windows), feature = "net"))] + #[cfg_attr(docsrs, doc(all(windows, cfg(feature = "net"))))] $item )* } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index c9bbae52664..4e336da104c 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -2,26 +2,68 @@ use std::ffi::OsStr; use std::fmt; use std::io; use std::mem; -use std::os::windows::ffi::OsStrExt as _; -use std::os::windows::io::RawHandle; -use std::os::windows::io::{AsRawHandle, FromRawHandle}; use std::pin::Pin; use std::ptr; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use winapi::shared::minwindef::{DWORD, FALSE}; -use winapi::um::fileapi; -use winapi::um::handleapi; -use winapi::um::namedpipeapi; -use winapi::um::winbase; -use winapi::um::winnt; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::net::asyncify; -// Interned constant to wait forever. Not available in winapi. -const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; +// This module permits rustdoc to work by hiding non-existant items for other +// platforms. +#[cfg(not(doc))] +mod sys { + pub(super) use mio::windows as mio_windows; + pub(super) use std::os::windows::ffi::OsStrExt; + pub(super) use std::os::windows::io::RawHandle; + pub(super) use std::os::windows::io::{AsRawHandle, FromRawHandle}; + pub(super) use winapi::shared::minwindef::{DWORD, FALSE}; + pub(super) use winapi::um::fileapi; + pub(super) use winapi::um::handleapi; + pub(super) use winapi::um::namedpipeapi; + pub(super) use winapi::um::winbase; + pub(super) use winapi::um::winnt; + + // Interned constant to wait forever. Not available in winapi. + pub(super) const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; + + /// Raw handle conversion for [NamedPipe]. + /// + /// # Panics + /// + /// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O + /// enabled]. + /// + /// [Tokio Runtime]: crate::runtime::Runtime + /// [I/O enabled]: crate::runtime::Builder::enable_io + impl FromRawHandle for super::NamedPipe { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + Self::try_from_raw_handle(handle).unwrap() + } + } + + impl AsRawHandle for super::NamedPipe { + fn as_raw_handle(&self) -> RawHandle { + self.io.as_raw_handle() + } + } +} + +// Stubs needed when generating documentation. None of these show up in public +// docs, but because they are present in function signatures rustdoc needs them. +#[cfg(doc)] +mod sys { + pub(super) type DWORD = u32; + pub(super) type RawHandle = (); + + pub(super) mod mio_windows { + pub type NamedPipe = (); + } +} + +use self::sys::*; /// A [Windows named pipe]. /// @@ -121,12 +163,12 @@ const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; /// ``` /// /// [create]: NamedPipeClientBuilder::create -/// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY +/// [ERROR_PIPE_BUSY]: ::winapi::shared::winerror::ERROR_PIPE_BUSY /// [wait]: NamedPipeClientBuilder::wait /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { - io: PollEvented, + io: PollEvented, } impl NamedPipe { @@ -149,7 +191,7 @@ impl NamedPipe { /// [Tokio Runtime]: crate::runtime::Runtime /// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { - let named_pipe = mio::windows::NamedPipe::from_raw_handle(handle); + let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); Ok(NamedPipe { io: PollEvented::new(named_pipe)?, @@ -335,27 +377,6 @@ impl AsyncWrite for NamedPipe { } } -/// Raw handle conversion for [NamedPipe]. -/// -/// # Panics -/// -/// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O -/// enabled]. -/// -/// [Tokio Runtime]: crate::runtime::Runtime -/// [I/O enabled]: crate::runtime::Builder::enable_io -impl FromRawHandle for NamedPipe { - unsafe fn from_raw_handle(handle: RawHandle) -> Self { - Self::try_from_raw_handle(handle).unwrap() - } -} - -impl AsRawHandle for NamedPipe { - fn as_raw_handle(&self) -> RawHandle { - self.io.as_raw_handle() - } -} - // Helper to set a boolean flag as a bitfield. macro_rules! bool_flag { ($f:expr, $t:expr, $flag:expr) => {{ @@ -562,7 +583,7 @@ impl NamedPipeBuilder { /// /// This corresponds to setting [FILE_FLAG_FIRST_PIPE_INSTANCE]. /// - /// [ERROR_ACCESS_DENIED]: winapi::shared::winerror::ERROR_ACCESS_DENIED + /// [ERROR_ACCESS_DENIED]: ::winapi::shared::winerror::ERROR_ACCESS_DENIED /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance /// /// # Examples @@ -708,7 +729,7 @@ impl NamedPipeBuilder { return Err(io::Error::last_os_error()); } - let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = mio_windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -976,7 +997,8 @@ impl NamedPipeClientBuilder { /// [winapi]. This error is raised when the named pipe has been created, /// but the server is not currently waiting for a connection. /// - /// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY + /// [winapi]: ::winapi + /// [ERROR_PIPE_BUSY]: ::winapi::shared::winerror::ERROR_PIPE_BUSY /// /// The generic connect loop looks like this. /// @@ -1042,7 +1064,7 @@ impl NamedPipeClientBuilder { return Err(io::Error::last_os_error()); } - let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = mio_windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -1067,16 +1089,19 @@ pub enum PipeMode { /// Data is written to the pipe as a stream of bytes. The pipe does not /// distinguish bytes written during different write operations. /// - /// Corresponds to [PIPE_TYPE_BYTE][winbase::PIPE_TYPE_BYTE]. + /// Corresponds to [PIPE_TYPE_BYTE]. + /// + /// [PIPE_TYPE_BYTE]: ::winapi::um::winbase::PIPE_TYPE_BYTE Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not /// read completely. /// - /// Corresponds to [PIPE_TYPE_MESSAGE][winbase::PIPE_TYPE_MESSAGE]. + /// Corresponds to [PIPE_TYPE_MESSAGE]. /// - /// [ERROR_MORE_DATA]: winapi::shared::winerror::ERROR_MORE_DATA + /// [ERROR_MORE_DATA]: ::winapi::shared::winerror::ERROR_MORE_DATA + /// [PIPE_TYPE_MESSAGE]: ::winapi::um::winbase::PIPE_TYPE_MESSAGE Message, } @@ -1086,11 +1111,15 @@ pub enum PipeMode { pub enum PipeEnd { /// The [NamedPipe] refers to the client end of a named pipe instance. /// - /// Corresponds to [PIPE_CLIENT_END][winbase::PIPE_CLIENT_END]. + /// Corresponds to [PIPE_CLIENT_END]. + /// + /// [PIPE_CLIENT_END]: ::winapi::um::winbase::PIPE_CLIENT_END Client, /// The [NamedPipe] refers to the server end of a named pipe instance. /// - /// Corresponds to [PIPE_SERVER_END][winbase::PIPE_SERVER_END]. + /// Corresponds to [PIPE_SERVER_END]. + /// + /// [PIPE_SERVER_END]: ::winapi::um::winbase::PIPE_SERVER_END Server, } From 2acf90bd43e26f7dfe5307727c30361891a558d7 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sun, 9 May 2021 10:43:43 +0200 Subject: [PATCH 13/58] Revert "enable windows-specific docs to build on non-windows" This reverts commit ef03f09d26168d8fb51169b40db7a018df5ddb93. --- tokio/src/macros/cfg.rs | 4 +- tokio/src/net/windows/named_pipe.rs | 117 +++++++++++----------------- 2 files changed, 46 insertions(+), 75 deletions(-) diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index fdb8a5fe1a8..b5154ed590a 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -186,8 +186,8 @@ macro_rules! cfg_net_unix { macro_rules! cfg_net_windows { ($($item:item)*) => { $( - #[cfg(all(any(doc, windows), feature = "net"))] - #[cfg_attr(docsrs, doc(all(windows, cfg(feature = "net"))))] + #[cfg(all(windows, feature = "net"))] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] $item )* } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 4e336da104c..c9bbae52664 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -2,68 +2,26 @@ use std::ffi::OsStr; use std::fmt; use std::io; use std::mem; +use std::os::windows::ffi::OsStrExt as _; +use std::os::windows::io::RawHandle; +use std::os::windows::io::{AsRawHandle, FromRawHandle}; use std::pin::Pin; use std::ptr; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; +use winapi::shared::minwindef::{DWORD, FALSE}; +use winapi::um::fileapi; +use winapi::um::handleapi; +use winapi::um::namedpipeapi; +use winapi::um::winbase; +use winapi::um::winnt; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::net::asyncify; -// This module permits rustdoc to work by hiding non-existant items for other -// platforms. -#[cfg(not(doc))] -mod sys { - pub(super) use mio::windows as mio_windows; - pub(super) use std::os::windows::ffi::OsStrExt; - pub(super) use std::os::windows::io::RawHandle; - pub(super) use std::os::windows::io::{AsRawHandle, FromRawHandle}; - pub(super) use winapi::shared::minwindef::{DWORD, FALSE}; - pub(super) use winapi::um::fileapi; - pub(super) use winapi::um::handleapi; - pub(super) use winapi::um::namedpipeapi; - pub(super) use winapi::um::winbase; - pub(super) use winapi::um::winnt; - - // Interned constant to wait forever. Not available in winapi. - pub(super) const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; - - /// Raw handle conversion for [NamedPipe]. - /// - /// # Panics - /// - /// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O - /// enabled]. - /// - /// [Tokio Runtime]: crate::runtime::Runtime - /// [I/O enabled]: crate::runtime::Builder::enable_io - impl FromRawHandle for super::NamedPipe { - unsafe fn from_raw_handle(handle: RawHandle) -> Self { - Self::try_from_raw_handle(handle).unwrap() - } - } - - impl AsRawHandle for super::NamedPipe { - fn as_raw_handle(&self) -> RawHandle { - self.io.as_raw_handle() - } - } -} - -// Stubs needed when generating documentation. None of these show up in public -// docs, but because they are present in function signatures rustdoc needs them. -#[cfg(doc)] -mod sys { - pub(super) type DWORD = u32; - pub(super) type RawHandle = (); - - pub(super) mod mio_windows { - pub type NamedPipe = (); - } -} - -use self::sys::*; +// Interned constant to wait forever. Not available in winapi. +const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; /// A [Windows named pipe]. /// @@ -163,12 +121,12 @@ use self::sys::*; /// ``` /// /// [create]: NamedPipeClientBuilder::create -/// [ERROR_PIPE_BUSY]: ::winapi::shared::winerror::ERROR_PIPE_BUSY +/// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY /// [wait]: NamedPipeClientBuilder::wait /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { - io: PollEvented, + io: PollEvented, } impl NamedPipe { @@ -191,7 +149,7 @@ impl NamedPipe { /// [Tokio Runtime]: crate::runtime::Runtime /// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { - let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); + let named_pipe = mio::windows::NamedPipe::from_raw_handle(handle); Ok(NamedPipe { io: PollEvented::new(named_pipe)?, @@ -377,6 +335,27 @@ impl AsyncWrite for NamedPipe { } } +/// Raw handle conversion for [NamedPipe]. +/// +/// # Panics +/// +/// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O +/// enabled]. +/// +/// [Tokio Runtime]: crate::runtime::Runtime +/// [I/O enabled]: crate::runtime::Builder::enable_io +impl FromRawHandle for NamedPipe { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + Self::try_from_raw_handle(handle).unwrap() + } +} + +impl AsRawHandle for NamedPipe { + fn as_raw_handle(&self) -> RawHandle { + self.io.as_raw_handle() + } +} + // Helper to set a boolean flag as a bitfield. macro_rules! bool_flag { ($f:expr, $t:expr, $flag:expr) => {{ @@ -583,7 +562,7 @@ impl NamedPipeBuilder { /// /// This corresponds to setting [FILE_FLAG_FIRST_PIPE_INSTANCE]. /// - /// [ERROR_ACCESS_DENIED]: ::winapi::shared::winerror::ERROR_ACCESS_DENIED + /// [ERROR_ACCESS_DENIED]: winapi::shared::winerror::ERROR_ACCESS_DENIED /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance /// /// # Examples @@ -729,7 +708,7 @@ impl NamedPipeBuilder { return Err(io::Error::last_os_error()); } - let io = mio_windows::NamedPipe::from_raw_handle(h); + let io = mio::windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -997,8 +976,7 @@ impl NamedPipeClientBuilder { /// [winapi]. This error is raised when the named pipe has been created, /// but the server is not currently waiting for a connection. /// - /// [winapi]: ::winapi - /// [ERROR_PIPE_BUSY]: ::winapi::shared::winerror::ERROR_PIPE_BUSY + /// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY /// /// The generic connect loop looks like this. /// @@ -1064,7 +1042,7 @@ impl NamedPipeClientBuilder { return Err(io::Error::last_os_error()); } - let io = mio_windows::NamedPipe::from_raw_handle(h); + let io = mio::windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -1089,19 +1067,16 @@ pub enum PipeMode { /// Data is written to the pipe as a stream of bytes. The pipe does not /// distinguish bytes written during different write operations. /// - /// Corresponds to [PIPE_TYPE_BYTE]. - /// - /// [PIPE_TYPE_BYTE]: ::winapi::um::winbase::PIPE_TYPE_BYTE + /// Corresponds to [PIPE_TYPE_BYTE][winbase::PIPE_TYPE_BYTE]. Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not /// read completely. /// - /// Corresponds to [PIPE_TYPE_MESSAGE]. + /// Corresponds to [PIPE_TYPE_MESSAGE][winbase::PIPE_TYPE_MESSAGE]. /// - /// [ERROR_MORE_DATA]: ::winapi::shared::winerror::ERROR_MORE_DATA - /// [PIPE_TYPE_MESSAGE]: ::winapi::um::winbase::PIPE_TYPE_MESSAGE + /// [ERROR_MORE_DATA]: winapi::shared::winerror::ERROR_MORE_DATA Message, } @@ -1111,15 +1086,11 @@ pub enum PipeMode { pub enum PipeEnd { /// The [NamedPipe] refers to the client end of a named pipe instance. /// - /// Corresponds to [PIPE_CLIENT_END]. - /// - /// [PIPE_CLIENT_END]: ::winapi::um::winbase::PIPE_CLIENT_END + /// Corresponds to [PIPE_CLIENT_END][winbase::PIPE_CLIENT_END]. Client, /// The [NamedPipe] refers to the server end of a named pipe instance. /// - /// Corresponds to [PIPE_SERVER_END]. - /// - /// [PIPE_SERVER_END]: ::winapi::um::winbase::PIPE_SERVER_END + /// Corresponds to [PIPE_SERVER_END][winbase::PIPE_SERVER_END]. Server, } From b5d5d85232d30cae14b2e9488d3cc440c68daa4c Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 10:18:34 +0200 Subject: [PATCH 14/58] make sure cross platform documentation for named pipes work --- tokio/src/doc/mod.rs | 23 ++++++++++ tokio/src/doc/os.rs | 26 +++++++++++ tokio/src/doc/winapi.rs | 52 +++++++++++++++++++++ tokio/src/lib.rs | 22 +++++++++ tokio/src/macros/cfg.rs | 4 +- tokio/src/net/windows/named_pipe.rs | 71 ++++++++++++++++++----------- 6 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 tokio/src/doc/mod.rs create mode 100644 tokio/src/doc/os.rs create mode 100644 tokio/src/doc/winapi.rs diff --git a/tokio/src/doc/mod.rs b/tokio/src/doc/mod.rs new file mode 100644 index 00000000000..12c22470273 --- /dev/null +++ b/tokio/src/doc/mod.rs @@ -0,0 +1,23 @@ +//! Types which are documented locally in the Tokio crate, but does not actually +//! live here. +//! +//! **Note** this module is only visible on docs.rs, you cannot use it directly +//! in your own code. + +/// The name of a type which is not defined here. +/// +/// This is typically used as an alias for another type, like so: +/// +/// ```rust,ignore +/// /// See [some::other::location](https://example.com). +/// type DEFINED_ELSEWHERE = crate::doc::NotDefinedHere; +/// ``` +/// +/// This type is uninhabitable like the [`never` type] to ensure that no one +/// will ever accidentally use it. +/// +/// [`never` type]: https://doc.rust-lang.org/std/primitive.never.html +pub enum NotDefinedHere {} + +pub mod os; +pub mod winapi; diff --git a/tokio/src/doc/os.rs b/tokio/src/doc/os.rs new file mode 100644 index 00000000000..0ddf86959b8 --- /dev/null +++ b/tokio/src/doc/os.rs @@ -0,0 +1,26 @@ +//! See [std::os](https://doc.rust-lang.org/std/os/index.html). + +/// Platform-specific extensions to `std` for Windows. +/// +/// See [std::os::windows](https://doc.rust-lang.org/std/os/windows/index.html). +pub mod windows { + /// Windows-specific extensions to general I/O primitives. + /// + /// See [std::os::windows::io](https://doc.rust-lang.org/std/os/windows/io/index.html). + pub mod io { + /// See [std::os::windows::io::RawHandle](https://doc.rust-lang.org/std/os/windows/io/type.RawHandle.html) + pub type RawHandle = crate::doc::NotDefinedHere; + + /// See [std::os::windows::io::AsRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html) + pub trait AsRawHandle { + /// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle) + fn as_raw_handle(&self) -> RawHandle; + } + + /// See [std::os::windows::io::FromRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html) + pub trait FromRawHandle { + /// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html#tymethod.from_raw_handle) + unsafe fn from_raw_handle(handle: RawHandle) -> Self; + } + } +} diff --git a/tokio/src/doc/winapi.rs b/tokio/src/doc/winapi.rs new file mode 100644 index 00000000000..e73920e97cf --- /dev/null +++ b/tokio/src/doc/winapi.rs @@ -0,0 +1,52 @@ +//! See [winapi]. +//! +//! [winapi]: https://docs.rs/winapi + +/// See [winapi::shared](https://docs.rs/winapi/*/winapi/shared/index.html). +pub mod shared { + /// See [winapi::shared::winerror](https://docs.rs/winapi/*/winapi/shared/winerror/index.html). + #[allow(non_camel_case_types)] + pub mod winerror { + /// See [winapi::shared::winerror::ERROR_ACCESS_DENIED][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_ACCESS_DENIED.html + pub type ERROR_ACCESS_DENIED = crate::doc::NotDefinedHere; + + /// See [winapi::shared::winerror::ERROR_PIPE_BUSY][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_PIPE_BUSY.html + pub type ERROR_PIPE_BUSY = crate::doc::NotDefinedHere; + + /// See [winapi::shared::winerror::ERROR_MORE_DATA][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_MORE_DATA.html + pub type ERROR_MORE_DATA = crate::doc::NotDefinedHere; + } +} + +/// See [winapi::um](https://docs.rs/winapi/*/winapi/um/index.html). +pub mod um { + /// See [winapi::um::winbase](https://docs.rs/winapi/*/winapi/um/winbase/index.html). + #[allow(non_camel_case_types)] + pub mod winbase { + /// See [winapi::um::winbase::PIPE_TYPE_MESSAGE][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_MESSAGE.html + pub type PIPE_TYPE_MESSAGE = crate::doc::NotDefinedHere; + + /// See [winapi::um::winbase::PIPE_TYPE_BYTE][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_BYTE.html + pub type PIPE_TYPE_BYTE = crate::doc::NotDefinedHere; + + /// See [winapi::um::winbase::PIPE_CLIENT_END][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_CLIENT_END.html + pub type PIPE_CLIENT_END = crate::doc::NotDefinedHere; + + /// See [winapi::um::winbase::PIPE_SERVER_END][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_SERVER_END.html + pub type PIPE_SERVER_END = crate::doc::NotDefinedHere; + } +} diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index 15aeced6d1e..90b2931d3bc 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -442,6 +442,28 @@ mod util; /// ``` pub mod stream {} +// local re-exports of platform specific things, allowing for decent +// documentation to be shimmed in on docs.rs + +#[cfg(docsrs)] +pub mod doc; + +#[cfg(docsrs)] +#[allow(unused)] +pub(crate) use self::doc::os; + +#[cfg(not(docsrs))] +#[allow(unused)] +pub(crate) use std::os; + +#[cfg(docsrs)] +#[allow(unused)] +pub(crate) use self::doc::winapi; + +#[cfg(all(not(docsrs), windows, feature = "net"))] +#[allow(unused)] +pub(crate) use ::winapi; + cfg_macros! { /// Implementation detail of the `select!` macro. This macro is **not** /// intended to be used as part of the public API and is permitted to diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index b5154ed590a..78c6a294f59 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -186,8 +186,8 @@ macro_rules! cfg_net_unix { macro_rules! cfg_net_windows { ($($item:item)*) => { $( - #[cfg(all(windows, feature = "net"))] - #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[cfg(all(any(docsrs, windows), feature = "net"))] + #[cfg_attr(docsrs, doc(cfg(all(windows, feature = "net"))))] $item )* } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index c9bbae52664..396734acb63 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -2,26 +2,44 @@ use std::ffi::OsStr; use std::fmt; use std::io; use std::mem; -use std::os::windows::ffi::OsStrExt as _; -use std::os::windows::io::RawHandle; -use std::os::windows::io::{AsRawHandle, FromRawHandle}; use std::pin::Pin; use std::ptr; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use winapi::shared::minwindef::{DWORD, FALSE}; -use winapi::um::fileapi; -use winapi::um::handleapi; -use winapi::um::namedpipeapi; -use winapi::um::winbase; -use winapi::um::winnt; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::net::asyncify; +use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; -// Interned constant to wait forever. Not available in winapi. -const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; +// Hide imports which are not used when generating documentation. +#[cfg(not(docsrs))] +mod doc { + pub(super) use crate::os::windows::ffi::OsStrExt; + pub(super) use crate::winapi::shared::minwindef::{DWORD, FALSE}; + pub(super) use crate::winapi::um::fileapi; + pub(super) use crate::winapi::um::handleapi; + pub(super) use crate::winapi::um::namedpipeapi; + pub(super) use crate::winapi::um::winbase; + pub(super) use crate::winapi::um::winnt; + + pub(super) use mio::windows as mio_windows; + + // Interned constant to wait forever. Not available in winapi. + pub(super) const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; +} + +// NB: none of these shows up in public API, so don't document them. +#[cfg(docsrs)] +mod doc { + pub type DWORD = crate::doc::NotDefinedHere; + + pub(super) mod mio_windows { + pub type NamedPipe = crate::doc::NotDefinedHere; + } +} + +use self::doc::*; /// A [Windows named pipe]. /// @@ -121,12 +139,12 @@ const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; /// ``` /// /// [create]: NamedPipeClientBuilder::create -/// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY +/// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [wait]: NamedPipeClientBuilder::wait /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { - io: PollEvented, + io: PollEvented, } impl NamedPipe { @@ -149,7 +167,7 @@ impl NamedPipe { /// [Tokio Runtime]: crate::runtime::Runtime /// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { - let named_pipe = mio::windows::NamedPipe::from_raw_handle(handle); + let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); Ok(NamedPipe { io: PollEvented::new(named_pipe)?, @@ -562,7 +580,7 @@ impl NamedPipeBuilder { /// /// This corresponds to setting [FILE_FLAG_FIRST_PIPE_INSTANCE]. /// - /// [ERROR_ACCESS_DENIED]: winapi::shared::winerror::ERROR_ACCESS_DENIED + /// [ERROR_ACCESS_DENIED]: crate::winapi::shared::winerror::ERROR_ACCESS_DENIED /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance /// /// # Examples @@ -613,7 +631,7 @@ impl NamedPipeBuilder { /// This corresponds to specifying [nMaxInstances]. /// /// [nMaxInstances]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea - /// [PIPE_UNLIMITED_INSTANCES]: winapi::um::winbase::PIPE_UNLIMITED_INSTANCES + /// [PIPE_UNLIMITED_INSTANCES]: crate::winapi::um::winbase::PIPE_UNLIMITED_INSTANCES /// /// # Panics /// @@ -691,7 +709,7 @@ impl NamedPipeBuilder { /// The caller must ensure that `attrs` points to an initialized instance of /// a [SECURITY_ATTRIBUTES] structure. /// - /// [SECURITY_ATTRIBUTES]: [winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { let h = namedpipeapi::CreateNamedPipeW( self.name.as_ptr(), @@ -708,7 +726,7 @@ impl NamedPipeBuilder { return Err(io::Error::last_os_error()); } - let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = mio_windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -976,7 +994,8 @@ impl NamedPipeClientBuilder { /// [winapi]. This error is raised when the named pipe has been created, /// but the server is not currently waiting for a connection. /// - /// [ERROR_PIPE_BUSY]: winapi::shared::winerror::ERROR_PIPE_BUSY + /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY + /// [winapi]: crate::winapi /// /// The generic connect loop looks like this. /// @@ -1022,7 +1041,7 @@ impl NamedPipeClientBuilder { /// The caller must ensure that `attrs` points to an initialized instance /// of a [SECURITY_ATTRIBUTES] structure. /// - /// [SECURITY_ATTRIBUTES]: [winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { // NB: We could use a platform specialized `OpenOptions` here, but since // we have access to winapi it ultimately doesn't hurt to use @@ -1042,7 +1061,7 @@ impl NamedPipeClientBuilder { return Err(io::Error::last_os_error()); } - let io = mio::windows::NamedPipe::from_raw_handle(h); + let io = mio_windows::NamedPipe::from_raw_handle(h); let io = PollEvented::new(io)?; Ok(NamedPipe { io }) @@ -1067,16 +1086,16 @@ pub enum PipeMode { /// Data is written to the pipe as a stream of bytes. The pipe does not /// distinguish bytes written during different write operations. /// - /// Corresponds to [PIPE_TYPE_BYTE][winbase::PIPE_TYPE_BYTE]. + /// Corresponds to [PIPE_TYPE_BYTE][crate::winapi::um::winbase::PIPE_TYPE_BYTE]. Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not /// read completely. /// - /// Corresponds to [PIPE_TYPE_MESSAGE][winbase::PIPE_TYPE_MESSAGE]. + /// Corresponds to [PIPE_TYPE_MESSAGE][crate::winapi::um::winbase::PIPE_TYPE_MESSAGE]. /// - /// [ERROR_MORE_DATA]: winapi::shared::winerror::ERROR_MORE_DATA + /// [ERROR_MORE_DATA]: crate::winapi::shared::winerror::ERROR_MORE_DATA Message, } @@ -1086,11 +1105,11 @@ pub enum PipeMode { pub enum PipeEnd { /// The [NamedPipe] refers to the client end of a named pipe instance. /// - /// Corresponds to [PIPE_CLIENT_END][winbase::PIPE_CLIENT_END]. + /// Corresponds to [PIPE_CLIENT_END][crate::winapi::um::winbase::PIPE_CLIENT_END]. Client, /// The [NamedPipe] refers to the server end of a named pipe instance. /// - /// Corresponds to [PIPE_SERVER_END][winbase::PIPE_SERVER_END]. + /// Corresponds to [PIPE_SERVER_END][crate::winapi::um::winbase::PIPE_SERVER_END]. Server, } From b25019b241522ef6111a9717bbd57a275da0bf2e Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 10:38:05 +0200 Subject: [PATCH 15/58] fix private doc link --- tokio/src/net/windows/named_pipe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 396734acb63..fd7b81fa5ea 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -165,7 +165,7 @@ impl NamedPipe { /// [I/O enabled]. /// /// [Tokio Runtime]: crate::runtime::Runtime - /// [I/O enabled]: crate::runtime::RuntimeBuilder::enable_io + /// [I/O enabled]: crate::runtime::Builder::enable_io unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); From edf4fa574081a85f27be40a126f78297070ce66d Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 13:09:59 +0200 Subject: [PATCH 16/58] switch to options style building --- examples/named-pipe-multi-client.rs | 42 +- examples/named-pipe.rs | 11 +- tokio/src/net/windows/mod.rs | 2 +- tokio/src/net/windows/named_pipe.rs | 619 ++++++++++++++-------------- tokio/tests/named_pipe.rs | 45 +- 5 files changed, 340 insertions(+), 379 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index ef2666b5240..56dff401bdc 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -3,24 +3,22 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { use std::time::Duration; - use tokio::io::AsyncWriteExt as _; - use tokio::io::{AsyncBufReadExt as _, BufReader}; - use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions}; use tokio::time; use winapi::shared::winerror; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client"; const N: usize = 10; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - // The first server needs to be constructed early so that clients can // be correctly connected. Otherwise a waiting client will error. // // Here we also make use of `first_pipe_instance`, which will ensure // that there are no other servers up and running already. - let mut server = server_builder.clone().first_pipe_instance(true).create()?; + let mut server = NamedPipeOptions::new() + .first_pipe_instance(true) + .create(PIPE_NAME)?; let server = tokio::spawn(async move { // Artificial workload. @@ -29,20 +27,19 @@ async fn windows_main() -> io::Result<()> { for _ in 0..N { // Wait for client to connect. server.connect().await?; - let mut inner = BufReader::new(server); + let mut inner = server; // Construct the next server to be connected before sending the one // we already have of onto a task. This ensures that the server // isn't closed (after it's done in the task) before a new one is // available. Otherwise the client might error with // `io::ErrorKind::NotFound`. - server = server_builder.create()?; + server = NamedPipeOptions::new().create(PIPE_NAME)?; let _ = tokio::spawn(async move { - let mut buf = String::new(); - inner.read_line(&mut buf).await?; - inner.write_all(b"pong\n").await?; - inner.flush().await?; + let mut buf = [0u8; 4]; + inner.read_exact(&mut buf).await?; + inner.write_all(b"pong").await?; Ok::<_, io::Error>(()) }); } @@ -53,11 +50,9 @@ async fn windows_main() -> io::Result<()> { let mut clients = Vec::new(); for _ in 0..N { - let client_builder = client_builder.clone(); - clients.push(tokio::spawn(async move { - let client = loop { - match client_builder.create() { + let mut client = loop { + match NamedPipeClientOptions::new().create(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) => return Err(e), @@ -68,22 +63,19 @@ async fn windows_main() -> io::Result<()> { // We immediately try to create a client, if it's not found or // the pipe is busy we use the specialized wait function on the // client builder. - client_builder.wait(Some(Duration::from_secs(5))).await?; + wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; }; - let mut client = BufReader::new(client); - - let mut buf = String::new(); - client.write_all(b"ping\n").await?; - client.flush().await?; - client.read_line(&mut buf).await?; + let mut buf = [0u8; 4]; + client.write_all(b"ping").await?; + client.read_exact(&mut buf).await?; Ok::<_, io::Error>(buf) })); } for client in clients { let result = client.await?; - assert_eq!(result?, "pong\n"); + assert_eq!(&result?[..], b"pong"); } server.await??; diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index 78ee4dbb7d2..983ee42681c 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -5,14 +5,11 @@ async fn windows_main() -> io::Result<()> { use std::time::Duration; use tokio::io::AsyncWriteExt as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; - use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions}; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - let server = server_builder.create()?; + let server = NamedPipeOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { // Note: we wait for a client to connect. @@ -27,8 +24,8 @@ async fn windows_main() -> io::Result<()> { }); let client = tokio::spawn(async move { - client_builder.wait(Some(Duration::from_secs(5))).await?; - let client = client_builder.create()?; + wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index 47a752e4dbb..e764c7eff58 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -2,5 +2,5 @@ mod named_pipe; pub use self::named_pipe::{ - NamedPipe, NamedPipeBuilder, NamedPipeClientBuilder, PipeEnd, PipeMode, + wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeEnd, PipeMode, }; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index fd7b81fa5ea..052541db8bc 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1,10 +1,8 @@ use std::ffi::OsStr; -use std::fmt; use std::io; use std::mem; use std::pin::Pin; use std::ptr; -use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; @@ -43,8 +41,8 @@ use self::doc::*; /// A [Windows named pipe]. /// -/// Constructed using [NamedPipeClientBuilder::create] for clients, or -/// [NamedPipeBuilder::create] for servers. See their corresponding +/// Constructed using [NamedPipeClientOptions::create] for clients, or +/// [NamedPipeOptions::create] for servers. See their corresponding /// documentation for examples. /// /// Connecting a client involves a few steps. First we must try to [create], the @@ -58,22 +56,20 @@ use self::doc::*; /// /// ```no_run /// use std::time::Duration; -/// use tokio::net::windows::NamedPipeClientBuilder; +/// use tokio::net::windows::{NamedPipeClientOptions, wait_named_pipe}; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); -/// /// let client = loop { -/// match client_builder.create() { +/// match NamedPipeClientOptions::new().create(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), /// } /// -/// client_builder.wait(Some(Duration::from_secs(5))).await?; +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; /// }; /// /// /* use the connected client */ @@ -89,22 +85,21 @@ use self::doc::*; /// ```no_run /// use std::io; /// use std::sync::Arc; -/// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; +/// use tokio::net::windows::NamedPipeOptions; /// use tokio::sync::Notify; /// /// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-server"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let server_builder = NamedPipeBuilder::new(PIPE_NAME); -/// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); -/// /// // The first server needs to be constructed early so that clients can /// // be correctly connected. Otherwise calling .wait will cause the client to /// // error. /// // /// // Here we also make use of `first_pipe_instance`, which will ensure that /// // there are no other servers up and running already. -/// let mut server = server_builder.clone().first_pipe_instance(true).create()?; +/// let mut server = NamedPipeOptions::new() +/// .first_pipe_instance(true) +/// .create(PIPE_NAME)?; /// /// let shutdown = Arc::new(Notify::new()); /// let shutdown2 = shutdown.clone(); @@ -123,7 +118,8 @@ use self::doc::*; /// // isn't closed (after it's done in the task) before a new one is /// // available. Otherwise the client might error with /// // `io::ErrorKind::NotFound`. -/// server = server_builder.create()?; +/// server = NamedPipeOptions::new() +/// .create(PIPE_NAME)?; /// /// let client = tokio::spawn(async move { /// /* use the connected client */ @@ -138,9 +134,9 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// [create]: NamedPipeClientBuilder::create +/// [create]: NamedPipeClientOptions::create /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY -/// [wait]: NamedPipeClientBuilder::wait +/// [wait]: NamedPipeClientOptions::wait /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { @@ -183,11 +179,12 @@ impl NamedPipe { /// [ConnectNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe /// /// ```no_run - /// use tokio::net::windows::NamedPipeBuilder; + /// use tokio::net::windows::NamedPipeOptions; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe"); - /// let pipe = builder.create()?; + /// let pipe = NamedPipeOptions::new().create(PIPE_NAME)?; /// /// // Wait for a client to connect. /// pipe.connect().await?; @@ -214,17 +211,16 @@ impl NamedPipe { /// /// ``` /// use tokio::io::AsyncWriteExt as _; - /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-disconnect"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let server = server_builder.create()?; + /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; /// - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// let mut client = client_builder.create()?; + /// let mut client = NamedPipeClientOptions::new() + /// .create(PIPE_NAME)?; /// /// // Wait for a client to become connected. /// server.connect().await?; @@ -245,19 +241,18 @@ impl NamedPipe { /// Retrieves information about the current named pipe. /// /// ``` - /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode, PipeEnd}; + /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions, PipeMode, PipeEnd}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-info"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME) + /// let server = NamedPipeOptions::new() /// .pipe_mode(PipeMode::Message) - /// .max_instances(5); + /// .max_instances(5) + /// .create(PIPE_NAME)?; /// - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// - /// let server = server_builder.create()?; - /// let client = client_builder.create()?; + /// let client = NamedPipeClientOptions::new() + /// .create(PIPE_NAME)?; /// /// let server_info = server.info()?; /// let client_info = client.info()?; @@ -391,10 +386,9 @@ macro_rules! bool_flag { /// options. This is required to use for named pipe servers who wants to modify /// pipe-related options. /// -/// See [NamedPipeBuilder::create]. -#[derive(Clone)] -pub struct NamedPipeBuilder { - name: Arc<[u16]>, +/// See [NamedPipeOptions::create]. +#[derive(Debug, Clone)] +pub struct NamedPipeOptions { open_mode: DWORD, pipe_mode: DWORD, max_instances: DWORD, @@ -403,26 +397,21 @@ pub struct NamedPipeBuilder { default_timeout: DWORD, } -impl NamedPipeBuilder { +impl NamedPipeOptions { /// Creates a new named pipe builder with the default settings. /// /// ``` - /// use tokio::net::windows::NamedPipeBuilder; + /// use tokio::net::windows::NamedPipeOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-new"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let server = server_builder.create()?; + /// let server = NamedPipeOptions::new() + /// .create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn new(addr: impl AsRef) -> NamedPipeBuilder { - NamedPipeBuilder { - name: addr - .as_ref() - .encode_wide() - .chain(Some(0)) - .collect::>(), + pub fn new() -> NamedPipeOptions { + NamedPipeOptions { open_mode: winbase::PIPE_ACCESS_DUPLEX | winbase::FILE_FLAG_OVERLAPPED, pipe_mode: winbase::PIPE_TYPE_BYTE | winbase::PIPE_REJECT_REMOTE_CLIENTS, max_instances: winbase::PIPE_UNLIMITED_INSTANCES, @@ -440,7 +429,7 @@ impl NamedPipeBuilder { /// This corresponding to specifying [dwPipeMode]. /// /// [dwPipeMode]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea - pub fn pipe_mode(mut self, pipe_mode: PipeMode) -> Self { + pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self { self.pipe_mode = match pipe_mode { PipeMode::Byte => winbase::PIPE_TYPE_BYTE, PipeMode::Message => winbase::PIPE_TYPE_MESSAGE, @@ -460,26 +449,29 @@ impl NamedPipeBuilder { /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; + /// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// /// // Server side prevents connecting by denying inbound access, client errors /// // when attempting to create the connection. /// { - /// let server_builder = server_builder.clone().access_inbound(false); - /// let _server = server_builder.create()?; + /// let _server = NamedPipeOptions::new() + /// .access_inbound(false) + /// .create(PIPE_NAME)?; + /// + /// let e = NamedPipeClientOptions::new() + /// .create(PIPE_NAME) + /// .unwrap_err(); /// - /// let e = client_builder.create().unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); /// /// // Disabling writing allows a client to connect, but leads to runtime /// // error if a write is attempted. - /// let mut client = client_builder.clone().write(false).create()?; + /// let mut client = NamedPipeClientOptions::new() + /// .write(false) + /// .create(PIPE_NAME)?; /// /// let e = client.write(b"ping").await.unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); @@ -487,8 +479,13 @@ impl NamedPipeBuilder { /// /// // A functional, unidirectional server-to-client only communication. /// { - /// let mut server = server_builder.clone().access_inbound(false).create()?; - /// let mut client = client_builder.clone().write(false).create()?; + /// let mut server = NamedPipeOptions::new() + /// .access_inbound(false) + /// .create(PIPE_NAME)?; + /// + /// let mut client = NamedPipeClientOptions::new() + /// .write(false) + /// .create(PIPE_NAME)?; /// /// let write = server.write_all(b"ping"); /// @@ -502,7 +499,7 @@ impl NamedPipeBuilder { /// } /// # Ok(()) } /// ``` - pub fn access_inbound(mut self, allowed: bool) -> Self { + pub fn access_inbound(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_INBOUND); self } @@ -518,26 +515,27 @@ impl NamedPipeBuilder { /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; + /// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// /// // Server side prevents connecting by denying outbound access, client errors /// // when attempting to create the connection. /// { - /// let server_builder = server_builder.clone().access_outbound(false); - /// let _server = server_builder.create()?; + /// let _server = NamedPipeOptions::new() + /// .access_outbound(false) + /// .create(PIPE_NAME)?; + /// + /// let e = NamedPipeClientOptions::new() + /// .create(PIPE_NAME) + /// .unwrap_err(); /// - /// let e = client_builder.create().unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); /// /// // Disabling reading allows a client to connect, but leads to runtime /// // error if a read is attempted. - /// let mut client = client_builder.clone().read(false).create()?; + /// let mut client = NamedPipeClientOptions::new().read(false).create(PIPE_NAME)?; /// /// let mut buf = [0u8; 4]; /// let e = client.read(&mut buf).await.unwrap_err(); @@ -546,8 +544,8 @@ impl NamedPipeBuilder { /// /// // A functional, unidirectional client-to-server only communication. /// { - /// let mut server = server_builder.clone().access_outbound(false).create()?; - /// let mut client = client_builder.clone().read(false).create()?; + /// let mut server = NamedPipeOptions::new().access_outbound(false).create(PIPE_NAME)?; + /// let mut client = NamedPipeClientOptions::new().read(false).create(PIPE_NAME)?; /// /// // TODO: Explain why this test doesn't work without calling connect /// // first. @@ -569,7 +567,7 @@ impl NamedPipeBuilder { /// } /// # Ok(()) } /// ``` - pub fn access_outbound(mut self, allowed: bool) -> Self { + pub fn access_outbound(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.open_mode, allowed, winbase::PIPE_ACCESS_OUTBOUND); self } @@ -587,23 +585,24 @@ impl NamedPipeBuilder { /// /// ``` /// use std::io; - /// use tokio::net::windows::NamedPipeBuilder; + /// use tokio::net::windows::NamedPipeOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-first-instance"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// let builder = NamedPipeBuilder::new(PIPE_NAME).first_pipe_instance(true); + /// let mut builder = NamedPipeOptions::new(); + /// builder.first_pipe_instance(true); /// - /// let server = builder.create()?; - /// let e = builder.create().unwrap_err(); + /// let server = builder.create(PIPE_NAME)?; + /// let e = builder.create(PIPE_NAME).unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); /// drop(server); /// /// // OK: since, we've closed the other instance. - /// let _server2 = builder.create()?; + /// let _server2 = builder.create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn first_pipe_instance(mut self, first: bool) -> Self { + pub fn first_pipe_instance(&mut self, first: bool) -> &mut Self { bool_flag!( self.open_mode, first, @@ -618,7 +617,7 @@ impl NamedPipeBuilder { /// This corresponds to setting [PIPE_REJECT_REMOTE_CLIENTS]. /// /// [PIPE_REJECT_REMOTE_CLIENTS]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients - pub fn reject_remote_clients(mut self, reject: bool) -> Self { + pub fn reject_remote_clients(&mut self, reject: bool) -> &mut Self { bool_flag!(self.pipe_mode, reject, winbase::PIPE_REJECT_REMOTE_CLIENTS); self } @@ -639,13 +638,13 @@ impl NamedPipeBuilder { /// you do not wish to set an instance limit, leave it unspecified. /// /// ```should_panic - /// use tokio::net::windows::NamedPipeBuilder; + /// use tokio::net::windows::NamedPipeOptions; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeBuilder::new(r"\\.\pipe\mynamedpipe").max_instances(255); + /// let builder = NamedPipeOptions::new().max_instances(255); /// # Ok(()) } /// ``` - pub fn max_instances(mut self, instances: usize) -> Self { + pub fn max_instances(&mut self, instances: usize) -> &mut Self { assert!(instances < 255, "cannot specify more than 254 instances"); self.max_instances = instances as DWORD; self @@ -656,7 +655,7 @@ impl NamedPipeBuilder { /// This corresponds to specifying [nOutBufferSize]. /// /// [nOutBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea - pub fn out_buffer_size(mut self, buffer: u32) -> Self { + pub fn out_buffer_size(&mut self, buffer: u32) -> &mut Self { self.out_buffer_size = buffer as DWORD; self } @@ -666,7 +665,7 @@ impl NamedPipeBuilder { /// This corresponds to specifying [nInBufferSize]. /// /// [nInBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea - pub fn in_buffer_size(mut self, buffer: u32) -> Self { + pub fn in_buffer_size(&mut self, buffer: u32) -> &mut Self { self.in_buffer_size = buffer as DWORD; self } @@ -677,32 +676,31 @@ impl NamedPipeBuilder { /// This function will call the [CreateNamedPipe] function and return the /// result. /// - /// [new]: NamedPipeBuilder::new + /// [new]: NamedPipeOptions::new /// [CreateNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// /// ``` - /// use tokio::net::windows::NamedPipeBuilder; + /// use tokio::net::windows::NamedPipeOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-create"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let server = server_builder.create()?; + /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn create(&self) -> io::Result { + pub fn create(&self, addr: impl AsRef) -> io::Result { // Safety: We're calling create_with_security_attributes w/ a null // pointer which disables it. - unsafe { self.create_with_security_attributes(ptr::null_mut()) } + unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } } /// Create the named pipe identified by the name provided in [new] for use /// by a server. /// - /// This is the same as [create][NamedPipeBuilder::create] except that it + /// This is the same as [create][NamedPipeOptions::create] except that it /// supports providing security attributes. /// - /// [new]: NamedPipeBuilder::new + /// [new]: NamedPipeOptions::new /// /// # Safety /// @@ -710,9 +708,15 @@ impl NamedPipeBuilder { /// a [SECURITY_ATTRIBUTES] structure. /// /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] - pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { + pub unsafe fn create_with_security_attributes( + &self, + addr: impl AsRef, + attrs: *mut (), + ) -> io::Result { + let addr = encode_addr(addr); + let h = namedpipeapi::CreateNamedPipeW( - self.name.as_ptr(), + addr.as_ptr(), self.open_mode, self.pipe_mode, self.max_instances, @@ -733,234 +737,42 @@ impl NamedPipeBuilder { } } -impl fmt::Debug for NamedPipeBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = String::from_utf16_lossy(&self.name[..self.name.len() - 1]); - f.debug_struct("NamedPipeBuilder") - .field("name", &name) - .finish() - } -} - /// A builder suitable for building and interacting with named pipes from the /// client side. /// -/// See [NamedPipeClientBuilder::create]. -#[derive(Clone)] -pub struct NamedPipeClientBuilder { - name: Arc<[u16]>, +/// See [NamedPipeClientOptions::create]. +#[derive(Debug, Clone)] +pub struct NamedPipeClientOptions { desired_access: DWORD, } -impl NamedPipeClientBuilder { +impl NamedPipeClientOptions { /// Creates a new named pipe builder with the default settings. /// /// ``` - /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; + /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-new"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// // Server must be created in order for the client creation to succeed. - /// let server = NamedPipeBuilder::new(PIPE_NAME).create()?; - /// - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// let client = client_builder.create()?; + /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; + /// let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn new(addr: impl AsRef) -> Self { + pub fn new() -> Self { Self { - name: addr - .as_ref() - .encode_wide() - .chain(Some(0)) - .collect::>(), desired_access: winnt::GENERIC_READ | winnt::GENERIC_WRITE, } } - /// Waits until either a time-out interval elapses or an instance of the - /// specified named pipe is available for connection (that is, the pipe's - /// server process has a pending [connect] operation on the pipe). - /// - /// This corresponds to the [`WaitNamedPipeW`] system call. - /// - /// [`WaitNamedPipeW`]: - /// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea - /// [connect]: NamedPipe::connect - /// - /// # Errors - /// - /// If a server hasn't already created the named pipe, this will return an - /// error with the kind [std::io::ErrorKind::NotFound]. - /// - /// ``` - /// use std::io; - /// use std::time::Duration; - /// use tokio::net::windows::NamedPipeClientBuilder; - /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error1"; - /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// - /// let e = client_builder.wait(Some(Duration::from_secs(1))).await.unwrap_err(); - /// - /// // Errors because no server exists. - /// assert_eq!(e.kind(), io::ErrorKind::NotFound); - /// # Ok(()) } - /// ``` - /// - /// Waiting while a server is being closed will first cause it to block, but - /// then error with [std::io::ErrorKind::NotFound]. - /// - /// ``` - /// use std::io; - /// use std::time::Duration; - /// use tokio::net::windows::{NamedPipeClientBuilder, NamedPipeBuilder}; - /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error2"; - /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// - /// let server = server_builder.create()?; - /// - /// // Construct a client that occupies the server so that the next one is - /// // forced to wait. - /// let _client = client_builder.create()?; - /// - /// tokio::spawn(async move { - /// // Drop the server after 100ms, causing the waiting client to err. - /// tokio::time::sleep(Duration::from_millis(100)).await; - /// drop(server); - /// }); - /// - /// let e = client_builder.wait(Some(Duration::from_secs(1))).await.unwrap_err(); - /// - /// assert_eq!(e.kind(), io::ErrorKind::NotFound); - /// # Ok(()) } - /// ``` - /// - /// # Examples - /// - /// ``` - /// use std::io; - /// use std::time::Duration; - /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder}; - /// use winapi::shared::winerror; - /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait"; - /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server_builder = NamedPipeBuilder::new(PIPE_NAME); - /// let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - /// - /// // The first server needs to be constructed early so that clients can - /// // be correctly connected. Otherwise calling .wait will cause the client - /// // to error because the file descriptor doesn't exist. - /// // - /// // Here we also make use of `first_pipe_instance`, which will ensure - /// // that there are no other servers up and running already. - /// let mut server = server_builder.clone().first_pipe_instance(true).create()?; - /// - /// let client = tokio::spawn(async move { - /// // Wait forever until a socket is available to be connected to. - /// let mut client = loop { - /// match client_builder.create() { - /// Ok(client) => break client, - /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), - /// Err(e) => return Err(e), - /// } - /// - /// client_builder.wait(Some(Duration::from_secs(5))).await?; - /// }; - /// - /// let mut buf = [0u8; 4]; - /// client.read_exact(&mut buf[..]).await?; - /// Ok::<_, io::Error>(buf) - /// }); - /// - /// let server = tokio::spawn(async move { - /// tokio::time::sleep(Duration::from_millis(200)).await; - /// - /// // Calling `connect` is necessary for the waiting client to wake up, - /// // even if the server is created after the client. - /// server.connect().await?; - /// - /// server.write_all(b"ping").await?; - /// Ok::<_, io::Error>(()) - /// }); - /// - /// let (client, server) = tokio::try_join!(client, server)?; - /// let payload = client?; - /// assert_eq!(&payload[..], b"ping"); - /// let _ = server?; - /// # Ok(()) } - /// ``` - /// - /// # Panics - /// - /// Panics if the specified duration is larger than `0xffffffff` - /// milliseconds, which is roughly equal to 1193 hours. - /// - /// ```should_panic - /// use std::time::Duration; - /// use tokio::net::windows::NamedPipeClientBuilder; - /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); - /// - /// builder.wait(Some(Duration::from_millis(0xffffffff))).await?; - /// # Ok(()) } - /// ``` - pub async fn wait(&self, timeout: Option) -> io::Result<()> { - let timeout = match timeout { - Some(timeout) => { - let timeout = timeout.as_millis(); - assert! { - timeout < NMPWAIT_WAIT_FOREVER as u128, - "timeout out of bounds, can wait at most {}ms, but got {}ms", NMPWAIT_WAIT_FOREVER - 1, - timeout - }; - timeout as DWORD - } - None => NMPWAIT_WAIT_FOREVER, - }; - - let name = self.name.clone(); - - // TODO: Is this the right thread pool to use? `WaitNamedPipeW` could - // potentially block for a fairly long time all though it's only - // expected to be used when connecting something which should be fairly - // constrained. - // - // But doing something silly like spawning hundreds of clients trying to - // connect to a named pipe server without a timeout could easily end up - // starving the thread pool. - let task = asyncify(move || { - // Safety: There's nothing unsafe about this. - let result = unsafe { namedpipeapi::WaitNamedPipeW(name.as_ptr(), timeout) }; - - if result == FALSE { - return Err(io::Error::last_os_error()); - } - - Ok(()) - }); - - task.await - } - /// If the client supports reading data. This is enabled by default. /// /// This corresponds to setting [GENERIC_READ] in the call to [CreateFile]. /// /// [GENERIC_READ]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - pub fn read(mut self, allowed: bool) -> Self { + pub fn read(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.desired_access, allowed, winnt::GENERIC_READ); self } @@ -971,7 +783,7 @@ impl NamedPipeClientBuilder { /// /// [GENERIC_WRITE]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - pub fn write(mut self, allowed: bool) -> Self { + pub fn write(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.desired_access, allowed, winnt::GENERIC_WRITE); self } @@ -980,7 +792,7 @@ impl NamedPipeClientBuilder { /// /// This constructs the handle using [CreateFile]. /// - /// [new]: NamedPipeClientBuilder::new + /// [new]: NamedPipeClientOptions::new /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// /// # Errors @@ -1002,20 +814,20 @@ impl NamedPipeClientBuilder { /// ```no_run /// use std::io; /// use std::time::Duration; - /// use tokio::net::windows::NamedPipeClientBuilder; + /// use tokio::net::windows::{NamedPipeClientOptions, wait_named_pipe}; /// use winapi::shared::winerror; /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeClientBuilder::new(r"\\.\pipe\mynamedpipe"); + /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = loop { - /// match builder.create() { + /// match NamedPipeClientOptions::new().create(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), /// } /// - /// if builder.wait(Some(Duration::from_secs(5))).await.is_err() { + /// if wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await.is_err() { /// return Err(io::Error::new(io::ErrorKind::Other, "server timed out")); /// } /// }; @@ -1023,18 +835,18 @@ impl NamedPipeClientBuilder { /// // use the connected client. /// # Ok(()) } /// ``` - pub fn create(&self) -> io::Result { + pub fn create(&self, addr: impl AsRef) -> io::Result { // Safety: We're calling create_with_security_attributes w/ a null // pointer which disables it. - unsafe { self.create_with_security_attributes(ptr::null_mut()) } + unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } } /// Open the named pipe identified by the name provided in [new]. /// - /// This is the same as [create][NamedPipeClientBuilder::create] except that + /// This is the same as [create][NamedPipeClientOptions::create] except that /// it supports providing security attributes. /// - /// [new]: NamedPipeClientBuilder::new + /// [new]: NamedPipeClientOptions::new /// /// # Safety /// @@ -1042,13 +854,19 @@ impl NamedPipeClientBuilder { /// of a [SECURITY_ATTRIBUTES] structure. /// /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] - pub unsafe fn create_with_security_attributes(&self, attrs: *mut ()) -> io::Result { + pub unsafe fn create_with_security_attributes( + &self, + addr: impl AsRef, + attrs: *mut (), + ) -> io::Result { + let addr = encode_addr(addr); + // NB: We could use a platform specialized `OpenOptions` here, but since // we have access to winapi it ultimately doesn't hurt to use // `CreateFile` explicitly since it allows the use of our already - // well-structured wide `name` to pass into CreateFileW. + // well-structured wide `addr` to pass into CreateFileW. let h = fileapi::CreateFileW( - self.name.as_ptr(), + addr.as_ptr(), self.desired_access, 0, attrs as *mut _, @@ -1068,18 +886,9 @@ impl NamedPipeClientBuilder { } } -impl fmt::Debug for NamedPipeClientBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = String::from_utf16_lossy(&self.name[..self.name.len() - 1]); - f.debug_struct("NamedPipeClientBuilder") - .field("name", &name) - .finish() - } -} - /// The pipe mode of a [NamedPipe]. /// -/// Set through [NamedPipeBuilder::pipe_mode]. +/// Set through [NamedPipeOptions::pipe_mode]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeMode { @@ -1129,3 +938,179 @@ pub struct PipeInfo { /// The number of bytes to reserve for the input buffer. pub in_buffer_size: u32, } + +/// Waits until either a time-out interval elapses or an instance of the +/// specified named pipe is available for connection (that is, the pipe's +/// server process has a pending [connect] operation on the pipe). +/// +/// This corresponds to the [`WaitNamedPipeW`] system call. +/// +/// [`WaitNamedPipeW`]: +/// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea +/// [connect]: NamedPipe::connect +/// +/// # Errors +/// +/// If a server hasn't already created the named pipe, this will return an +/// error with the kind [std::io::ErrorKind::NotFound]. +/// +/// ``` +/// use std::io; +/// use std::time::Duration; +/// use tokio::net::windows::wait_named_pipe; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error1"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).await.unwrap_err(); +/// +/// // Errors because no server exists. +/// assert_eq!(e.kind(), io::ErrorKind::NotFound); +/// # Ok(()) } +/// ``` +/// +/// Waiting while a server is being closed will first cause it to block, but +/// then error with [std::io::ErrorKind::NotFound]. +/// +/// ``` +/// use std::io; +/// use std::time::Duration; +/// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions, wait_named_pipe}; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error2"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let server = NamedPipeOptions::new() +/// .create(PIPE_NAME)?; +/// +/// // Construct a client that occupies the server so that the next one is +/// // forced to wait. +/// let _client = NamedPipeClientOptions::new() +/// .create(PIPE_NAME)?; +/// +/// tokio::spawn(async move { +/// // Drop the server after 100ms, causing the waiting client to err. +/// tokio::time::sleep(Duration::from_millis(100)).await; +/// drop(server); +/// }); +/// +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).await.unwrap_err(); +/// +/// assert_eq!(e.kind(), io::ErrorKind::NotFound); +/// # Ok(()) } +/// ``` +/// +/// # Examples +/// +/// ``` +/// use std::io; +/// use std::time::Duration; +/// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +/// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions, wait_named_pipe}; +/// use winapi::shared::winerror; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// // The first server needs to be constructed early so that clients can +/// // be correctly connected. Otherwise calling .wait will cause the client +/// // to error because the file descriptor doesn't exist. +/// // +/// // Here we also make use of `first_pipe_instance`, which will ensure +/// // that there are no other servers up and running already. +/// let mut server = NamedPipeOptions::new() +/// .first_pipe_instance(true) +/// .create(PIPE_NAME)?; +/// +/// let client = tokio::spawn(async move { +/// // Wait forever until a socket is available to be connected to. +/// let mut client = loop { +/// match NamedPipeClientOptions::new().create(PIPE_NAME) { +/// Ok(client) => break client, +/// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), +/// Err(e) => return Err(e), +/// } +/// +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; +/// }; +/// +/// let mut buf = [0u8; 4]; +/// client.read_exact(&mut buf[..]).await?; +/// Ok::<_, io::Error>(buf) +/// }); +/// +/// let server = tokio::spawn(async move { +/// tokio::time::sleep(Duration::from_millis(200)).await; +/// +/// // Calling `connect` is necessary for the waiting client to wake up, +/// // even if the server is created after the client. +/// server.connect().await?; +/// +/// server.write_all(b"ping").await?; +/// Ok::<_, io::Error>(()) +/// }); +/// +/// let (client, server) = tokio::try_join!(client, server)?; +/// let payload = client?; +/// assert_eq!(&payload[..], b"ping"); +/// let _ = server?; +/// # Ok(()) } +/// ``` +/// +/// # Panics +/// +/// Panics if the specified duration is larger than `0xffffffff` +/// milliseconds, which is roughly equal to 1193 hours. +/// +/// ```should_panic +/// use std::time::Duration; +/// use tokio::net::windows::wait_named_pipe; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(0xffffffff))).await?; +/// # Ok(()) } +/// ``` +pub async fn wait_named_pipe(addr: impl AsRef, timeout: Option) -> io::Result<()> { + let addr = encode_addr(addr); + + let timeout = match timeout { + Some(timeout) => { + let timeout = timeout.as_millis(); + assert! { + timeout < NMPWAIT_WAIT_FOREVER as u128, + "timeout out of bounds, can wait at most {}ms, but got {}ms", NMPWAIT_WAIT_FOREVER - 1, + timeout + }; + timeout as DWORD + } + None => NMPWAIT_WAIT_FOREVER, + }; + + // TODO: Is this the right thread pool to use? `WaitNamedPipeW` could + // potentially block for a fairly long time all though it's only + // expected to be used when connecting something which should be fairly + // constrained. + // + // But doing something silly like spawning hundreds of clients trying to + // connect to a named pipe server without a timeout could easily end up + // starving the thread pool. + let task = asyncify(move || { + // Safety: There's nothing unsafe about this. + let result = unsafe { namedpipeapi::WaitNamedPipeW(addr.as_ptr(), timeout) }; + + if result == FALSE { + return Err(io::Error::last_os_error()); + } + + Ok(()) + }); + + task.await +} + +/// Encode an address so that it is a null-terminated wide string. +fn encode_addr(addr: impl AsRef) -> Box<[u16]> { + addr.as_ref().encode_wide().chain(Some(0)).collect() +} diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index a78cada6458..e1eff5410ed 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -5,20 +5,18 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; use tokio::io::AsyncWriteExt as _; -use tokio::net::windows::{NamedPipeBuilder, NamedPipeClientBuilder, PipeMode}; +use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode}; use winapi::shared::winerror; #[tokio::test] async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; - let mut server = server_builder.create()?; assert_eq!(num_instances("test-named-pipe-client-drop")?, 1); - let client = client_builder.create()?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; server.connect().await?; drop(client); @@ -37,11 +35,8 @@ async fn test_named_pipe_client_drop() -> io::Result<()> { async fn test_named_pipe_client_connect() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-connect"; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - let server = server_builder.create()?; - let client = client_builder.create()?; + let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; server.connect().await?; let e = client.connect().await.unwrap_err(); @@ -58,10 +53,7 @@ async fn test_named_pipe_single_client() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-single-client"; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - - let server = server_builder.create()?; + let server = NamedPipeOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { // Note: we wait for a client to connect. @@ -76,9 +68,9 @@ async fn test_named_pipe_single_client() -> io::Result<()> { }); let client = tokio::spawn(async move { - client_builder.wait(None).await?; + wait_named_pipe(PIPE_NAME, None).await?; - let client = client_builder.create()?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); @@ -103,13 +95,10 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-multi-client"; const N: usize = 10; - let server_builder = NamedPipeBuilder::new(PIPE_NAME); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); - // The first server needs to be constructed early so that clients can // be correctly connected. Otherwise calling .wait will cause the client to // error. - let mut server = server_builder.create()?; + let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { for _ in 0..N { @@ -122,7 +111,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // isn't closed (after it's done in the task) before a new one is // available. Otherwise the client might error with // `io::ErrorKind::NotFound`. - server = server_builder.create()?; + server = NamedPipeOptions::new().create(PIPE_NAME)?; let _ = tokio::spawn(async move { let mut buf = String::new(); @@ -139,8 +128,6 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { let mut clients = Vec::new(); for _ in 0..N { - let client_builder = client_builder.clone(); - clients.push(tokio::spawn(async move { // This showcases a generic connect loop. // @@ -148,7 +135,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // pipe is busy we use the specialized wait function on the client // builder. let client = loop { - match client_builder.create() { + match NamedPipeClientOptions::new().create(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) if e.kind() == io::ErrorKind::NotFound => (), @@ -156,7 +143,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { } // Wait for a named pipe to become available. - client_builder.wait(None).await?; + wait_named_pipe(PIPE_NAME, None).await?; }; let mut client = BufReader::new(client); @@ -183,11 +170,11 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { async fn test_named_pipe_mode_message() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-mode-message"; - let server_builder = NamedPipeBuilder::new(PIPE_NAME).pipe_mode(PipeMode::Message); - let client_builder = NamedPipeClientBuilder::new(PIPE_NAME); + let server = NamedPipeOptions::new() + .pipe_mode(PipeMode::Message) + .create(PIPE_NAME)?; - let server = server_builder.create()?; - let _ = client_builder.create()?; + let _ = NamedPipeClientOptions::new().create(PIPE_NAME)?; server.connect().await?; Ok(()) } From 8fb3c7f37aa4d9a4b288fa6fcdf29d951eae4110 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 17:09:01 +0200 Subject: [PATCH 17/58] add peeking functionality --- examples/Cargo.toml | 5 + examples/named-pipe-peek.rs | 66 +++++++++++ tokio/src/net/windows/named_pipe.rs | 174 +++++++++++++++++++++++++++- tokio/tests/named_pipe.rs | 158 ++++++++++++++++++++++++- 4 files changed, 396 insertions(+), 7 deletions(-) create mode 100644 examples/named-pipe-peek.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index cf5fcbd48e9..eff3636c596 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,6 +22,7 @@ serde_json = "1.0" httparse = "1.0" time = "0.1" once_cell = "1.5.2" +rand = "0.8.3" [target.'cfg(windows)'.dev-dependencies.winapi] version = "0.3.8" @@ -86,3 +87,7 @@ path = "named-pipe.rs" [[example]] name = "named-pipe-multi-client" path = "named-pipe-multi-client.rs" + +[[example]] +name = "named-pipe-peek" +path = "named-pipe-peek.rs" diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs new file mode 100644 index 00000000000..d89dfa75dd6 --- /dev/null +++ b/examples/named-pipe-peek.rs @@ -0,0 +1,66 @@ +use std::io; + +const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; +const N: usize = 1000; + +#[cfg(windows)] +async fn windows_main() -> io::Result<()> { + use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + + let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; + let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + let client = tokio::spawn(async move { + for _ in 0..N { + client.write_all(b"ping").await?; + } + + let mut buf = [0u8; 4]; + client.read_exact(&mut buf).await?; + + Ok::<_, io::Error>(buf) + }); + + let mut buf = [0u8; 4]; + let mut available = 0; + + for n in 0..N { + if available < 4 { + println!("read_exact: (n: {}, available: {})", n, available); + server.read_exact(&mut buf).await?; + assert_eq!(&buf[..], b"ping"); + + let (_, info) = server.peek(None)?; + available = info.total_bytes_available; + continue; + } + + println!("read_exact: (n: {}, available: {})", n, available); + server.read_exact(&mut buf).await?; + available -= buf.len(); + assert_eq!(&buf[..], b"ping"); + } + + server.write_all(b"pong").await?; + + let buf = client.await??; + assert_eq!(&buf[..], b"pong"); + Ok(()) +} + +#[tokio::main] +async fn main() -> io::Result<()> { + #[cfg(windows)] + { + windows_main().await?; + } + + #[cfg(not(windows))] + { + println!("Named pipes are only supported on Windows!"); + } + + Ok(()) +} diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 052541db8bc..1c014429413 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -310,6 +310,131 @@ impl NamedPipe { }) } } + + /// Copies data from a named or anonymous pipe into a buffer without + /// removing it from the pipe. It also returns information about data in the + /// pipe. + /// + /// # Considerations + /// + /// Data reported through peek is sporadic. Once peek returns any data for a + /// given named pipe, further calls to it are not gauranteed to return the + /// same or higher number of bytes available + /// ([PipePeekInfo::total_bytes_available]). It might even report a count of + /// `0` even if no data has been read from the named pipe that was + /// previously peeked. + /// + /// Peeking does not update the state of the named pipe, so in order to + /// advance it you have to actively issue reads. A peek reporting a number + /// of bytes available ([PipePeekInfo::total_bytes_available]) of `0` does + /// not guarantee that there is no data available to read from the named + /// pipe. Even if a peer is writing data, reads still have to be issued for + /// the state of the named pipe to update. + /// + /// Finally, peeking might report no data available indefinitely if there's + /// too little data in the buffer of the named pipe. + /// + /// You can play around with the [`named-pipe-peek` example] to get a feel + /// for how this function behaves. + /// + /// [`named-pipe-peek` example]: https://github.com/tokio-rs/tokio/blob/master/examples/named-pipe-peek.rs + /// + /// # Examples + /// + /// ``` + /// use std::io; + /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; + /// const N: usize = 100; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; + /// let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + /// server.connect().await?; + /// + /// let client = tokio::spawn(async move { + /// for _ in 0..N { + /// client.write_all(b"ping").await?; + /// } + /// + /// let mut buf = [0u8; 4]; + /// client.read_exact(&mut buf).await?; + /// + /// Ok::<_, io::Error>(buf) + /// }); + /// + /// let mut buf = [0u8; 4]; + /// let mut available = 0; + /// + /// for n in 0..N { + /// if available < 4 { + /// server.read_exact(&mut buf).await?; + /// assert_eq!(&buf[..], b"ping"); + /// + /// let (_, info) = server.peek(None)?; + /// available = info.total_bytes_available; + /// continue; + /// } + /// + /// // here we know that at least `available` bytes are immediately + /// // ready to read. + /// server.read_exact(&mut buf).await?; + /// available -= buf.len(); + /// assert_eq!(&buf[..], b"ping"); + /// } + /// + /// server.write_all(b"pong").await?; + /// + /// let buf = client.await??; + /// assert_eq!(&buf[..], b"pong"); + /// # Ok(()) } + /// ``` + pub fn peek(&mut self, buf: Option<&mut [u8]>) -> io::Result<(usize, PipePeekInfo)> { + use std::convert::TryFrom as _; + + unsafe { + let mut n = mem::MaybeUninit::zeroed(); + let mut total_bytes_available = mem::MaybeUninit::zeroed(); + let mut bytes_left_this_message = mem::MaybeUninit::zeroed(); + + let (buf, len) = match buf { + Some(buf) => { + let len = DWORD::try_from(buf.len()).expect("buffer too large for win32 api"); + (buf.as_mut_ptr() as *mut _, len) + } + None => (ptr::null_mut(), 0), + }; + + let result = namedpipeapi::PeekNamedPipe( + self.io.as_raw_handle(), + buf, + len, + n.as_mut_ptr(), + total_bytes_available.as_mut_ptr(), + bytes_left_this_message.as_mut_ptr(), + ); + + if result == FALSE { + return Err(io::Error::last_os_error()); + } + + let n = usize::try_from(n.assume_init()).expect("output size too large"); + + let total_bytes_available = usize::try_from(total_bytes_available.assume_init()) + .expect("available bytes too large"); + let bytes_left_this_message = usize::try_from(bytes_left_this_message.assume_init()) + .expect("bytes left in message too large"); + + let info = PipePeekInfo { + total_bytes_available, + bytes_left_this_message, + }; + + Ok((n, info)) + } + } } impl AsyncRead for NamedPipe { @@ -939,9 +1064,26 @@ pub struct PipeInfo { pub in_buffer_size: u32, } -/// Waits until either a time-out interval elapses or an instance of the -/// specified named pipe is available for connection (that is, the pipe's -/// server process has a pending [connect] operation on the pipe). +/// Information about a pipe gained by peeking it. +/// +/// See [NamedPipe::peek]. +#[derive(Debug, Clone)] +pub struct PipePeekInfo { + /// Indicates the total number of bytes available on the pipe. + pub total_bytes_available: usize, + /// Indicates the number of bytes left in the current message. + /// + /// This is undefined unless the pipe mode is [PipeMode::Message]. + pub bytes_left_this_message: usize, +} + +/// Waits until either a configurable time-out interval elapses or an instance +/// of the specified named pipe is available for connection. That is, the pipe's +/// server process has a pending [connect] operation waiting on the other end of +/// the pipe. +/// +/// If a zero duration is provided, the default timeout of the named pipe will +/// be used. /// /// This corresponds to the [`WaitNamedPipeW`] system call. /// @@ -951,8 +1093,8 @@ pub struct PipeInfo { /// /// # Errors /// -/// If a server hasn't already created the named pipe, this will return an -/// error with the kind [std::io::ErrorKind::NotFound]. +/// If a server hasn't already created the named pipe, this will return an error +/// with the kind [std::io::ErrorKind::NotFound]. /// /// ``` /// use std::io; @@ -1000,6 +1142,28 @@ pub struct PipeInfo { /// # Ok(()) } /// ``` /// +/// If a wait times out, this function will error with +/// [io::ErrorKind::TimedOut]. +/// +/// ``` +/// use std::io; +/// use std::time::Duration; +/// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions, wait_named_pipe}; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error-timedout"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let server = NamedPipeOptions::new().create(PIPE_NAME)?; +/// // connect one client, causing the server to be occupied. +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).await?; +/// let client1 = NamedPipeClientOptions::new().create(PIPE_NAME)?; +/// +/// // this times out because the server is busy. +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).await.unwrap_err(); +/// assert_eq!(e.kind(), io::ErrorKind::TimedOut); +/// # Ok(()) } +/// ``` +/// /// # Examples /// /// ``` diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index e1eff5410ed..1a54cd4d1a1 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -4,10 +4,164 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; -use tokio::io::AsyncWriteExt as _; -use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode}; +use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +use tokio::net::windows::{ + wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode, +}; use winapi::shared::winerror; +#[tokio::test] +async fn test_named_pipe_peek_consumed() -> io::Result<()> { + const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; + const N: usize = 1000; + + let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; + let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + let client = tokio::spawn(async move { + for _ in 0..N { + client.write_all(b"ping").await?; + } + + let mut buf = [0u8; 4]; + client.read_exact(&mut buf).await?; + + Ok::<_, io::Error>(buf) + }); + + let mut buf = [0u8; 4]; + let mut available = 0; + + for _ in 0..N { + if available < buf.len() { + server.read_exact(&mut buf).await?; + assert_eq!(&buf[..], b"ping"); + + let (_, info) = server.peek(Some(&mut buf[..]))?; + available = info.total_bytes_available; + continue; + } + + server.read_exact(&mut buf).await?; + available -= buf.len(); + assert_eq!(&buf[..], b"ping"); + } + + server.write_all(b"pong").await?; + + let buf = client.await??; + assert_eq!(&buf[..], b"pong"); + Ok(()) +} + +async fn peek_ping_pong(n: usize, mut client: NamedPipe, mut server: NamedPipe) -> io::Result<()> { + use rand::Rng as _; + use std::sync::Arc; + + /// A weirdly sized read to induce as many partial reads as possible. + const UNALIGNED: usize = 27; + + let mut data = vec![0; 1024]; + + let mut r = rand::thread_rng(); + r.fill(&mut data[..]); + + let data = Arc::new(data); + let data2 = data.clone(); + + let client = tokio::spawn(async move { + for _ in 0..n { + client.write_all(&data2[..]).await?; + } + + let mut buf = [0u8; 4]; + client.read_exact(&mut buf).await?; + Ok::<_, io::Error>(buf) + }); + + let server = tokio::spawn(async move { + let mut full_buf = vec![0u8; data.len()]; + let mut peeks = 0u32; + + for _ in 0..n { + let mut buf = &mut full_buf[..]; + + while !buf.is_empty() { + let e = usize::min(buf.len(), UNALIGNED); + let r = server.read(&mut buf[..e]).await?; + buf = &mut buf[r..]; + + let (_, info) = server.peek(None)?; + + if info.total_bytes_available != 0 { + peeks += 1; + } + } + + assert_eq!(&full_buf[..], &data[..]); + } + + server.write_all(b"pong").await?; + + // NB: this is not at all helpful, but keeping it here because when run + // in release mode we can usually see a couple of hundred peeks go + // through. + assert!(peeks == 0 || peeks > 0); + Ok::<_, io::Error>(()) + }); + + let (client, server) = tokio::try_join!(client, server)?; + assert_eq!(&client?[..], b"pong"); + let _ = server?; + Ok(()) +} + +#[tokio::test] +async fn test_named_pipe_peek() -> io::Result<()> { + { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-small"; + + let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + peek_ping_pong(1, client, server).await?; + } + + { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; + + let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + peek_ping_pong(1, server, client).await?; + } + + { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-big"; + + let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + peek_ping_pong(100, client, server).await?; + } + + { + const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; + + let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + server.connect().await?; + + peek_ping_pong(100, server, client).await?; + } + + Ok(()) +} + #[tokio::test] async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; From 491f70bb972b758702b81d51adc0941dbe492114 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 17:19:55 +0200 Subject: [PATCH 18/58] fix non-windows build failure --- examples/named-pipe-peek.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs index d89dfa75dd6..12528fa5e13 100644 --- a/examples/named-pipe-peek.rs +++ b/examples/named-pipe-peek.rs @@ -1,13 +1,13 @@ use std::io; -const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; -const N: usize = 1000; - #[cfg(windows)] async fn windows_main() -> io::Result<()> { use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; + const N: usize = 1000; + let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; server.connect().await?; From 06147b515cbdcb9ee5079bfdeb055ecee91cc569 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 10 May 2021 18:23:00 +0200 Subject: [PATCH 19/58] fix link --- tokio/src/net/windows/mod.rs | 1 + tokio/src/net/windows/named_pipe.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index e764c7eff58..2466ceaaff4 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -3,4 +3,5 @@ mod named_pipe; pub use self::named_pipe::{ wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeEnd, PipeMode, + PipePeekInfo, }; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 1c014429413..943e94a58e6 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -136,7 +136,7 @@ use self::doc::*; /// /// [create]: NamedPipeClientOptions::create /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY -/// [wait]: NamedPipeClientOptions::wait +/// [wait]: wait_named_pipe /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { @@ -319,17 +319,16 @@ impl NamedPipe { /// /// Data reported through peek is sporadic. Once peek returns any data for a /// given named pipe, further calls to it are not gauranteed to return the - /// same or higher number of bytes available - /// ([PipePeekInfo::total_bytes_available]). It might even report a count of - /// `0` even if no data has been read from the named pipe that was - /// previously peeked. + /// same or higher number of bytes available ([total_bytes_available]). It + /// might even report a count of `0` even if no data has been read from the + /// named pipe that was previously peeked. /// /// Peeking does not update the state of the named pipe, so in order to /// advance it you have to actively issue reads. A peek reporting a number - /// of bytes available ([PipePeekInfo::total_bytes_available]) of `0` does - /// not guarantee that there is no data available to read from the named - /// pipe. Even if a peer is writing data, reads still have to be issued for - /// the state of the named pipe to update. + /// of bytes available ([total_bytes_available]) of `0` does not guarantee + /// that there is no data available to read from the named pipe. Even if a + /// peer is writing data, reads still have to be issued for the state of the + /// named pipe to update. /// /// Finally, peeking might report no data available indefinitely if there's /// too little data in the buffer of the named pipe. @@ -337,6 +336,7 @@ impl NamedPipe { /// You can play around with the [`named-pipe-peek` example] to get a feel /// for how this function behaves. /// + /// [total_bytes_available]: PipePeekInfo::total_bytes_available /// [`named-pipe-peek` example]: https://github.com/tokio-rs/tokio/blob/master/examples/named-pipe-peek.rs /// /// # Examples From a2d300f44fe20b11feed419a07baf830b66e0bda Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 12 May 2021 07:56:29 +0200 Subject: [PATCH 20/58] mark more things as #[non_exhaustive] --- tokio/src/net/windows/named_pipe.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 943e94a58e6..4afb3bef4d5 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1051,6 +1051,7 @@ pub enum PipeEnd { /// /// Constructed through [NamedPipe::info]. #[derive(Debug)] +#[non_exhaustive] pub struct PipeInfo { /// Indicates the mode of a named pipe. pub mode: PipeMode, @@ -1068,6 +1069,7 @@ pub struct PipeInfo { /// /// See [NamedPipe::peek]. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct PipePeekInfo { /// Indicates the total number of bytes available on the pipe. pub total_bytes_available: usize, From f349c274aeed03070ecf98038b4c169e3b4f7eea Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 12 May 2021 08:03:29 +0200 Subject: [PATCH 21/58] use ReadBuf in NamedPipe::peek --- examples/named-pipe-peek.rs | 2 +- tokio/src/net/windows/named_pipe.rs | 48 ++++++++++++++++++----------- tokio/tests/named_pipe.rs | 6 ++-- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs index 12528fa5e13..1ec3c41c04f 100644 --- a/examples/named-pipe-peek.rs +++ b/examples/named-pipe-peek.rs @@ -32,7 +32,7 @@ async fn windows_main() -> io::Result<()> { server.read_exact(&mut buf).await?; assert_eq!(&buf[..], b"ping"); - let (_, info) = server.peek(None)?; + let info = server.peek(None)?; available = info.total_bytes_available; continue; } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 4afb3bef4d5..31cefdf968a 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -373,7 +373,7 @@ impl NamedPipe { /// server.read_exact(&mut buf).await?; /// assert_eq!(&buf[..], b"ping"); /// - /// let (_, info) = server.peek(None)?; + /// let info = server.peek(None)?; /// available = info.total_bytes_available; /// continue; /// } @@ -391,7 +391,7 @@ impl NamedPipe { /// assert_eq!(&buf[..], b"pong"); /// # Ok(()) } /// ``` - pub fn peek(&mut self, buf: Option<&mut [u8]>) -> io::Result<(usize, PipePeekInfo)> { + pub fn peek(&mut self, mut buf: Option<&mut ReadBuf<'_>>) -> io::Result { use std::convert::TryFrom as _; unsafe { @@ -399,29 +399,41 @@ impl NamedPipe { let mut total_bytes_available = mem::MaybeUninit::zeroed(); let mut bytes_left_this_message = mem::MaybeUninit::zeroed(); - let (buf, len) = match buf { - Some(buf) => { - let len = DWORD::try_from(buf.len()).expect("buffer too large for win32 api"); - (buf.as_mut_ptr() as *mut _, len) - } - None => (ptr::null_mut(), 0), + let result = { + let (buf, len) = match &mut buf { + Some(buf) => { + let len = DWORD::try_from(buf.capacity()) + .expect("buffer too large for win32 api"); + // Safety: the OS has no expectation on whether the + // buffer is initialized or not. + let buf = buf.inner_mut() as *mut _ as *mut _; + (buf, len) + } + None => (ptr::null_mut(), 0), + }; + + namedpipeapi::PeekNamedPipe( + self.io.as_raw_handle(), + buf, + len, + n.as_mut_ptr(), + total_bytes_available.as_mut_ptr(), + bytes_left_this_message.as_mut_ptr(), + ) }; - let result = namedpipeapi::PeekNamedPipe( - self.io.as_raw_handle(), - buf, - len, - n.as_mut_ptr(), - total_bytes_available.as_mut_ptr(), - bytes_left_this_message.as_mut_ptr(), - ); - if result == FALSE { return Err(io::Error::last_os_error()); } let n = usize::try_from(n.assume_init()).expect("output size too large"); + if let Some(buf) = buf { + // Safety: we trust that the OS has initialized up until `n` + // through the call to PeekNamedPipe. + buf.assume_init(n); + } + let total_bytes_available = usize::try_from(total_bytes_available.assume_init()) .expect("available bytes too large"); let bytes_left_this_message = usize::try_from(bytes_left_this_message.assume_init()) @@ -432,7 +444,7 @@ impl NamedPipe { bytes_left_this_message, }; - Ok((n, info)) + Ok(info) } } } diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 1a54cd4d1a1..3ec99639ff7 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -4,7 +4,7 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; -use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _, ReadBuf}; use tokio::net::windows::{ wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode, }; @@ -38,7 +38,7 @@ async fn test_named_pipe_peek_consumed() -> io::Result<()> { server.read_exact(&mut buf).await?; assert_eq!(&buf[..], b"ping"); - let (_, info) = server.peek(Some(&mut buf[..]))?; + let info = server.peek(Some(&mut ReadBuf::new(&mut buf[..])))?; available = info.total_bytes_available; continue; } @@ -92,7 +92,7 @@ async fn peek_ping_pong(n: usize, mut client: NamedPipe, mut server: NamedPipe) let r = server.read(&mut buf[..e]).await?; buf = &mut buf[r..]; - let (_, info) = server.peek(None)?; + let info = server.peek(None)?; if info.total_bytes_available != 0 { peeks += 1; From ec66427bb163c4c64602dbbead68cac303810b18 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 12 May 2021 08:26:17 +0200 Subject: [PATCH 22/58] make wait_named_pipe non-async --- examples/named-pipe-multi-client.rs | 14 ++-- examples/named-pipe.rs | 7 +- tokio/src/net/mod.rs | 22 ------ tokio/src/net/windows/named_pipe.rs | 114 +++++++++++++++------------- tokio/tests/named_pipe.rs | 10 +-- 5 files changed, 77 insertions(+), 90 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index 56dff401bdc..8a9e63e40be 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -4,7 +4,7 @@ use std::io; async fn windows_main() -> io::Result<()> { use std::time::Duration; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions}; + use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; use tokio::time; use winapi::shared::winerror; @@ -51,6 +51,11 @@ async fn windows_main() -> io::Result<()> { for _ in 0..N { clients.push(tokio::spawn(async move { + // This showcases a generic connect loop. + // + // We immediately try to create a client, if it's not found or + // the pipe is busy we use the specialized wait function on the + // client builder. let mut client = loop { match NamedPipeClientOptions::new().create(PIPE_NAME) { Ok(client) => break client, @@ -58,12 +63,7 @@ async fn windows_main() -> io::Result<()> { Err(e) => return Err(e), } - // This showcases a generic connect loop. - // - // We immediately try to create a client, if it's not found or - // the pipe is busy we use the specialized wait function on the - // client builder. - wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; + time::sleep(Duration::from_millis(5)).await; }; let mut buf = [0u8; 4]; diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index 983ee42681c..bb82ff87e69 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -2,10 +2,9 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { - use std::time::Duration; use tokio::io::AsyncWriteExt as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; - use tokio::net::windows::{wait_named_pipe, NamedPipeClientOptions, NamedPipeOptions}; + use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; @@ -24,7 +23,9 @@ async fn windows_main() -> io::Result<()> { }); let client = tokio::spawn(async move { - wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; + // There's no need to use a connect loop here, since we know that the + // server is already up - `create` was called before spawning any of the + // tasks. let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); diff --git a/tokio/src/net/mod.rs b/tokio/src/net/mod.rs index 6d8150aba4d..0b8c1ecd194 100644 --- a/tokio/src/net/mod.rs +++ b/tokio/src/net/mod.rs @@ -49,26 +49,4 @@ cfg_net_unix! { cfg_net_windows! { pub mod windows; - - use std::io; - - pub(crate) async fn asyncify(f: F) -> io::Result - where - F: FnOnce() -> io::Result + Send + 'static, - T: Send + 'static, - { - match sys::run(f).await { - Ok(res) => res, - Err(_) => Err(io::Error::new( - io::ErrorKind::Other, - "background task failed", - )), - } - } - - /// Types in this module can be mocked out in tests. - mod sys { - // TODO: don't rename - pub(crate) use crate::blocking::spawn_blocking as run; - } } diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 31cefdf968a..51245cafc64 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -7,7 +7,6 @@ use std::task::{Context, Poll}; use std::time::Duration; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; -use crate::net::asyncify; use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; // Hide imports which are not used when generating documentation. @@ -49,14 +48,15 @@ use self::doc::*; /// error typically indicates one of two things: /// /// * [std::io::ErrorKind::NotFound] - There is no server available. -/// * [ERROR_PIPE_BUSY] - There is a server available, but it is busy. Use -/// [wait] until it becomes available. +/// * [ERROR_PIPE_BUSY] - There is a server available, but it is busy. Sleep for +/// a while and try again, or use [wait_named_pipe] until it becomes available. /// /// So a typical client connect loop will look like the this: /// /// ```no_run /// use std::time::Duration; -/// use tokio::net::windows::{NamedPipeClientOptions, wait_named_pipe}; +/// use tokio::net::windows::NamedPipeClientOptions; +/// use tokio::time; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client"; @@ -69,7 +69,7 @@ use self::doc::*; /// Err(e) => return Err(e), /// } /// -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; +/// time::sleep(Duration::from_millis(50)).await; /// }; /// /// /* use the connected client */ @@ -136,7 +136,6 @@ use self::doc::*; /// /// [create]: NamedPipeClientOptions::create /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY -/// [wait]: wait_named_pipe /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { @@ -807,15 +806,23 @@ impl NamedPipeOptions { self } - /// Create the named pipe identified by the name provided in [new] for use - /// by a server. + /// Create the named pipe identified by `addr` for use as a server. /// /// This function will call the [CreateNamedPipe] function and return the /// result. /// - /// [new]: NamedPipeOptions::new /// [CreateNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// + /// # Errors + /// + /// This errors if called outside of a [Tokio Runtime] which doesn't have + /// [I/O enabled] or if any OS-specific I/O errors occur. + /// + /// [Tokio Runtime]: crate::runtime::Runtime + /// [I/O enabled]: crate::runtime::Builder::enable_io + /// + /// # Examples + /// /// ``` /// use tokio::net::windows::NamedPipeOptions; /// @@ -831,13 +838,18 @@ impl NamedPipeOptions { unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } } - /// Create the named pipe identified by the name provided in [new] for use - /// by a server. + /// Create the named pipe identified by `addr` for use as a server. /// /// This is the same as [create][NamedPipeOptions::create] except that it /// supports providing security attributes. /// - /// [new]: NamedPipeOptions::new + /// # Errors + /// + /// This errors if called outside of a [Tokio Runtime] which doesn't have + /// [I/O enabled] or if any OS-specific I/O errors occur. + /// + /// [Tokio Runtime]: crate::runtime::Runtime + /// [I/O enabled]: crate::runtime::Builder::enable_io /// /// # Safety /// @@ -925,17 +937,19 @@ impl NamedPipeClientOptions { self } - /// Open the named pipe identified by the name provided in [new]. + /// Open the named pipe identified by `addr`. /// /// This constructs the handle using [CreateFile]. /// - /// [new]: NamedPipeClientOptions::new /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// /// # Errors /// + /// This errors if called outside of a [Tokio Runtime] which doesn't have + /// [I/O enabled] or if any OS-specific I/O errors occur. + /// /// There are a few errors you should be aware of that you need to take into - /// account when creating a named pipe on the client side. + /// account when creating a named pipe on the client side: /// /// * [std::io::ErrorKind::NotFound] - This indicates that the named pipe /// does not exist. Presumably the server is not up. @@ -944,14 +958,17 @@ impl NamedPipeClientOptions { /// but the server is not currently waiting for a connection. /// /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY + /// [I/O enabled]: crate::runtime::Builder::enable_io + /// [Tokio Runtime]: crate::runtime::Runtime /// [winapi]: crate::winapi /// - /// The generic connect loop looks like this. + /// A connect loop that waits until a socket becomes available looks like + /// this: /// /// ```no_run - /// use std::io; /// use std::time::Duration; - /// use tokio::net::windows::{NamedPipeClientOptions, wait_named_pipe}; + /// use tokio::net::windows::NamedPipeClientOptions; + /// use tokio::time; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; @@ -964,9 +981,7 @@ impl NamedPipeClientOptions { /// Err(e) => return Err(e), /// } /// - /// if wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await.is_err() { - /// return Err(io::Error::new(io::ErrorKind::Other, "server timed out")); - /// } + /// time::sleep(Duration::from_millis(50)).await; /// }; /// /// // use the connected client. @@ -978,13 +993,11 @@ impl NamedPipeClientOptions { unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } } - /// Open the named pipe identified by the name provided in [new]. + /// Open the named pipe identified by `addr`. /// /// This is the same as [create][NamedPipeClientOptions::create] except that /// it supports providing security attributes. /// - /// [new]: NamedPipeClientOptions::new - /// /// # Safety /// /// The caller must ensure that `attrs` points to an initialized instance @@ -1096,6 +1109,12 @@ pub struct PipePeekInfo { /// server process has a pending [connect] operation waiting on the other end of /// the pipe. /// +/// This function **blocks** until the given named pipe is available. If you +/// want to use it in an async context, make use of +/// [tokio::task::spawn_blocking], but be aware that if used carelessly it might +/// end up starving the thread pool. One should in particular avoid to use it +/// with an overly large timeout, because the operation cannot be cancelled. +/// /// If a zero duration is provided, the default timeout of the named pipe will /// be used. /// @@ -1117,8 +1136,8 @@ pub struct PipePeekInfo { /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error1"; /// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).await.unwrap_err(); +/// # fn main() -> std::io::Result<()> { +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).unwrap_err(); /// /// // Errors because no server exists. /// assert_eq!(e.kind(), io::ErrorKind::NotFound); @@ -1132,6 +1151,7 @@ pub struct PipePeekInfo { /// use std::io; /// use std::time::Duration; /// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions, wait_named_pipe}; +/// use tokio::time; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error2"; /// @@ -1146,11 +1166,11 @@ pub struct PipePeekInfo { /// /// tokio::spawn(async move { /// // Drop the server after 100ms, causing the waiting client to err. -/// tokio::time::sleep(Duration::from_millis(100)).await; +/// time::sleep(Duration::from_millis(100)).await; /// drop(server); /// }); /// -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).await.unwrap_err(); +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).unwrap_err(); /// /// assert_eq!(e.kind(), io::ErrorKind::NotFound); /// # Ok(()) } @@ -1169,11 +1189,11 @@ pub struct PipePeekInfo { /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; /// // connect one client, causing the server to be occupied. -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).await?; +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10)))?; /// let client1 = NamedPipeClientOptions::new().create(PIPE_NAME)?; /// /// // this times out because the server is busy. -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).await.unwrap_err(); +/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::TimedOut); /// # Ok(()) } /// ``` @@ -1209,7 +1229,9 @@ pub struct PipePeekInfo { /// Err(e) => return Err(e), /// } /// -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(5))).await?; +/// tokio::task::spawn_blocking(move || { +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(500))) +/// }).await??; /// }; /// /// let mut buf = [0u8; 4]; @@ -1246,11 +1268,11 @@ pub struct PipePeekInfo { /// /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; /// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(0xffffffff))).await?; +/// # fn main() -> std::io::Result<()> { +/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(0xffffffff)))?; /// # Ok(()) } /// ``` -pub async fn wait_named_pipe(addr: impl AsRef, timeout: Option) -> io::Result<()> { +pub fn wait_named_pipe(addr: impl AsRef, timeout: Option) -> io::Result<()> { let addr = encode_addr(addr); let timeout = match timeout { @@ -1266,26 +1288,14 @@ pub async fn wait_named_pipe(addr: impl AsRef, timeout: Option) None => NMPWAIT_WAIT_FOREVER, }; - // TODO: Is this the right thread pool to use? `WaitNamedPipeW` could - // potentially block for a fairly long time all though it's only - // expected to be used when connecting something which should be fairly - // constrained. - // - // But doing something silly like spawning hundreds of clients trying to - // connect to a named pipe server without a timeout could easily end up - // starving the thread pool. - let task = asyncify(move || { - // Safety: There's nothing unsafe about this. - let result = unsafe { namedpipeapi::WaitNamedPipeW(addr.as_ptr(), timeout) }; - - if result == FALSE { - return Err(io::Error::last_os_error()); - } + // Safety: There's nothing unsafe about this. + let result = unsafe { namedpipeapi::WaitNamedPipeW(addr.as_ptr(), timeout) }; - Ok(()) - }); + if result == FALSE { + return Err(io::Error::last_os_error()); + } - task.await + Ok(()) } /// Encode an address so that it is a null-terminated wide string. diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 3ec99639ff7..99567f32d52 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -4,10 +4,10 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; +use std::time::Duration; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _, ReadBuf}; -use tokio::net::windows::{ - wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode, -}; +use tokio::net::windows::{NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode}; +use tokio::time; use winapi::shared::winerror; #[tokio::test] @@ -222,8 +222,6 @@ async fn test_named_pipe_single_client() -> io::Result<()> { }); let client = tokio::spawn(async move { - wait_named_pipe(PIPE_NAME, None).await?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); @@ -297,7 +295,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { } // Wait for a named pipe to become available. - wait_named_pipe(PIPE_NAME, None).await?; + time::sleep(Duration::from_millis(50)).await; }; let mut client = BufReader::new(client); From 435a84f075df2f85e9a351e7f4c49515405c4861 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 12 May 2021 08:37:48 +0200 Subject: [PATCH 23/58] fix rustdoc --- tokio/src/net/windows/named_pipe.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 51245cafc64..3767b5920de 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -77,10 +77,10 @@ use self::doc::*; /// ``` /// /// A client will error with [std::io::ErrorKind::NotFound] for most creation -/// oriented operations like [create] or [wait] unless at least once server -/// instance is up and running at all time. This means that the typical listen -/// loop for a server is a bit involved, because we have to ensure that we never -/// drop a server accidentally while a client might want to connect. +/// oriented operations like [create] or [wait_named_pipe] unless at least once +/// server instance is up and running at all time. This means that the typical +/// listen loop for a server is a bit involved, because we have to ensure that +/// we never drop a server accidentally while a client might want to connect. /// /// ```no_run /// use std::io; @@ -1110,17 +1110,18 @@ pub struct PipePeekInfo { /// the pipe. /// /// This function **blocks** until the given named pipe is available. If you -/// want to use it in an async context, make use of -/// [tokio::task::spawn_blocking], but be aware that if used carelessly it might -/// end up starving the thread pool. One should in particular avoid to use it -/// with an overly large timeout, because the operation cannot be cancelled. +/// want to use it in an async context, make use of [spawn_blocking], but be +/// aware that if used carelessly it might end up starving the thread pool. One +/// should in particular avoid to use it with an overly large timeout, because +/// the operation cannot be cancelled. /// /// If a zero duration is provided, the default timeout of the named pipe will /// be used. /// -/// This corresponds to the [`WaitNamedPipeW`] system call. +/// This corresponds to the [WaitNamedPipeW] system call. /// -/// [`WaitNamedPipeW`]: +/// [spawn_blocking]: crate::task::spawn_blocking +/// [WaitNamedPipeW]: /// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea /// [connect]: NamedPipe::connect /// From 08db16bbd62925ba3e4bf9994d8df036965fd231 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 14 May 2021 17:20:20 +0200 Subject: [PATCH 24/58] first batch of review fixes --- tokio/src/net/windows/named_pipe.rs | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 3767b5920de..ad1ac2f87f4 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -394,19 +394,19 @@ impl NamedPipe { use std::convert::TryFrom as _; unsafe { - let mut n = mem::MaybeUninit::zeroed(); - let mut total_bytes_available = mem::MaybeUninit::zeroed(); - let mut bytes_left_this_message = mem::MaybeUninit::zeroed(); + let mut n = 0; + let mut total_bytes_available = 0; + let mut bytes_left_this_message = 0; let result = { let (buf, len) = match &mut buf { Some(buf) => { - let len = DWORD::try_from(buf.capacity()) + let unfilled = buf.unfilled_mut(); + let len = DWORD::try_from(unfilled.len()) .expect("buffer too large for win32 api"); // Safety: the OS has no expectation on whether the // buffer is initialized or not. - let buf = buf.inner_mut() as *mut _ as *mut _; - (buf, len) + (unfilled.as_mut_ptr() as *mut _, len) } None => (ptr::null_mut(), 0), }; @@ -415,9 +415,9 @@ impl NamedPipe { self.io.as_raw_handle(), buf, len, - n.as_mut_ptr(), - total_bytes_available.as_mut_ptr(), - bytes_left_this_message.as_mut_ptr(), + &mut n, + &mut total_bytes_available, + &mut bytes_left_this_message, ) }; @@ -425,18 +425,19 @@ impl NamedPipe { return Err(io::Error::last_os_error()); } - let n = usize::try_from(n.assume_init()).expect("output size too large"); + let n = usize::try_from(n).expect("output size too large"); if let Some(buf) = buf { // Safety: we trust that the OS has initialized up until `n` // through the call to PeekNamedPipe. buf.assume_init(n); + buf.advance(n); } - let total_bytes_available = usize::try_from(total_bytes_available.assume_init()) - .expect("available bytes too large"); - let bytes_left_this_message = usize::try_from(bytes_left_this_message.assume_init()) - .expect("bytes left in message too large"); + let total_bytes_available = + usize::try_from(total_bytes_available).expect("available bytes too large"); + let bytes_left_this_message = + usize::try_from(bytes_left_this_message).expect("bytes left in message too large"); let info = PipePeekInfo { total_bytes_available, @@ -953,9 +954,9 @@ impl NamedPipeClientOptions { /// /// * [std::io::ErrorKind::NotFound] - This indicates that the named pipe /// does not exist. Presumably the server is not up. - /// * [ERROR_PIPE_BUSY] - which needs to be tested for through a constant in - /// [winapi]. This error is raised when the named pipe has been created, - /// but the server is not currently waiting for a connection. + /// * [ERROR_PIPE_BUSY] - This error is raised when the named pipe exists, + /// but the server is not currently waiting for a connection. Please see the + /// examples for how to check for this error. /// /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [I/O enabled]: crate::runtime::Builder::enable_io @@ -1100,7 +1101,7 @@ pub struct PipePeekInfo { pub total_bytes_available: usize, /// Indicates the number of bytes left in the current message. /// - /// This is undefined unless the pipe mode is [PipeMode::Message]. + /// If the pipe mode is not [PipeMode::Message], then this is zero. pub bytes_left_this_message: usize, } From a8a207df08f9ea27c32aa4d010b71e552d327eda Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 14 May 2021 17:35:53 +0200 Subject: [PATCH 25/58] rip wait_named_pipe --- tokio/src/net/windows/mod.rs | 3 +- tokio/src/net/windows/named_pipe.rs | 199 ---------------------------- 2 files changed, 1 insertion(+), 201 deletions(-) diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index 2466ceaaff4..414945269b1 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -2,6 +2,5 @@ mod named_pipe; pub use self::named_pipe::{ - wait_named_pipe, NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeEnd, PipeMode, - PipePeekInfo, + NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeEnd, PipeMode, PipePeekInfo, }; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index ad1ac2f87f4..b1efed43508 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -4,7 +4,6 @@ use std::mem; use std::pin::Pin; use std::ptr; use std::task::{Context, Poll}; -use std::time::Duration; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; @@ -21,9 +20,6 @@ mod doc { pub(super) use crate::winapi::um::winnt; pub(super) use mio::windows as mio_windows; - - // Interned constant to wait forever. Not available in winapi. - pub(super) const NMPWAIT_WAIT_FOREVER: DWORD = 0xffffffff; } // NB: none of these shows up in public API, so don't document them. @@ -1105,201 +1101,6 @@ pub struct PipePeekInfo { pub bytes_left_this_message: usize, } -/// Waits until either a configurable time-out interval elapses or an instance -/// of the specified named pipe is available for connection. That is, the pipe's -/// server process has a pending [connect] operation waiting on the other end of -/// the pipe. -/// -/// This function **blocks** until the given named pipe is available. If you -/// want to use it in an async context, make use of [spawn_blocking], but be -/// aware that if used carelessly it might end up starving the thread pool. One -/// should in particular avoid to use it with an overly large timeout, because -/// the operation cannot be cancelled. -/// -/// If a zero duration is provided, the default timeout of the named pipe will -/// be used. -/// -/// This corresponds to the [WaitNamedPipeW] system call. -/// -/// [spawn_blocking]: crate::task::spawn_blocking -/// [WaitNamedPipeW]: -/// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea -/// [connect]: NamedPipe::connect -/// -/// # Errors -/// -/// If a server hasn't already created the named pipe, this will return an error -/// with the kind [std::io::ErrorKind::NotFound]. -/// -/// ``` -/// use std::io; -/// use std::time::Duration; -/// use tokio::net::windows::wait_named_pipe; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error1"; -/// -/// # fn main() -> std::io::Result<()> { -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).unwrap_err(); -/// -/// // Errors because no server exists. -/// assert_eq!(e.kind(), io::ErrorKind::NotFound); -/// # Ok(()) } -/// ``` -/// -/// Waiting while a server is being closed will first cause it to block, but -/// then error with [std::io::ErrorKind::NotFound]. -/// -/// ``` -/// use std::io; -/// use std::time::Duration; -/// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions, wait_named_pipe}; -/// use tokio::time; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error2"; -/// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let server = NamedPipeOptions::new() -/// .create(PIPE_NAME)?; -/// -/// // Construct a client that occupies the server so that the next one is -/// // forced to wait. -/// let _client = NamedPipeClientOptions::new() -/// .create(PIPE_NAME)?; -/// -/// tokio::spawn(async move { -/// // Drop the server after 100ms, causing the waiting client to err. -/// time::sleep(Duration::from_millis(100)).await; -/// drop(server); -/// }); -/// -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_secs(1))).unwrap_err(); -/// -/// assert_eq!(e.kind(), io::ErrorKind::NotFound); -/// # Ok(()) } -/// ``` -/// -/// If a wait times out, this function will error with -/// [io::ErrorKind::TimedOut]. -/// -/// ``` -/// use std::io; -/// use std::time::Duration; -/// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions, wait_named_pipe}; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait-error-timedout"; -/// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let server = NamedPipeOptions::new().create(PIPE_NAME)?; -/// // connect one client, causing the server to be occupied. -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10)))?; -/// let client1 = NamedPipeClientOptions::new().create(PIPE_NAME)?; -/// -/// // this times out because the server is busy. -/// let e = wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(10))).unwrap_err(); -/// assert_eq!(e.kind(), io::ErrorKind::TimedOut); -/// # Ok(()) } -/// ``` -/// -/// # Examples -/// -/// ``` -/// use std::io; -/// use std::time::Duration; -/// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; -/// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions, wait_named_pipe}; -/// use winapi::shared::winerror; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-wait"; -/// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// // The first server needs to be constructed early so that clients can -/// // be correctly connected. Otherwise calling .wait will cause the client -/// // to error because the file descriptor doesn't exist. -/// // -/// // Here we also make use of `first_pipe_instance`, which will ensure -/// // that there are no other servers up and running already. -/// let mut server = NamedPipeOptions::new() -/// .first_pipe_instance(true) -/// .create(PIPE_NAME)?; -/// -/// let client = tokio::spawn(async move { -/// // Wait forever until a socket is available to be connected to. -/// let mut client = loop { -/// match NamedPipeClientOptions::new().create(PIPE_NAME) { -/// Ok(client) => break client, -/// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), -/// Err(e) => return Err(e), -/// } -/// -/// tokio::task::spawn_blocking(move || { -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(500))) -/// }).await??; -/// }; -/// -/// let mut buf = [0u8; 4]; -/// client.read_exact(&mut buf[..]).await?; -/// Ok::<_, io::Error>(buf) -/// }); -/// -/// let server = tokio::spawn(async move { -/// tokio::time::sleep(Duration::from_millis(200)).await; -/// -/// // Calling `connect` is necessary for the waiting client to wake up, -/// // even if the server is created after the client. -/// server.connect().await?; -/// -/// server.write_all(b"ping").await?; -/// Ok::<_, io::Error>(()) -/// }); -/// -/// let (client, server) = tokio::try_join!(client, server)?; -/// let payload = client?; -/// assert_eq!(&payload[..], b"ping"); -/// let _ = server?; -/// # Ok(()) } -/// ``` -/// -/// # Panics -/// -/// Panics if the specified duration is larger than `0xffffffff` -/// milliseconds, which is roughly equal to 1193 hours. -/// -/// ```should_panic -/// use std::time::Duration; -/// use tokio::net::windows::wait_named_pipe; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; -/// -/// # fn main() -> std::io::Result<()> { -/// wait_named_pipe(PIPE_NAME, Some(Duration::from_millis(0xffffffff)))?; -/// # Ok(()) } -/// ``` -pub fn wait_named_pipe(addr: impl AsRef, timeout: Option) -> io::Result<()> { - let addr = encode_addr(addr); - - let timeout = match timeout { - Some(timeout) => { - let timeout = timeout.as_millis(); - assert! { - timeout < NMPWAIT_WAIT_FOREVER as u128, - "timeout out of bounds, can wait at most {}ms, but got {}ms", NMPWAIT_WAIT_FOREVER - 1, - timeout - }; - timeout as DWORD - } - None => NMPWAIT_WAIT_FOREVER, - }; - - // Safety: There's nothing unsafe about this. - let result = unsafe { namedpipeapi::WaitNamedPipeW(addr.as_ptr(), timeout) }; - - if result == FALSE { - return Err(io::Error::last_os_error()); - } - - Ok(()) -} - /// Encode an address so that it is a null-terminated wide string. fn encode_addr(addr: impl AsRef) -> Box<[u16]> { addr.as_ref().encode_wide().chain(Some(0)).collect() From 591de4e856ee603e39d53efecadb67aaf1cb88d1 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 17 May 2021 13:41:45 +0200 Subject: [PATCH 26/58] remove dead links --- tokio/src/net/windows/named_pipe.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index b1efed43508..af31dbdbfb1 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -45,7 +45,7 @@ use self::doc::*; /// /// * [std::io::ErrorKind::NotFound] - There is no server available. /// * [ERROR_PIPE_BUSY] - There is a server available, but it is busy. Sleep for -/// a while and try again, or use [wait_named_pipe] until it becomes available. +/// a while and try again. /// /// So a typical client connect loop will look like the this: /// @@ -73,10 +73,10 @@ use self::doc::*; /// ``` /// /// A client will error with [std::io::ErrorKind::NotFound] for most creation -/// oriented operations like [create] or [wait_named_pipe] unless at least once -/// server instance is up and running at all time. This means that the typical -/// listen loop for a server is a bit involved, because we have to ensure that -/// we never drop a server accidentally while a client might want to connect. +/// oriented operations like [create] unless at least once server instance is up +/// and running at all time. This means that the typical listen loop for a +/// server is a bit involved, because we have to ensure that we never drop a +/// server accidentally while a client might want to connect. /// /// ```no_run /// use std::io; From 88b6a9f6ef9f7e19acfb102add3a2a56fdaacb6c Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 17 May 2021 14:04:28 +0200 Subject: [PATCH 27/58] remove panicky FromRawHandle impl --- tokio/src/net/windows/named_pipe.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index af31dbdbfb1..930aa4f40ae 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -481,21 +481,6 @@ impl AsyncWrite for NamedPipe { } } -/// Raw handle conversion for [NamedPipe]. -/// -/// # Panics -/// -/// This panics if called outside of a [Tokio Runtime] which doesn't have [I/O -/// enabled]. -/// -/// [Tokio Runtime]: crate::runtime::Runtime -/// [I/O enabled]: crate::runtime::Builder::enable_io -impl FromRawHandle for NamedPipe { - unsafe fn from_raw_handle(handle: RawHandle) -> Self { - Self::try_from_raw_handle(handle).unwrap() - } -} - impl AsRawHandle for NamedPipe { fn as_raw_handle(&self) -> RawHandle { self.io.as_raw_handle() From 056c5dee1279403d1762a23e0f42be4a6a256c05 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 17 May 2021 14:57:00 +0200 Subject: [PATCH 28/58] make from_raw_handle pub --- tokio/src/net/windows/named_pipe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 930aa4f40ae..cb8a7f48857 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -157,7 +157,7 @@ impl NamedPipe { /// /// [Tokio Runtime]: crate::runtime::Runtime /// [I/O enabled]: crate::runtime::Builder::enable_io - unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { + pub unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); Ok(NamedPipe { From 0d6af110bd5c5e8fe8a6d717c9531360f20f23d1 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 12:37:50 +0200 Subject: [PATCH 29/58] adjust documentation --- tokio/src/net/windows/named_pipe.rs | 144 ++++++++++++++-------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index cb8a7f48857..e847a8df02c 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -36,15 +36,15 @@ use self::doc::*; /// A [Windows named pipe]. /// -/// Constructed using [NamedPipeClientOptions::create] for clients, or -/// [NamedPipeOptions::create] for servers. See their corresponding +/// Constructed using [`NamedPipeClientOptions::create`] for clients, or +/// [`NamedPipeOptions::create`] for servers. See their corresponding /// documentation for examples. /// -/// Connecting a client involves a few steps. First we must try to [create], the +/// Connecting a client involves a few steps. First we must try to [`create`], the /// error typically indicates one of two things: /// -/// * [std::io::ErrorKind::NotFound] - There is no server available. -/// * [ERROR_PIPE_BUSY] - There is a server available, but it is busy. Sleep for +/// * [`std::io::ErrorKind::NotFound`] - There is no server available. +/// * [`ERROR_PIPE_BUSY`] - There is a server available, but it is busy. Sleep for /// a while and try again. /// /// So a typical client connect loop will look like the this: @@ -72,8 +72,8 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// A client will error with [std::io::ErrorKind::NotFound] for most creation -/// oriented operations like [create] unless at least once server instance is up +/// A client will error with [`std::io::ErrorKind::NotFound`] for most creation +/// oriented operations like [`create`] unless at least once server instance is up /// and running at all time. This means that the typical listen loop for a /// server is a bit involved, because we have to ensure that we never drop a /// server accidentally while a client might want to connect. @@ -130,8 +130,8 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// [create]: NamedPipeClientOptions::create -/// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY +/// [`create`]: NamedPipeClientOptions::create +/// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] pub struct NamedPipe { @@ -169,9 +169,9 @@ impl NamedPipe { /// connect to an instance of a named pipe. A client process connects by /// creating a named pipe with the same name. /// - /// This corresponds to the [ConnectNamedPipe] system call. + /// This corresponds to the [`ConnectNamedPipe`] system call. /// - /// [ConnectNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe + /// [`ConnectNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe /// /// ```no_run /// use tokio::net::windows::NamedPipeOptions; @@ -314,13 +314,13 @@ impl NamedPipe { /// /// Data reported through peek is sporadic. Once peek returns any data for a /// given named pipe, further calls to it are not gauranteed to return the - /// same or higher number of bytes available ([total_bytes_available]). It + /// same or higher number of bytes available ([`total_bytes_available`]). It /// might even report a count of `0` even if no data has been read from the /// named pipe that was previously peeked. /// /// Peeking does not update the state of the named pipe, so in order to /// advance it you have to actively issue reads. A peek reporting a number - /// of bytes available ([total_bytes_available]) of `0` does not guarantee + /// of bytes available ([`total_bytes_available`]) of `0` does not guarantee /// that there is no data available to read from the named pipe. Even if a /// peer is writing data, reads still have to be issued for the state of the /// named pipe to update. @@ -331,7 +331,7 @@ impl NamedPipe { /// You can play around with the [`named-pipe-peek` example] to get a feel /// for how this function behaves. /// - /// [total_bytes_available]: PipePeekInfo::total_bytes_available + /// [`total_bytes_available`]: PipePeekInfo::total_bytes_available /// [`named-pipe-peek` example]: https://github.com/tokio-rs/tokio/blob/master/examples/named-pipe-peek.rs /// /// # Examples @@ -504,7 +504,7 @@ macro_rules! bool_flag { /// options. This is required to use for named pipe servers who wants to modify /// pipe-related options. /// -/// See [NamedPipeOptions::create]. +/// See [`NamedPipeOptions::create`]. #[derive(Debug, Clone)] pub struct NamedPipeOptions { open_mode: DWORD, @@ -541,12 +541,12 @@ impl NamedPipeOptions { /// The pipe mode. /// - /// The default pipe mode is [PipeMode::Byte]. See [PipeMode] for + /// The default pipe mode is [`PipeMode::Byte`]. See [`PipeMode`] for /// documentation of what each mode means. /// - /// This corresponding to specifying [dwPipeMode]. + /// This corresponding to specifying [`dwPipeMode`]. /// - /// [dwPipeMode]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [`dwPipeMode`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self { self.pipe_mode = match pipe_mode { PipeMode::Byte => winbase::PIPE_TYPE_BYTE, @@ -558,9 +558,9 @@ impl NamedPipeOptions { /// The flow of data in the pipe goes from client to server only. /// - /// This corresponds to setting [PIPE_ACCESS_INBOUND]. + /// This corresponds to setting [`PIPE_ACCESS_INBOUND`]. /// - /// [PIPE_ACCESS_INBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound + /// [`PIPE_ACCESS_INBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound /// /// # Examples /// @@ -624,9 +624,9 @@ impl NamedPipeOptions { /// The flow of data in the pipe goes from server to client only. /// - /// This corresponds to setting [PIPE_ACCESS_OUTBOUND]. + /// This corresponds to setting [`PIPE_ACCESS_OUTBOUND`]. /// - /// [PIPE_ACCESS_OUTBOUND]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound + /// [`PIPE_ACCESS_OUTBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound /// /// # Examples /// @@ -692,12 +692,12 @@ impl NamedPipeOptions { /// If you attempt to create multiple instances of a pipe with this flag, /// creation of the first instance succeeds, but creation of the next - /// instance fails with [ERROR_ACCESS_DENIED]. + /// instance fails with [`ERROR_ACCESS_DENIED`]. /// - /// This corresponds to setting [FILE_FLAG_FIRST_PIPE_INSTANCE]. + /// This corresponds to setting [`FILE_FLAG_FIRST_PIPE_INSTANCE`]. /// - /// [ERROR_ACCESS_DENIED]: crate::winapi::shared::winerror::ERROR_ACCESS_DENIED - /// [FILE_FLAG_FIRST_PIPE_INSTANCE]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance + /// [`ERROR_ACCESS_DENIED`]: crate::winapi::shared::winerror::ERROR_ACCESS_DENIED + /// [`FILE_FLAG_FIRST_PIPE_INSTANCE`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance /// /// # Examples /// @@ -732,9 +732,9 @@ impl NamedPipeOptions { /// Indicates whether this server can accept remote clients or not. This is /// enabled by default. /// - /// This corresponds to setting [PIPE_REJECT_REMOTE_CLIENTS]. + /// This corresponds to setting [`PIPE_REJECT_REMOTE_CLIENTS`]. /// - /// [PIPE_REJECT_REMOTE_CLIENTS]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients + /// [`PIPE_REJECT_REMOTE_CLIENTS`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients pub fn reject_remote_clients(&mut self, reject: bool) -> &mut Self { bool_flag!(self.pipe_mode, reject, winbase::PIPE_REJECT_REMOTE_CLIENTS); self @@ -745,10 +745,9 @@ impl NamedPipeOptions { /// be specified for other instances of the pipe. Acceptable values are in /// the range 1 through 254. The default value is unlimited. /// - /// This corresponds to specifying [nMaxInstances]. + /// This corresponds to specifying [`nMaxInstances`]. /// - /// [nMaxInstances]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea - /// [PIPE_UNLIMITED_INSTANCES]: crate::winapi::um::winbase::PIPE_UNLIMITED_INSTANCES + /// [`nMaxInstances`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// /// # Panics /// @@ -770,9 +769,9 @@ impl NamedPipeOptions { /// The number of bytes to reserve for the output buffer. /// - /// This corresponds to specifying [nOutBufferSize]. + /// This corresponds to specifying [`nOutBufferSize`]. /// - /// [nOutBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [`nOutBufferSize`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea pub fn out_buffer_size(&mut self, buffer: u32) -> &mut Self { self.out_buffer_size = buffer as DWORD; self @@ -780,9 +779,9 @@ impl NamedPipeOptions { /// The number of bytes to reserve for the input buffer. /// - /// This corresponds to specifying [nInBufferSize]. + /// This corresponds to specifying [`nInBufferSize`]. /// - /// [nInBufferSize]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [`nInBufferSize`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea pub fn in_buffer_size(&mut self, buffer: u32) -> &mut Self { self.in_buffer_size = buffer as DWORD; self @@ -790,10 +789,10 @@ impl NamedPipeOptions { /// Create the named pipe identified by `addr` for use as a server. /// - /// This function will call the [CreateNamedPipe] function and return the + /// This function will call the [`CreateNamedPipe`] function and return the /// result. /// - /// [CreateNamedPipe]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + /// [`CreateNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// /// # Errors /// @@ -822,7 +821,7 @@ impl NamedPipeOptions { /// Create the named pipe identified by `addr` for use as a server. /// - /// This is the same as [create][NamedPipeOptions::create] except that it + /// This is the same as [`create`][NamedPipeOptions::create] except that it /// supports providing security attributes. /// /// # Errors @@ -836,9 +835,9 @@ impl NamedPipeOptions { /// # Safety /// /// The caller must ensure that `attrs` points to an initialized instance of - /// a [SECURITY_ATTRIBUTES] structure. + /// a [`SECURITY_ATTRIBUTES`] structure. /// - /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, @@ -871,7 +870,7 @@ impl NamedPipeOptions { /// A builder suitable for building and interacting with named pipes from the /// client side. /// -/// See [NamedPipeClientOptions::create]. +/// See [`NamedPipeClientOptions::create`]. #[derive(Debug, Clone)] pub struct NamedPipeClientOptions { desired_access: DWORD, @@ -899,10 +898,10 @@ impl NamedPipeClientOptions { /// If the client supports reading data. This is enabled by default. /// - /// This corresponds to setting [GENERIC_READ] in the call to [CreateFile]. + /// This corresponds to setting [`GENERIC_READ`] in the call to [`CreateFile`]. /// - /// [GENERIC_READ]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights - /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + /// [`GENERIC_READ`]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew pub fn read(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.desired_access, allowed, winnt::GENERIC_READ); self @@ -910,10 +909,10 @@ impl NamedPipeClientOptions { /// If the created pipe supports writing data. This is enabled by default. /// - /// This corresponds to setting [GENERIC_WRITE] in the call to [CreateFile]. + /// This corresponds to setting [`GENERIC_WRITE`] in the call to [`CreateFile`]. /// - /// [GENERIC_WRITE]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights - /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + /// [`GENERIC_WRITE`]: https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew pub fn write(&mut self, allowed: bool) -> &mut Self { bool_flag!(self.desired_access, allowed, winnt::GENERIC_WRITE); self @@ -921,28 +920,28 @@ impl NamedPipeClientOptions { /// Open the named pipe identified by `addr`. /// - /// This constructs the handle using [CreateFile]. + /// This constructs the handle using [`CreateFile`]. /// - /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// /// # Errors /// /// This errors if called outside of a [Tokio Runtime] which doesn't have /// [I/O enabled] or if any OS-specific I/O errors occur. /// - /// There are a few errors you should be aware of that you need to take into - /// account when creating a named pipe on the client side: + /// There are a few errors you need to take into account when creating a + /// named pipe on the client side: /// - /// * [std::io::ErrorKind::NotFound] - This indicates that the named pipe + /// * [`std::io::ErrorKind::NotFound`] - This indicates that the named pipe /// does not exist. Presumably the server is not up. - /// * [ERROR_PIPE_BUSY] - This error is raised when the named pipe exists, + /// * [`ERROR_PIPE_BUSY`] - This error is raised when the named pipe exists, /// but the server is not currently waiting for a connection. Please see the /// examples for how to check for this error. /// - /// [ERROR_PIPE_BUSY]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY + /// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [I/O enabled]: crate::runtime::Builder::enable_io /// [Tokio Runtime]: crate::runtime::Runtime - /// [winapi]: crate::winapi + /// [`winapi`]: crate::winapi /// /// A connect loop that waits until a socket becomes available looks like /// this: @@ -977,15 +976,15 @@ impl NamedPipeClientOptions { /// Open the named pipe identified by `addr`. /// - /// This is the same as [create][NamedPipeClientOptions::create] except that + /// This is the same as [`create`][NamedPipeClientOptions::create] except that /// it supports providing security attributes. /// /// # Safety /// /// The caller must ensure that `attrs` points to an initialized instance - /// of a [SECURITY_ATTRIBUTES] structure. + /// of a [`SECURITY_ATTRIBUTES`] structure. /// - /// [SECURITY_ATTRIBUTES]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, @@ -1018,45 +1017,45 @@ impl NamedPipeClientOptions { } } -/// The pipe mode of a [NamedPipe]. +/// The pipe mode of a [`NamedPipe`]. /// -/// Set through [NamedPipeOptions::pipe_mode]. +/// Set through [`NamedPipeOptions::pipe_mode`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeMode { /// Data is written to the pipe as a stream of bytes. The pipe does not /// distinguish bytes written during different write operations. /// - /// Corresponds to [PIPE_TYPE_BYTE][crate::winapi::um::winbase::PIPE_TYPE_BYTE]. + /// Corresponds to [`PIPE_TYPE_BYTE`][crate::winapi::um::winbase::PIPE_TYPE_BYTE]. Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading - /// function on [NamedPipe] returns [ERROR_MORE_DATA] when a message is not + /// function on [`NamedPipe`] returns [`ERROR_MORE_DATA`] when a message is not /// read completely. /// - /// Corresponds to [PIPE_TYPE_MESSAGE][crate::winapi::um::winbase::PIPE_TYPE_MESSAGE]. + /// Corresponds to [`PIPE_TYPE_MESSAGE`][crate::winapi::um::winbase::PIPE_TYPE_MESSAGE]. /// - /// [ERROR_MORE_DATA]: crate::winapi::shared::winerror::ERROR_MORE_DATA + /// [`ERROR_MORE_DATA`]: crate::winapi::shared::winerror::ERROR_MORE_DATA Message, } -/// Indicates the end of a [NamedPipe]. +/// Indicates the end of a [`NamedPipe`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeEnd { - /// The [NamedPipe] refers to the client end of a named pipe instance. + /// The [`NamedPipe`] refers to the client end of a named pipe instance. /// - /// Corresponds to [PIPE_CLIENT_END][crate::winapi::um::winbase::PIPE_CLIENT_END]. + /// Corresponds to [`PIPE_CLIENT_END`][`crate::winapi::um::winbase::PIPE_CLIENT_END`]. Client, - /// The [NamedPipe] refers to the server end of a named pipe instance. + /// The [`NamedPipe`] refers to the server end of a named pipe instance. /// - /// Corresponds to [PIPE_SERVER_END][crate::winapi::um::winbase::PIPE_SERVER_END]. + /// Corresponds to [`PIPE_SERVER_END`][`crate::winapi::um::winbase::PIPE_SERVER_END`]. Server, } /// Information about a named pipe. /// -/// Constructed through [NamedPipe::info]. +/// Constructed through [`NamedPipe::info`]. #[derive(Debug)] #[non_exhaustive] pub struct PipeInfo { @@ -1074,7 +1073,7 @@ pub struct PipeInfo { /// Information about a pipe gained by peeking it. /// -/// See [NamedPipe::peek]. +/// See [`NamedPipe::peek`]. #[derive(Debug, Clone)] #[non_exhaustive] pub struct PipePeekInfo { @@ -1082,7 +1081,8 @@ pub struct PipePeekInfo { pub total_bytes_available: usize, /// Indicates the number of bytes left in the current message. /// - /// If the pipe mode is not [PipeMode::Message], then this is zero. + /// If the pipe mode is not [`PipeMode::Message`], then this is + /// zero. pub bytes_left_this_message: usize, } From 18505ffd5cf0e7399df543a7031776a1566d0c0a Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 12:42:26 +0200 Subject: [PATCH 30/58] avoid additional allocations in encode_addr Co-authored-by: Alice Ryhl --- tokio/src/net/windows/named_pipe.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index e847a8df02c..69a8e730483 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1088,5 +1088,9 @@ pub struct PipePeekInfo { /// Encode an address so that it is a null-terminated wide string. fn encode_addr(addr: impl AsRef) -> Box<[u16]> { - addr.as_ref().encode_wide().chain(Some(0)).collect() + let len = addr.as_ref().encode_wide().count(); + let mut vec = Vec::with_capacity(len + 1); + vec.extend(addr.as_ref().encode_wide()); + vec.push(0); + vec.into_boxed_slice() } From 7586e384502341be352ec428f2a05bce956f52b0 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:04:20 +0200 Subject: [PATCH 31/58] clamp buffers --- tokio/src/net/windows/named_pipe.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 69a8e730483..3b9f6d7ce9c 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -398,10 +398,9 @@ impl NamedPipe { let (buf, len) = match &mut buf { Some(buf) => { let unfilled = buf.unfilled_mut(); - let len = DWORD::try_from(unfilled.len()) - .expect("buffer too large for win32 api"); - // Safety: the OS has no expectation on whether the - // buffer is initialized or not. + let len = ::try_from(unfilled.len()).unwrap_or(DWORD::MAX); + // NB: The OS has no expectation on whether the buffer + // is initialized or not. (unfilled.as_mut_ptr() as *mut _, len) } None => (ptr::null_mut(), 0), @@ -421,7 +420,9 @@ impl NamedPipe { return Err(io::Error::last_os_error()); } - let n = usize::try_from(n).expect("output size too large"); + // NB: This is either guaranteed to fit within `usize` because of + // the clamping above, or the OS is reporting bogus values. + let n = usize::try_from(n).unwrap(); if let Some(buf) = buf { // Safety: we trust that the OS has initialized up until `n` From 6099b0c79409533e5cf2cd86a8497c8b05207a63 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:12:06 +0200 Subject: [PATCH 32/58] remove shutdown logic in doctest --- tokio/src/net/windows/named_pipe.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 3b9f6d7ce9c..490f7c20700 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -80,9 +80,7 @@ use self::doc::*; /// /// ```no_run /// use std::io; -/// use std::sync::Arc; /// use tokio::net::windows::NamedPipeOptions; -/// use tokio::sync::Notify; /// /// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-server"; /// @@ -97,36 +95,30 @@ use self::doc::*; /// .first_pipe_instance(true) /// .create(PIPE_NAME)?; /// -/// let shutdown = Arc::new(Notify::new()); -/// let shutdown2 = shutdown.clone(); -/// /// // Spawn the server loop. /// let server = tokio::spawn(async move { /// loop { /// // Wait for a client to connect. -/// let connected = tokio::select! { -/// connected = server.connect() => connected, -/// _ = shutdown2.notified() => break, -/// }; +/// let connected = server.connect().await?; /// /// // Construct the next server to be connected before sending the one /// // we already have of onto a task. This ensures that the server /// // isn't closed (after it's done in the task) before a new one is /// // available. Otherwise the client might error with /// // `io::ErrorKind::NotFound`. -/// server = NamedPipeOptions::new() -/// .create(PIPE_NAME)?; +/// server = NamedPipeOptions::new().create(PIPE_NAME)?; /// /// let client = tokio::spawn(async move { /// /* use the connected client */ /// # Ok::<_, std::io::Error>(()) /// }); +/// # if true { break } // needed for type inference to work /// } /// /// Ok::<_, io::Error>(()) /// }); -/// # shutdown.notify_one(); -/// # let _ = server.await??; +/// +/// /* do something else not server related here */ /// # Ok(()) } /// ``` /// From 477a569ef9e5bc8eafc17fe5b2c46c8fc999d421 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:32:53 +0200 Subject: [PATCH 33/58] allow setting security_qos_flags and ensure SECURITY_SQOS_PRESENT is set --- tokio/src/net/windows/named_pipe.rs | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 490f7c20700..36375fb1a9d 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -867,6 +867,7 @@ impl NamedPipeOptions { #[derive(Debug, Clone)] pub struct NamedPipeClientOptions { desired_access: DWORD, + security_qos_flags: DWORD, } impl NamedPipeClientOptions { @@ -886,6 +887,7 @@ impl NamedPipeClientOptions { pub fn new() -> Self { Self { desired_access: winnt::GENERIC_READ | winnt::GENERIC_WRITE, + security_qos_flags: winbase::SECURITY_IDENTIFICATION | winbase::SECURITY_SQOS_PRESENT, } } @@ -911,6 +913,33 @@ impl NamedPipeClientOptions { self } + /// Sets qos flags which are combined with other flags and attributes in the + /// call to [`CreateFile`]. + /// + /// By default `security_qos_flags` is set to [`SECURITY_IDENTIFICATION`], + /// calling this function would override that value completely with the + /// argument specified. + /// + /// When `security_qos_flags` is not set, a malicious program can gain the + /// elevated privileges of a privileged Rust process when it allows opening + /// user-specified paths, by tricking it into opening a named pipe. So + /// arguably `security_qos_flags` should also be set when opening arbitrary + /// paths. However the bits can then conflict with other flags, specifically + /// `FILE_FLAG_OPEN_NO_RECALL`. + /// + /// For information about possible values, see [Impersonation Levels] on the + /// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set + /// automatically when using this method. + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`SECURITY_IDENTIFICATION`]: winbase::SECURITY_IDENTIFICATION + /// [Impersonation Levels]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level + pub fn security_qos_flags(&mut self, flags: u32) -> &mut Self { + // See: https://github.com/rust-lang/rust/pull/58216 + self.security_qos_flags = flags | winbase::SECURITY_SQOS_PRESENT; + self + } + /// Open the named pipe identified by `addr`. /// /// This constructs the handle using [`CreateFile`]. @@ -995,7 +1024,7 @@ impl NamedPipeClientOptions { 0, attrs as *mut _, fileapi::OPEN_EXISTING, - winbase::FILE_FLAG_OVERLAPPED | winbase::SECURITY_IDENTIFICATION, + self.get_flags(), ptr::null_mut(), ); @@ -1008,6 +1037,10 @@ impl NamedPipeClientOptions { Ok(NamedPipe { io }) } + + fn get_flags(&self) -> u32 { + self.security_qos_flags | winbase::FILE_FLAG_OVERLAPPED + } } /// The pipe mode of a [`NamedPipe`]. From 50ff91c04d0b4f68565fb9ca97f3e37148001296 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:40:27 +0200 Subject: [PATCH 34/58] adjust safety documentation to mention it can be null --- tokio/src/net/windows/named_pipe.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 36375fb1a9d..3a7cfa9febc 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -998,15 +998,18 @@ impl NamedPipeClientOptions { /// Open the named pipe identified by `addr`. /// - /// This is the same as [`create`][NamedPipeClientOptions::create] except that - /// it supports providing security attributes. + /// This is the same as [`create`] except that it supports providing + /// security attributes. /// /// # Safety /// - /// The caller must ensure that `attrs` points to an initialized instance - /// of a [`SECURITY_ATTRIBUTES`] structure. + /// The caller must ensure that `attrs` points to an initialized instance of + /// a [`SECURITY_ATTRIBUTES`] structure. This argument *can* be set to null + /// and be ignored. But the caller should then instead prefer to use + /// [`create`]. /// /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [`create`]: [NamedPipeClientOptions::create] pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, From e7b10108ff70a32cd37a5a01c5f27f5a796e0671 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:50:18 +0200 Subject: [PATCH 35/58] document the behavior of max_instances --- tokio/src/net/windows/named_pipe.rs | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 3a7cfa9febc..d51cf7d8f7b 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -738,10 +738,48 @@ impl NamedPipeOptions { /// be specified for other instances of the pipe. Acceptable values are in /// the range 1 through 254. The default value is unlimited. /// + /// This value is only respected by the first instance that creates the pipe + /// and is ignored otherwise. + /// /// This corresponds to specifying [`nMaxInstances`]. /// /// [`nMaxInstances`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// + /// # Errors + /// + /// The same numbers of `max_instances` have to be used by all servers. Any + /// additional servers trying to be built which uses a mismatching value + /// will error. + /// + /// ``` + /// use std::io; + /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// + /// // Use this from winapi instead in real code. + /// const ERROR_PIPE_BUSY: u32 = 231; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-max-instances"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let mut server = NamedPipeOptions::new(); + /// server.max_instances(2); + /// + /// let s1 = server.create(PIPE_NAME)?; + /// let c1 = NamedPipeClientOptions::new().create(PIPE_NAME); + /// + /// let s2 = server.create(PIPE_NAME)?; + /// let c2 = NamedPipeClientOptions::new().create(PIPE_NAME); + /// + /// // Too many servers! + /// let e = server.create(PIPE_NAME).unwrap_err(); + /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32)); + /// + /// // Still too many servers even if we specify a higher value! + /// let e = server.max_instances(100).create(PIPE_NAME).unwrap_err(); + /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32)); + /// # Ok(()) } + /// ``` + /// /// # Panics /// /// This function will panic if more than 254 instances are specified. If From bb28d508c24fa59750ab137701238e892f772426 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:54:10 +0200 Subject: [PATCH 36/58] add missing constant in doc building --- tokio/src/doc/winapi.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tokio/src/doc/winapi.rs b/tokio/src/doc/winapi.rs index e73920e97cf..b7f80130a72 100644 --- a/tokio/src/doc/winapi.rs +++ b/tokio/src/doc/winapi.rs @@ -48,5 +48,10 @@ pub mod um { /// /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_SERVER_END.html pub type PIPE_SERVER_END = crate::doc::NotDefinedHere; + + /// See [winapi::um::winbase::SECURITY_IDENTIFICATION][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.SECURITY_IDENTIFICATION.html + pub type SECURITY_IDENTIFICATION = crate::doc::NotDefinedHere; } } From da2920bef1a70d47c839b7f938ac6b568198e06c Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:54:43 +0200 Subject: [PATCH 37/58] Update tokio/src/net/windows/named_pipe.rs Co-authored-by: Alice Ryhl --- tokio/src/net/windows/named_pipe.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index d51cf7d8f7b..3c2b619b1cc 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1041,10 +1041,9 @@ impl NamedPipeClientOptions { /// /// # Safety /// - /// The caller must ensure that `attrs` points to an initialized instance of - /// a [`SECURITY_ATTRIBUTES`] structure. This argument *can* be set to null - /// and be ignored. But the caller should then instead prefer to use - /// [`create`]. + /// The `attrs` argument must either be null or point at a valid instance + /// of the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, + /// the behavior is identical to calling the [`create`] method. /// /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] /// [`create`]: [NamedPipeClientOptions::create] From ed598ca851040cac3ae7b9c0244bb8b3ebd30f46 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 13:59:45 +0200 Subject: [PATCH 38/58] fix some broken and weird links --- tokio/src/net/windows/named_pipe.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 3c2b619b1cc..e0b08450aee 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -868,7 +868,7 @@ impl NamedPipeOptions { /// The caller must ensure that `attrs` points to an initialized instance of /// a [`SECURITY_ATTRIBUTES`] structure. /// - /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] + /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, @@ -970,7 +970,7 @@ impl NamedPipeClientOptions { /// automatically when using this method. /// /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea - /// [`SECURITY_IDENTIFICATION`]: winbase::SECURITY_IDENTIFICATION + /// [`SECURITY_IDENTIFICATION`]: crate::winapi::um::winbase::SECURITY_IDENTIFICATION /// [Impersonation Levels]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level pub fn security_qos_flags(&mut self, flags: u32) -> &mut Self { // See: https://github.com/rust-lang/rust/pull/58216 @@ -1111,11 +1111,11 @@ pub enum PipeMode { pub enum PipeEnd { /// The [`NamedPipe`] refers to the client end of a named pipe instance. /// - /// Corresponds to [`PIPE_CLIENT_END`][`crate::winapi::um::winbase::PIPE_CLIENT_END`]. + /// Corresponds to [`PIPE_CLIENT_END`][crate::winapi::um::winbase::PIPE_CLIENT_END]. Client, /// The [`NamedPipe`] refers to the server end of a named pipe instance. /// - /// Corresponds to [`PIPE_SERVER_END`][`crate::winapi::um::winbase::PIPE_SERVER_END`]. + /// Corresponds to [`PIPE_SERVER_END`][crate::winapi::um::winbase::PIPE_SERVER_END]. Server, } From e4dd1a079367a1a486757dea6498116e4a3940b3 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 14:10:26 +0200 Subject: [PATCH 39/58] use winapi instead of redefining a const --- tokio/src/net/windows/named_pipe.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index e0b08450aee..c6ce3c443b6 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -754,9 +754,7 @@ impl NamedPipeOptions { /// ``` /// use std::io; /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; - /// - /// // Use this from winapi instead in real code. - /// const ERROR_PIPE_BUSY: u32 = 231; + /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-max-instances"; /// @@ -772,11 +770,11 @@ impl NamedPipeOptions { /// /// // Too many servers! /// let e = server.create(PIPE_NAME).unwrap_err(); - /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32)); + /// assert_eq!(e.raw_os_error(), Some(winerror::ERROR_PIPE_BUSY as i32)); /// /// // Still too many servers even if we specify a higher value! /// let e = server.max_instances(100).create(PIPE_NAME).unwrap_err(); - /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32)); + /// assert_eq!(e.raw_os_error(), Some(winerror::ERROR_PIPE_BUSY as i32)); /// # Ok(()) } /// ``` /// From 5753f3c815a485f7f0097fac66337fafcf234760 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 14:13:56 +0200 Subject: [PATCH 40/58] more missing doc items --- tokio/src/doc/winapi.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tokio/src/doc/winapi.rs b/tokio/src/doc/winapi.rs index b7f80130a72..be68749e00d 100644 --- a/tokio/src/doc/winapi.rs +++ b/tokio/src/doc/winapi.rs @@ -54,4 +54,13 @@ pub mod um { /// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.SECURITY_IDENTIFICATION.html pub type SECURITY_IDENTIFICATION = crate::doc::NotDefinedHere; } + + /// See [winapi::um::minwinbase](https://docs.rs/winapi/*/winapi/um/minwinbase/index.html). + #[allow(non_camel_case_types)] + pub mod minwinbase { + /// See [winapi::um::minwinbase::SECURITY_ATTRIBUTES][winapi] + /// + /// [winapi]: https://docs.rs/winapi/*/winapi/um/minwinbase/constant.SECURITY_ATTRIBUTES.html + pub type SECURITY_ATTRIBUTES = crate::doc::NotDefinedHere; + } } From 31b8584833df67a3a08c1481e5466c8552cd3b48 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 19 May 2021 14:15:04 +0200 Subject: [PATCH 41/58] refactor weird links --- tokio/src/net/windows/named_pipe.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index c6ce3c443b6..66b738acd2e 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1043,8 +1043,8 @@ impl NamedPipeClientOptions { /// of the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, /// the behavior is identical to calling the [`create`] method. /// - /// [`SECURITY_ATTRIBUTES`]: [crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES] - /// [`create`]: [NamedPipeClientOptions::create] + /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES + /// [`create`]: NamedPipeClientOptions::create pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, From 2a6acd9ae8d84980938127aaf14159c2754e2377 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Thu, 20 May 2021 16:54:59 +0200 Subject: [PATCH 42/58] use *mut c_void for opaque pointers --- tokio/src/net/windows/named_pipe.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 66b738acd2e..a9f98a9d840 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -4,6 +4,7 @@ use std::mem; use std::pin::Pin; use std::ptr; use std::task::{Context, Poll}; +use std::ffi::c_void; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; @@ -870,7 +871,7 @@ impl NamedPipeOptions { pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, - attrs: *mut (), + attrs: *mut c_void, ) -> io::Result { let addr = encode_addr(addr); @@ -1048,7 +1049,7 @@ impl NamedPipeClientOptions { pub unsafe fn create_with_security_attributes( &self, addr: impl AsRef, - attrs: *mut (), + attrs: *mut c_void, ) -> io::Result { let addr = encode_addr(addr); From e355dcaa1926bd59916c6a1e2b8972579e6ae9ff Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Thu, 20 May 2021 17:32:34 +0200 Subject: [PATCH 43/58] rustfmt again --- tokio/src/net/windows/named_pipe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index a9f98a9d840..ff8e8b18f3a 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1,10 +1,10 @@ +use std::ffi::c_void; use std::ffi::OsStr; use std::io; use std::mem; use std::pin::Pin; use std::ptr; use std::task::{Context, Poll}; -use std::ffi::c_void; use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf}; use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; From eb863cbf70611b5b996050cc6cc56d0714f5ae5a Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 22 May 2021 05:13:41 +0200 Subject: [PATCH 44/58] create_with_security_attributes to *_raw and improved docs --- tokio/src/net/windows/named_pipe.rs | 38 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index ff8e8b18f3a..991e21d3c36 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -844,15 +844,16 @@ impl NamedPipeOptions { /// # Ok(()) } /// ``` pub fn create(&self, addr: impl AsRef) -> io::Result { - // Safety: We're calling create_with_security_attributes w/ a null + // Safety: We're calling create_with_security_attributes_raw w/ a null // pointer which disables it. - unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } + unsafe { self.create_with_security_attributes_raw(addr, ptr::null_mut()) } } /// Create the named pipe identified by `addr` for use as a server. /// - /// This is the same as [`create`][NamedPipeOptions::create] except that it - /// supports providing security attributes. + /// This is the same as [`create`] except that it supports providing the raw + /// pointer to a structure of [`SECURITY_ATTRIBUTES`] which will be passed + /// as the `lpSecurityAttributes` argument to [`CreateFile`]. /// /// # Errors /// @@ -864,11 +865,14 @@ impl NamedPipeOptions { /// /// # Safety /// - /// The caller must ensure that `attrs` points to an initialized instance of - /// a [`SECURITY_ATTRIBUTES`] structure. + /// The `attrs` argument must either be null or point at a valid instance of + /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the + /// behavior is identical to calling the [`create`] method. /// + /// [`create`]: NamedPipeOptions::create + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES - pub unsafe fn create_with_security_attributes( + pub unsafe fn create_with_security_attributes_raw( &self, addr: impl AsRef, attrs: *mut c_void, @@ -1028,25 +1032,27 @@ impl NamedPipeClientOptions { /// # Ok(()) } /// ``` pub fn create(&self, addr: impl AsRef) -> io::Result { - // Safety: We're calling create_with_security_attributes w/ a null + // Safety: We're calling create_with_security_attributes_raw w/ a null // pointer which disables it. - unsafe { self.create_with_security_attributes(addr, ptr::null_mut()) } + unsafe { self.create_with_security_attributes_raw(addr, ptr::null_mut()) } } /// Open the named pipe identified by `addr`. /// - /// This is the same as [`create`] except that it supports providing - /// security attributes. + /// This is the same as [`create`] except that it supports providing the raw + /// pointer to a structure of [`SECURITY_ATTRIBUTES`] which will be passed + /// as the `lpSecurityAttributes` argument to [`CreateFile`]. /// /// # Safety /// - /// The `attrs` argument must either be null or point at a valid instance - /// of the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, - /// the behavior is identical to calling the [`create`] method. + /// The `attrs` argument must either be null or point at a valid instance of + /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the + /// behavior is identical to calling the [`create`] method. /// - /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES /// [`create`]: NamedPipeClientOptions::create - pub unsafe fn create_with_security_attributes( + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES + pub unsafe fn create_with_security_attributes_raw( &self, addr: impl AsRef, attrs: *mut c_void, From 3f272d05705cea8a8d3cd86a49c898cb84010488 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 11:51:37 +0200 Subject: [PATCH 45/58] rename named_pipe builders and remove re-export --- examples/named-pipe-multi-client.rs | 8 +- examples/named-pipe-peek.rs | 6 +- examples/named-pipe.rs | 6 +- tokio/src/net/windows/mod.rs | 5 +- tokio/src/net/windows/named_pipe.rs | 144 ++++++++++++++-------------- tokio/tests/named_pipe.rs | 44 ++++----- 6 files changed, 104 insertions(+), 109 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index 8a9e63e40be..b90154facf4 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -4,7 +4,7 @@ use std::io; async fn windows_main() -> io::Result<()> { use std::time::Duration; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; use tokio::time; use winapi::shared::winerror; @@ -16,7 +16,7 @@ async fn windows_main() -> io::Result<()> { // // Here we also make use of `first_pipe_instance`, which will ensure // that there are no other servers up and running already. - let mut server = NamedPipeOptions::new() + let mut server = ServerOptions::new() .first_pipe_instance(true) .create(PIPE_NAME)?; @@ -34,7 +34,7 @@ async fn windows_main() -> io::Result<()> { // isn't closed (after it's done in the task) before a new one is // available. Otherwise the client might error with // `io::ErrorKind::NotFound`. - server = NamedPipeOptions::new().create(PIPE_NAME)?; + server = ServerOptions::new().create(PIPE_NAME)?; let _ = tokio::spawn(async move { let mut buf = [0u8; 4]; @@ -57,7 +57,7 @@ async fn windows_main() -> io::Result<()> { // the pipe is busy we use the specialized wait function on the // client builder. let mut client = loop { - match NamedPipeClientOptions::new().create(PIPE_NAME) { + match ClientOptions::new().create(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) => return Err(e), diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs index 1ec3c41c04f..359a563c2c3 100644 --- a/examples/named-pipe-peek.rs +++ b/examples/named-pipe-peek.rs @@ -3,13 +3,13 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; const N: usize = 1000; - let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; - let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let mut server = ServerOptions::new().create(PIPE_NAME)?; + let mut client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; let client = tokio::spawn(async move { diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index bb82ff87e69..dcdb8636ee3 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -4,11 +4,11 @@ use std::io; async fn windows_main() -> io::Result<()> { use tokio::io::AsyncWriteExt as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; - use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { // Note: we wait for a client to connect. @@ -26,7 +26,7 @@ async fn windows_main() -> io::Result<()> { // There's no need to use a connect loop here, since we know that the // server is already up - `create` was called before spawning any of the // tasks. - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); diff --git a/tokio/src/net/windows/mod.rs b/tokio/src/net/windows/mod.rs index 414945269b1..060b68e663d 100644 --- a/tokio/src/net/windows/mod.rs +++ b/tokio/src/net/windows/mod.rs @@ -1,6 +1,3 @@ //! Windows specific network types. -mod named_pipe; -pub use self::named_pipe::{ - NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeEnd, PipeMode, PipePeekInfo, -}; +pub mod named_pipe; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 991e21d3c36..d638745421e 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -1,7 +1,10 @@ +//! Tokio support for [Windows named pipes]. +//! +//! [Windows named pipes]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes + use std::ffi::c_void; use std::ffi::OsStr; use std::io; -use std::mem; use std::pin::Pin; use std::ptr; use std::task::{Context, Poll}; @@ -37,8 +40,8 @@ use self::doc::*; /// A [Windows named pipe]. /// -/// Constructed using [`NamedPipeClientOptions::create`] for clients, or -/// [`NamedPipeOptions::create`] for servers. See their corresponding +/// Constructed using [`ClientOptions::create`] for clients, or +/// [`ServerOptions::create`] for servers. See their corresponding /// documentation for examples. /// /// Connecting a client involves a few steps. First we must try to [`create`], the @@ -52,7 +55,7 @@ use self::doc::*; /// /// ```no_run /// use std::time::Duration; -/// use tokio::net::windows::NamedPipeClientOptions; +/// use tokio::net::windows::named_pipe::ClientOptions; /// use tokio::time; /// use winapi::shared::winerror; /// @@ -60,7 +63,7 @@ use self::doc::*; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = loop { -/// match NamedPipeClientOptions::new().create(PIPE_NAME) { +/// match ClientOptions::new().create(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), @@ -81,7 +84,7 @@ use self::doc::*; /// /// ```no_run /// use std::io; -/// use tokio::net::windows::NamedPipeOptions; +/// use tokio::net::windows::named_pipe::ServerOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-server"; /// @@ -92,7 +95,7 @@ use self::doc::*; /// // /// // Here we also make use of `first_pipe_instance`, which will ensure that /// // there are no other servers up and running already. -/// let mut server = NamedPipeOptions::new() +/// let mut server = ServerOptions::new() /// .first_pipe_instance(true) /// .create(PIPE_NAME)?; /// @@ -107,7 +110,7 @@ use self::doc::*; /// // isn't closed (after it's done in the task) before a new one is /// // available. Otherwise the client might error with /// // `io::ErrorKind::NotFound`. -/// server = NamedPipeOptions::new().create(PIPE_NAME)?; +/// server = ServerOptions::new().create(PIPE_NAME)?; /// /// let client = tokio::spawn(async move { /// /* use the connected client */ @@ -123,7 +126,7 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// [`create`]: NamedPipeClientOptions::create +/// [`create`]: ClientOptions::create /// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] @@ -167,12 +170,12 @@ impl NamedPipe { /// [`ConnectNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe /// /// ```no_run - /// use tokio::net::windows::NamedPipeOptions; + /// use tokio::net::windows::named_pipe::ServerOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let pipe = NamedPipeOptions::new().create(PIPE_NAME)?; + /// let pipe = ServerOptions::new().create(PIPE_NAME)?; /// /// // Wait for a client to connect. /// pipe.connect().await?; @@ -199,15 +202,15 @@ impl NamedPipe { /// /// ``` /// use tokio::io::AsyncWriteExt as _; - /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-disconnect"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; + /// let server = ServerOptions::new().create(PIPE_NAME)?; /// - /// let mut client = NamedPipeClientOptions::new() + /// let mut client = ClientOptions::new() /// .create(PIPE_NAME)?; /// /// // Wait for a client to become connected. @@ -229,17 +232,17 @@ impl NamedPipe { /// Retrieves information about the current named pipe. /// /// ``` - /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions, PipeMode, PipeEnd}; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions, PipeMode, PipeEnd}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-info"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = NamedPipeOptions::new() + /// let server = ServerOptions::new() /// .pipe_mode(PipeMode::Message) /// .max_instances(5) /// .create(PIPE_NAME)?; /// - /// let client = NamedPipeClientOptions::new() + /// let client = ClientOptions::new() /// .create(PIPE_NAME)?; /// /// let server_info = server.info()?; @@ -256,28 +259,23 @@ impl NamedPipe { /// ``` pub fn info(&self) -> io::Result { unsafe { - let mut flags = mem::MaybeUninit::uninit(); - let mut out_buffer_size = mem::MaybeUninit::uninit(); - let mut in_buffer_size = mem::MaybeUninit::uninit(); - let mut max_instances = mem::MaybeUninit::uninit(); + let mut flags = 0; + let mut out_buffer_size = 0; + let mut in_buffer_size = 0; + let mut max_instances = 0; let result = namedpipeapi::GetNamedPipeInfo( self.io.as_raw_handle(), - flags.as_mut_ptr(), - out_buffer_size.as_mut_ptr(), - in_buffer_size.as_mut_ptr(), - max_instances.as_mut_ptr(), + &mut flags, + &mut out_buffer_size, + &mut in_buffer_size, + &mut max_instances, ); if result == FALSE { return Err(io::Error::last_os_error()); } - let flags = flags.assume_init(); - let out_buffer_size = out_buffer_size.assume_init(); - let in_buffer_size = in_buffer_size.assume_init(); - let max_instances = max_instances.assume_init(); - let mut end = PipeEnd::Client; let mut mode = PipeMode::Byte; @@ -332,14 +330,14 @@ impl NamedPipe { /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// use tokio::net::windows::named_pipe::{ServerOptions, ClientOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; /// const N: usize = 100; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; - /// let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + /// let mut server = ServerOptions::new().create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().create(PIPE_NAME)?; /// server.connect().await?; /// /// let client = tokio::spawn(async move { @@ -498,9 +496,9 @@ macro_rules! bool_flag { /// options. This is required to use for named pipe servers who wants to modify /// pipe-related options. /// -/// See [`NamedPipeOptions::create`]. +/// See [`ServerOptions::create`]. #[derive(Debug, Clone)] -pub struct NamedPipeOptions { +pub struct ServerOptions { open_mode: DWORD, pipe_mode: DWORD, max_instances: DWORD, @@ -509,21 +507,21 @@ pub struct NamedPipeOptions { default_timeout: DWORD, } -impl NamedPipeOptions { +impl ServerOptions { /// Creates a new named pipe builder with the default settings. /// /// ``` - /// use tokio::net::windows::NamedPipeOptions; + /// use tokio::net::windows::named_pipe::ServerOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-new"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = NamedPipeOptions::new() + /// let server = ServerOptions::new() /// .create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn new() -> NamedPipeOptions { - NamedPipeOptions { + pub fn new() -> ServerOptions { + ServerOptions { open_mode: winbase::PIPE_ACCESS_DUPLEX | winbase::FILE_FLAG_OVERLAPPED, pipe_mode: winbase::PIPE_TYPE_BYTE | winbase::PIPE_REJECT_REMOTE_CLIENTS, max_instances: winbase::PIPE_UNLIMITED_INSTANCES, @@ -561,7 +559,7 @@ impl NamedPipeOptions { /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound"; /// @@ -569,11 +567,11 @@ impl NamedPipeOptions { /// // Server side prevents connecting by denying inbound access, client errors /// // when attempting to create the connection. /// { - /// let _server = NamedPipeOptions::new() + /// let _server = ServerOptions::new() /// .access_inbound(false) /// .create(PIPE_NAME)?; /// - /// let e = NamedPipeClientOptions::new() + /// let e = ClientOptions::new() /// .create(PIPE_NAME) /// .unwrap_err(); /// @@ -581,7 +579,7 @@ impl NamedPipeOptions { /// /// // Disabling writing allows a client to connect, but leads to runtime /// // error if a write is attempted. - /// let mut client = NamedPipeClientOptions::new() + /// let mut client = ClientOptions::new() /// .write(false) /// .create(PIPE_NAME)?; /// @@ -591,11 +589,11 @@ impl NamedPipeOptions { /// /// // A functional, unidirectional server-to-client only communication. /// { - /// let mut server = NamedPipeOptions::new() + /// let mut server = ServerOptions::new() /// .access_inbound(false) /// .create(PIPE_NAME)?; /// - /// let mut client = NamedPipeClientOptions::new() + /// let mut client = ClientOptions::new() /// .write(false) /// .create(PIPE_NAME)?; /// @@ -627,7 +625,7 @@ impl NamedPipeOptions { /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - /// use tokio::net::windows::{NamedPipeClientOptions, NamedPipeOptions}; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; /// @@ -635,11 +633,11 @@ impl NamedPipeOptions { /// // Server side prevents connecting by denying outbound access, client errors /// // when attempting to create the connection. /// { - /// let _server = NamedPipeOptions::new() + /// let _server = ServerOptions::new() /// .access_outbound(false) /// .create(PIPE_NAME)?; /// - /// let e = NamedPipeClientOptions::new() + /// let e = ClientOptions::new() /// .create(PIPE_NAME) /// .unwrap_err(); /// @@ -647,7 +645,7 @@ impl NamedPipeOptions { /// /// // Disabling reading allows a client to connect, but leads to runtime /// // error if a read is attempted. - /// let mut client = NamedPipeClientOptions::new().read(false).create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().read(false).create(PIPE_NAME)?; /// /// let mut buf = [0u8; 4]; /// let e = client.read(&mut buf).await.unwrap_err(); @@ -656,8 +654,8 @@ impl NamedPipeOptions { /// /// // A functional, unidirectional client-to-server only communication. /// { - /// let mut server = NamedPipeOptions::new().access_outbound(false).create(PIPE_NAME)?; - /// let mut client = NamedPipeClientOptions::new().read(false).create(PIPE_NAME)?; + /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().read(false).create(PIPE_NAME)?; /// /// // TODO: Explain why this test doesn't work without calling connect /// // first. @@ -697,12 +695,12 @@ impl NamedPipeOptions { /// /// ``` /// use std::io; - /// use tokio::net::windows::NamedPipeOptions; + /// use tokio::net::windows::named_pipe::ServerOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-first-instance"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// let mut builder = NamedPipeOptions::new(); + /// let mut builder = ServerOptions::new(); /// builder.first_pipe_instance(true); /// /// let server = builder.create(PIPE_NAME)?; @@ -754,20 +752,20 @@ impl NamedPipeOptions { /// /// ``` /// use std::io; - /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// use tokio::net::windows::named_pipe::{ServerOptions, ClientOptions}; /// use winapi::shared::winerror; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-max-instances"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// let mut server = NamedPipeOptions::new(); + /// let mut server = ServerOptions::new(); /// server.max_instances(2); /// /// let s1 = server.create(PIPE_NAME)?; - /// let c1 = NamedPipeClientOptions::new().create(PIPE_NAME); + /// let c1 = ClientOptions::new().create(PIPE_NAME); /// /// let s2 = server.create(PIPE_NAME)?; - /// let c2 = NamedPipeClientOptions::new().create(PIPE_NAME); + /// let c2 = ClientOptions::new().create(PIPE_NAME); /// /// // Too many servers! /// let e = server.create(PIPE_NAME).unwrap_err(); @@ -785,10 +783,10 @@ impl NamedPipeOptions { /// you do not wish to set an instance limit, leave it unspecified. /// /// ```should_panic - /// use tokio::net::windows::NamedPipeOptions; + /// use tokio::net::windows::named_pipe::ServerOptions; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let builder = NamedPipeOptions::new().max_instances(255); + /// let builder = ServerOptions::new().max_instances(255); /// # Ok(()) } /// ``` pub fn max_instances(&mut self, instances: usize) -> &mut Self { @@ -835,12 +833,12 @@ impl NamedPipeOptions { /// # Examples /// /// ``` - /// use tokio::net::windows::NamedPipeOptions; + /// use tokio::net::windows::named_pipe::ServerOptions; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-create"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; + /// let server = ServerOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` pub fn create(&self, addr: impl AsRef) -> io::Result { @@ -869,7 +867,7 @@ impl NamedPipeOptions { /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the /// behavior is identical to calling the [`create`] method. /// - /// [`create`]: NamedPipeOptions::create + /// [`create`]: ServerOptions::create /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES pub unsafe fn create_with_security_attributes_raw( @@ -904,25 +902,25 @@ impl NamedPipeOptions { /// A builder suitable for building and interacting with named pipes from the /// client side. /// -/// See [`NamedPipeClientOptions::create`]. +/// See [`ClientOptions::create`]. #[derive(Debug, Clone)] -pub struct NamedPipeClientOptions { +pub struct ClientOptions { desired_access: DWORD, security_qos_flags: DWORD, } -impl NamedPipeClientOptions { +impl ClientOptions { /// Creates a new named pipe builder with the default settings. /// /// ``` - /// use tokio::net::windows::{NamedPipeOptions, NamedPipeClientOptions}; + /// use tokio::net::windows::named_pipe::{ServerOptions, ClientOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-new"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// // Server must be created in order for the client creation to succeed. - /// let server = NamedPipeOptions::new().create(PIPE_NAME)?; - /// let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + /// let server = ServerOptions::new().create(PIPE_NAME)?; + /// let client = ClientOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` pub fn new() -> Self { @@ -1011,7 +1009,7 @@ impl NamedPipeClientOptions { /// /// ```no_run /// use std::time::Duration; - /// use tokio::net::windows::NamedPipeClientOptions; + /// use tokio::net::windows::named_pipe::ClientOptions; /// use tokio::time; /// use winapi::shared::winerror; /// @@ -1019,7 +1017,7 @@ impl NamedPipeClientOptions { /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = loop { - /// match NamedPipeClientOptions::new().create(PIPE_NAME) { + /// match ClientOptions::new().create(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), @@ -1049,7 +1047,7 @@ impl NamedPipeClientOptions { /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the /// behavior is identical to calling the [`create`] method. /// - /// [`create`]: NamedPipeClientOptions::create + /// [`create`]: ClientOptions::create /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES pub unsafe fn create_with_security_attributes_raw( @@ -1090,7 +1088,7 @@ impl NamedPipeClientOptions { /// The pipe mode of a [`NamedPipe`]. /// -/// Set through [`NamedPipeOptions::pipe_mode`]. +/// Set through [`ServerOptions::pipe_mode`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeMode { diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 99567f32d52..93ecab5582d 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -6,7 +6,7 @@ use std::mem; use std::os::windows::io::AsRawHandle; use std::time::Duration; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _, ReadBuf}; -use tokio::net::windows::{NamedPipe, NamedPipeClientOptions, NamedPipeOptions, PipeMode}; +use tokio::net::windows::named_pipe::{ClientOptions, NamedPipe, PipeMode, ServerOptions}; use tokio::time; use winapi::shared::winerror; @@ -15,8 +15,8 @@ async fn test_named_pipe_peek_consumed() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; const N: usize = 1000; - let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; - let mut client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let mut server = ServerOptions::new().create(PIPE_NAME)?; + let mut client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; let client = tokio::spawn(async move { @@ -122,8 +122,8 @@ async fn test_named_pipe_peek() -> io::Result<()> { { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-small"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; peek_ping_pong(1, client, server).await?; @@ -132,8 +132,8 @@ async fn test_named_pipe_peek() -> io::Result<()> { { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; peek_ping_pong(1, server, client).await?; @@ -142,8 +142,8 @@ async fn test_named_pipe_peek() -> io::Result<()> { { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-big"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; peek_ping_pong(100, client, server).await?; @@ -152,8 +152,8 @@ async fn test_named_pipe_peek() -> io::Result<()> { { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; peek_ping_pong(100, server, client).await?; @@ -166,11 +166,11 @@ async fn test_named_pipe_peek() -> io::Result<()> { async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; - let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; + let mut server = ServerOptions::new().create(PIPE_NAME)?; assert_eq!(num_instances("test-named-pipe-client-drop")?, 1); - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; drop(client); @@ -189,8 +189,8 @@ async fn test_named_pipe_client_drop() -> io::Result<()> { async fn test_named_pipe_client_connect() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-connect"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; let e = client.connect().await.unwrap_err(); @@ -207,7 +207,7 @@ async fn test_named_pipe_single_client() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-single-client"; - let server = NamedPipeOptions::new().create(PIPE_NAME)?; + let server = ServerOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { // Note: we wait for a client to connect. @@ -222,7 +222,7 @@ async fn test_named_pipe_single_client() -> io::Result<()> { }); let client = tokio::spawn(async move { - let client = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().create(PIPE_NAME)?; let mut client = BufReader::new(client); @@ -250,7 +250,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // The first server needs to be constructed early so that clients can // be correctly connected. Otherwise calling .wait will cause the client to // error. - let mut server = NamedPipeOptions::new().create(PIPE_NAME)?; + let mut server = ServerOptions::new().create(PIPE_NAME)?; let server = tokio::spawn(async move { for _ in 0..N { @@ -263,7 +263,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // isn't closed (after it's done in the task) before a new one is // available. Otherwise the client might error with // `io::ErrorKind::NotFound`. - server = NamedPipeOptions::new().create(PIPE_NAME)?; + server = ServerOptions::new().create(PIPE_NAME)?; let _ = tokio::spawn(async move { let mut buf = String::new(); @@ -287,7 +287,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // pipe is busy we use the specialized wait function on the client // builder. let client = loop { - match NamedPipeClientOptions::new().create(PIPE_NAME) { + match ClientOptions::new().create(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) if e.kind() == io::ErrorKind::NotFound => (), @@ -322,11 +322,11 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { async fn test_named_pipe_mode_message() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-mode-message"; - let server = NamedPipeOptions::new() + let server = ServerOptions::new() .pipe_mode(PipeMode::Message) .create(PIPE_NAME)?; - let _ = NamedPipeClientOptions::new().create(PIPE_NAME)?; + let _ = ClientOptions::new().create(PIPE_NAME)?; server.connect().await?; Ok(()) } From f5c822cb3da2793bbc954c821dffdc5b0ae6bffc Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 11:57:49 +0200 Subject: [PATCH 46/58] rename ClientOptions::create to ClientOptions::open --- examples/named-pipe-multi-client.rs | 2 +- examples/named-pipe-peek.rs | 2 +- examples/named-pipe.rs | 4 +-- tokio/src/net/windows/named_pipe.rs | 50 +++++++++++++++-------------- tokio/tests/named_pipe.rs | 20 ++++++------ 5 files changed, 40 insertions(+), 38 deletions(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index b90154facf4..2b45e062230 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -57,7 +57,7 @@ async fn windows_main() -> io::Result<()> { // the pipe is busy we use the specialized wait function on the // client builder. let mut client = loop { - match ClientOptions::new().create(PIPE_NAME) { + match ClientOptions::new().open(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) => return Err(e), diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs index 359a563c2c3..b89f0bc9a7d 100644 --- a/examples/named-pipe-peek.rs +++ b/examples/named-pipe-peek.rs @@ -9,7 +9,7 @@ async fn windows_main() -> io::Result<()> { const N: usize = 1000; let mut server = ServerOptions::new().create(PIPE_NAME)?; - let mut client = ClientOptions::new().create(PIPE_NAME)?; + let mut client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; let client = tokio::spawn(async move { diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index dcdb8636ee3..76b88846c46 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -24,9 +24,9 @@ async fn windows_main() -> io::Result<()> { let client = tokio::spawn(async move { // There's no need to use a connect loop here, since we know that the - // server is already up - `create` was called before spawning any of the + // server is already up - `open` was called before spawning any of the // tasks. - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; let mut client = BufReader::new(client); diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index d638745421e..e30a5c811ca 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -63,7 +63,7 @@ use self::doc::*; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = loop { -/// match ClientOptions::new().create(PIPE_NAME) { +/// match ClientOptions::new().open(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), @@ -208,10 +208,11 @@ impl NamedPipe { /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-disconnect"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = ServerOptions::new().create(PIPE_NAME)?; + /// let server = ServerOptions::new() + /// .create(PIPE_NAME)?; /// /// let mut client = ClientOptions::new() - /// .create(PIPE_NAME)?; + /// .open(PIPE_NAME)?; /// /// // Wait for a client to become connected. /// server.connect().await?; @@ -243,7 +244,7 @@ impl NamedPipe { /// .create(PIPE_NAME)?; /// /// let client = ClientOptions::new() - /// .create(PIPE_NAME)?; + /// .open(PIPE_NAME)?; /// /// let server_info = server.info()?; /// let client_info = client.info()?; @@ -337,7 +338,7 @@ impl NamedPipe { /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let mut server = ServerOptions::new().create(PIPE_NAME)?; - /// let mut client = ClientOptions::new().create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().open(PIPE_NAME)?; /// server.connect().await?; /// /// let client = tokio::spawn(async move { @@ -516,8 +517,7 @@ impl ServerOptions { /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-new"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = ServerOptions::new() - /// .create(PIPE_NAME)?; + /// let server = ServerOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` pub fn new() -> ServerOptions { @@ -572,7 +572,7 @@ impl ServerOptions { /// .create(PIPE_NAME)?; /// /// let e = ClientOptions::new() - /// .create(PIPE_NAME) + /// .open(PIPE_NAME) /// .unwrap_err(); /// /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); @@ -581,7 +581,7 @@ impl ServerOptions { /// // error if a write is attempted. /// let mut client = ClientOptions::new() /// .write(false) - /// .create(PIPE_NAME)?; + /// .open(PIPE_NAME)?; /// /// let e = client.write(b"ping").await.unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); @@ -595,7 +595,7 @@ impl ServerOptions { /// /// let mut client = ClientOptions::new() /// .write(false) - /// .create(PIPE_NAME)?; + /// .open(PIPE_NAME)?; /// /// let write = server.write_all(b"ping"); /// @@ -638,14 +638,16 @@ impl ServerOptions { /// .create(PIPE_NAME)?; /// /// let e = ClientOptions::new() - /// .create(PIPE_NAME) + /// .open(PIPE_NAME) /// .unwrap_err(); /// /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); /// /// // Disabling reading allows a client to connect, but leads to runtime /// // error if a read is attempted. - /// let mut client = ClientOptions::new().read(false).create(PIPE_NAME)?; + /// let mut client = ClientOptions::new() + /// .read(false) + /// .open(PIPE_NAME)?; /// /// let mut buf = [0u8; 4]; /// let e = client.read(&mut buf).await.unwrap_err(); @@ -655,7 +657,7 @@ impl ServerOptions { /// // A functional, unidirectional client-to-server only communication. /// { /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; - /// let mut client = ClientOptions::new().read(false).create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().read(false).open(PIPE_NAME)?; /// /// // TODO: Explain why this test doesn't work without calling connect /// // first. @@ -762,10 +764,10 @@ impl ServerOptions { /// server.max_instances(2); /// /// let s1 = server.create(PIPE_NAME)?; - /// let c1 = ClientOptions::new().create(PIPE_NAME); + /// let c1 = ClientOptions::new().open(PIPE_NAME); /// /// let s2 = server.create(PIPE_NAME)?; - /// let c2 = ClientOptions::new().create(PIPE_NAME); + /// let c2 = ClientOptions::new().open(PIPE_NAME); /// /// // Too many servers! /// let e = server.create(PIPE_NAME).unwrap_err(); @@ -920,7 +922,7 @@ impl ClientOptions { /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// // Server must be created in order for the client creation to succeed. /// let server = ServerOptions::new().create(PIPE_NAME)?; - /// let client = ClientOptions::new().create(PIPE_NAME)?; + /// let client = ClientOptions::new().open(PIPE_NAME)?; /// # Ok(()) } /// ``` pub fn new() -> Self { @@ -1017,7 +1019,7 @@ impl ClientOptions { /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = loop { - /// match ClientOptions::new().create(PIPE_NAME) { + /// match ClientOptions::new().open(PIPE_NAME) { /// Ok(client) => break client, /// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), /// Err(e) => return Err(e), @@ -1029,15 +1031,15 @@ impl ClientOptions { /// // use the connected client. /// # Ok(()) } /// ``` - pub fn create(&self, addr: impl AsRef) -> io::Result { - // Safety: We're calling create_with_security_attributes_raw w/ a null + pub fn open(&self, addr: impl AsRef) -> io::Result { + // Safety: We're calling open_with_security_attributes_raw w/ a null // pointer which disables it. - unsafe { self.create_with_security_attributes_raw(addr, ptr::null_mut()) } + unsafe { self.open_with_security_attributes_raw(addr, ptr::null_mut()) } } /// Open the named pipe identified by `addr`. /// - /// This is the same as [`create`] except that it supports providing the raw + /// This is the same as [`open`] except that it supports providing the raw /// pointer to a structure of [`SECURITY_ATTRIBUTES`] which will be passed /// as the `lpSecurityAttributes` argument to [`CreateFile`]. /// @@ -1045,12 +1047,12 @@ impl ClientOptions { /// /// The `attrs` argument must either be null or point at a valid instance of /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the - /// behavior is identical to calling the [`create`] method. + /// behavior is identical to calling the [`open`] method. /// - /// [`create`]: ClientOptions::create + /// [`open`]: ClientOptions::create /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES - pub unsafe fn create_with_security_attributes_raw( + pub unsafe fn open_with_security_attributes_raw( &self, addr: impl AsRef, attrs: *mut c_void, diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 93ecab5582d..8abaa3ea1cd 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -16,7 +16,7 @@ async fn test_named_pipe_peek_consumed() -> io::Result<()> { const N: usize = 1000; let mut server = ServerOptions::new().create(PIPE_NAME)?; - let mut client = ClientOptions::new().create(PIPE_NAME)?; + let mut client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; let client = tokio::spawn(async move { @@ -123,7 +123,7 @@ async fn test_named_pipe_peek() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-small"; let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; peek_ping_pong(1, client, server).await?; @@ -133,7 +133,7 @@ async fn test_named_pipe_peek() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; peek_ping_pong(1, server, client).await?; @@ -143,7 +143,7 @@ async fn test_named_pipe_peek() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-big"; let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; peek_ping_pong(100, client, server).await?; @@ -153,7 +153,7 @@ async fn test_named_pipe_peek() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; peek_ping_pong(100, server, client).await?; @@ -170,7 +170,7 @@ async fn test_named_pipe_client_drop() -> io::Result<()> { assert_eq!(num_instances("test-named-pipe-client-drop")?, 1); - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; drop(client); @@ -190,7 +190,7 @@ async fn test_named_pipe_client_connect() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-connect"; let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; let e = client.connect().await.unwrap_err(); @@ -222,7 +222,7 @@ async fn test_named_pipe_single_client() -> io::Result<()> { }); let client = tokio::spawn(async move { - let client = ClientOptions::new().create(PIPE_NAME)?; + let client = ClientOptions::new().open(PIPE_NAME)?; let mut client = BufReader::new(client); @@ -287,7 +287,7 @@ async fn test_named_pipe_multi_client() -> io::Result<()> { // pipe is busy we use the specialized wait function on the client // builder. let client = loop { - match ClientOptions::new().create(PIPE_NAME) { + match ClientOptions::new().open(PIPE_NAME) { Ok(client) => break client, Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), Err(e) if e.kind() == io::ErrorKind::NotFound => (), @@ -326,7 +326,7 @@ async fn test_named_pipe_mode_message() -> io::Result<()> { .pipe_mode(PipeMode::Message) .create(PIPE_NAME)?; - let _ = ClientOptions::new().create(PIPE_NAME)?; + let _ = ClientOptions::new().open(PIPE_NAME)?; server.connect().await?; Ok(()) } From c8761a3118279fa997d1441b0635d2c1d8af1078 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 12:03:21 +0200 Subject: [PATCH 47/58] doc nits --- tokio/src/net/windows/named_pipe.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index e30a5c811ca..6fbdffe0a14 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -40,12 +40,12 @@ use self::doc::*; /// A [Windows named pipe]. /// -/// Constructed using [`ClientOptions::create`] for clients, or -/// [`ServerOptions::create`] for servers. See their corresponding -/// documentation for examples. +/// Constructed using [`ClientOptions::open`] for clients, or +/// [`ServerOptions::create`] for servers. See their corresponding documentation +/// for examples. /// -/// Connecting a client involves a few steps. First we must try to [`create`], the -/// error typically indicates one of two things: +/// Connecting a client involves a few steps. First we must try to +/// [`ClientOptions::open`], the error typically indicates one of two things: /// /// * [`std::io::ErrorKind::NotFound`] - There is no server available. /// * [`ERROR_PIPE_BUSY`] - There is a server available, but it is busy. Sleep for @@ -77,10 +77,10 @@ use self::doc::*; /// ``` /// /// A client will error with [`std::io::ErrorKind::NotFound`] for most creation -/// oriented operations like [`create`] unless at least once server instance is up -/// and running at all time. This means that the typical listen loop for a -/// server is a bit involved, because we have to ensure that we never drop a -/// server accidentally while a client might want to connect. +/// oriented operations like [`ClientOptions::open`] unless at least once server +/// instance is up and running at all time. This means that the typical listen +/// loop for a server is a bit involved, because we have to ensure that we never +/// drop a server accidentally while a client might want to connect. /// /// ```no_run /// use std::io; @@ -126,7 +126,6 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// [`create`]: ClientOptions::create /// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] @@ -904,7 +903,7 @@ impl ServerOptions { /// A builder suitable for building and interacting with named pipes from the /// client side. /// -/// See [`ClientOptions::create`]. +/// See [`ClientOptions::open`]. #[derive(Debug, Clone)] pub struct ClientOptions { desired_access: DWORD, @@ -1049,7 +1048,7 @@ impl ClientOptions { /// the [`SECURITY_ATTRIBUTES`] structure. If the argument is null, the /// behavior is identical to calling the [`open`] method. /// - /// [`open`]: ClientOptions::create + /// [`open`]: ClientOptions::open /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew /// [`SECURITY_ATTRIBUTES`]: crate::winapi::um::minwinbase::SECURITY_ATTRIBUTES pub unsafe fn open_with_security_attributes_raw( From 93b7dd07757b705cd028faa5377ad959624720cd Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 13:56:00 +0200 Subject: [PATCH 48/58] enabled I/O --- tokio/src/net/windows/named_pipe.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 6fbdffe0a14..c00af571a28 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -147,11 +147,11 @@ impl NamedPipe { /// /// # Errors /// - /// This errors if called outside of a [Tokio Runtime] which doesn't have - /// [I/O enabled]. + /// This errors if called outside of a [Tokio Runtime], or in a runtime that + /// has not [enabled I/O], or if any OS-specific I/O errors occur. /// /// [Tokio Runtime]: crate::runtime::Runtime - /// [I/O enabled]: crate::runtime::Builder::enable_io + /// [enabled I/O]: crate::runtime::Builder::enable_io pub unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); @@ -825,11 +825,11 @@ impl ServerOptions { /// /// # Errors /// - /// This errors if called outside of a [Tokio Runtime] which doesn't have - /// [I/O enabled] or if any OS-specific I/O errors occur. + /// This errors if called outside of a [Tokio Runtime], or in a runtime that + /// has not [enabled I/O], or if any OS-specific I/O errors occur. /// /// [Tokio Runtime]: crate::runtime::Runtime - /// [I/O enabled]: crate::runtime::Builder::enable_io + /// [enabled I/O]: crate::runtime::Builder::enable_io /// /// # Examples /// @@ -856,11 +856,11 @@ impl ServerOptions { /// /// # Errors /// - /// This errors if called outside of a [Tokio Runtime] which doesn't have - /// [I/O enabled] or if any OS-specific I/O errors occur. + /// This errors if called outside of a [Tokio Runtime], or in a runtime that + /// has not [enabled I/O], or if any OS-specific I/O errors occur. /// /// [Tokio Runtime]: crate::runtime::Runtime - /// [I/O enabled]: crate::runtime::Builder::enable_io + /// [enabled I/O]: crate::runtime::Builder::enable_io /// /// # Safety /// @@ -988,8 +988,8 @@ impl ClientOptions { /// /// # Errors /// - /// This errors if called outside of a [Tokio Runtime] which doesn't have - /// [I/O enabled] or if any OS-specific I/O errors occur. + /// This errors if called outside of a [Tokio Runtime], or in a runtime that + /// has not [enabled I/O], or if any OS-specific I/O errors occur. /// /// There are a few errors you need to take into account when creating a /// named pipe on the client side: @@ -1001,9 +1001,9 @@ impl ClientOptions { /// examples for how to check for this error. /// /// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY - /// [I/O enabled]: crate::runtime::Builder::enable_io - /// [Tokio Runtime]: crate::runtime::Runtime /// [`winapi`]: crate::winapi + /// [enabled I/O]: crate::runtime::Builder::enable_io + /// [Tokio Runtime]: crate::runtime::Runtime /// /// A connect loop that waits until a socket becomes available looks like /// this: From 7d1d2772c35de9fddd69b472ba1b9ecb04072440 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:00:50 +0200 Subject: [PATCH 49/58] improve docs and lie less --- tokio/src/net/windows/named_pipe.rs | 52 +++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index c00af571a28..e4f1dadf8cb 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -683,14 +683,44 @@ impl ServerOptions { self } - /// If you attempt to create multiple instances of a pipe with this flag, - /// creation of the first instance succeeds, but creation of the next - /// instance fails with [`ERROR_ACCESS_DENIED`]. + /// If you attempt to create multiple instances of a pipe with this flag + /// set, creation of the first server instance succeeds, but creation of any + /// subsequent instances will fail with + /// [`std::io::ErrorKind::PermissionDenied`]. + /// + /// This option is intended to be used with servers that want to ensure that + /// they are the only process listening for clients on a given named pipe. + /// This is accomplished by enabling it for the first server instance + /// created in a process. /// /// This corresponds to setting [`FILE_FLAG_FIRST_PIPE_INSTANCE`]. /// - /// [`ERROR_ACCESS_DENIED`]: crate::winapi::shared::winerror::ERROR_ACCESS_DENIED - /// [`FILE_FLAG_FIRST_PIPE_INSTANCE`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance + /// # Errors + /// + /// If this option is set and more than one instance of the server for a + /// given named pipe exists, calling [`create`] will fail with + /// [`std::io::ErrorKind::PermissionDenied`]. + /// + /// ``` + /// use std::io; + /// use tokio::net::windows::named_pipe::ServerOptions; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-first-instance-error"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let server1 = ServerOptions::new() + /// .first_pipe_instance(true) + /// .create(PIPE_NAME)?; + /// + /// // Second server errs, since it's not the first instance. + /// let e = ServerOptions::new() + /// .first_pipe_instance(true) + /// .create(PIPE_NAME) + /// .unwrap_err(); + /// + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// # Ok(()) } + /// ``` /// /// # Examples /// @@ -713,6 +743,9 @@ impl ServerOptions { /// let _server2 = builder.create(PIPE_NAME)?; /// # Ok(()) } /// ``` + /// + /// [`create`]: ServerOptions::create + /// [`FILE_FLAG_FIRST_PIPE_INSTANCE`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance pub fn first_pipe_instance(&mut self, first: bool) -> &mut Self { bool_flag!( self.open_mode, @@ -722,8 +755,8 @@ impl ServerOptions { self } - /// Indicates whether this server can accept remote clients or not. This is - /// enabled by default. + /// Indicates whether this server can accept remote clients or not. Remote + /// clients are disabled by default. /// /// This corresponds to setting [`PIPE_REJECT_REMOTE_CLIENTS`]. /// @@ -738,9 +771,6 @@ impl ServerOptions { /// be specified for other instances of the pipe. Acceptable values are in /// the range 1 through 254. The default value is unlimited. /// - /// This value is only respected by the first instance that creates the pipe - /// and is ignored otherwise. - /// /// This corresponds to specifying [`nMaxInstances`]. /// /// [`nMaxInstances`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea @@ -749,7 +779,7 @@ impl ServerOptions { /// /// The same numbers of `max_instances` have to be used by all servers. Any /// additional servers trying to be built which uses a mismatching value - /// will error. + /// might error. /// /// ``` /// use std::io; From 93b8ff1e87b6ce19792eb7723d0f60e22937d152 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:23:16 +0200 Subject: [PATCH 50/58] no aliasing of traits to _ --- tokio/src/net/windows/named_pipe.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index e4f1dadf8cb..d4a6ff294da 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -200,7 +200,7 @@ impl NamedPipe { /// process. /// /// ``` - /// use tokio::io::AsyncWriteExt as _; + /// use tokio::io::AsyncWriteExt; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// use winapi::shared::winerror; /// @@ -329,7 +329,7 @@ impl NamedPipe { /// /// ``` /// use std::io; - /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use tokio::net::windows::named_pipe::{ServerOptions, ClientOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; @@ -378,7 +378,7 @@ impl NamedPipe { /// # Ok(()) } /// ``` pub fn peek(&mut self, mut buf: Option<&mut ReadBuf<'_>>) -> io::Result { - use std::convert::TryFrom as _; + use std::convert::TryFrom; unsafe { let mut n = 0; @@ -557,7 +557,7 @@ impl ServerOptions { /// /// ``` /// use std::io; - /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound"; @@ -623,7 +623,7 @@ impl ServerOptions { /// /// ``` /// use std::io; - /// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; From ebe98ddc5dba1681f953001d79418213383fff5d Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:27:10 +0200 Subject: [PATCH 51/58] Update tokio/src/net/windows/named_pipe.rs Co-authored-by: Alice Ryhl --- tokio/src/net/windows/named_pipe.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index d4a6ff294da..20164ce3c27 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -76,9 +76,9 @@ use self::doc::*; /// # Ok(()) } /// ``` /// -/// A client will error with [`std::io::ErrorKind::NotFound`] for most creation -/// oriented operations like [`ClientOptions::open`] unless at least once server -/// instance is up and running at all time. This means that the typical listen +/// To avoid having clients fail with [`std::io::ErrorKind::NotFound`] when +/// connecting to a named pipe server, you must ensure that at least one server +/// instance is up and running at all times. This means that the typical listen /// loop for a server is a bit involved, because we have to ensure that we never /// drop a server accidentally while a client might want to connect. /// From 47c951e17e2b987b8264d40c1a1ea86db17fcdf7 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:34:57 +0200 Subject: [PATCH 52/58] remove comment and just connect --- tokio/src/net/windows/named_pipe.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 20164ce3c27..9e52e28be53 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -632,7 +632,7 @@ impl ServerOptions { /// // Server side prevents connecting by denying outbound access, client errors /// // when attempting to create the connection. /// { - /// let _server = ServerOptions::new() + /// let server = ServerOptions::new() /// .access_outbound(false) /// .create(PIPE_NAME)?; /// @@ -648,6 +648,8 @@ impl ServerOptions { /// .read(false) /// .open(PIPE_NAME)?; /// + /// server.connect().await?; + /// /// let mut buf = [0u8; 4]; /// let e = client.read(&mut buf).await.unwrap_err(); /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); @@ -657,11 +659,6 @@ impl ServerOptions { /// { /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; /// let mut client = ClientOptions::new().read(false).open(PIPE_NAME)?; - /// - /// // TODO: Explain why this test doesn't work without calling connect - /// // first. - /// // - /// // Because I have no idea -- udoprog /// server.connect().await?; /// /// let write = client.write_all(b"ping"); From fb9ed1380563ed24c4f5befae370eccb7c55cc9d Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:42:15 +0200 Subject: [PATCH 53/58] separate doc tests for errors and examples --- tokio/src/net/windows/named_pipe.rs | 97 ++++++++++++++++++----------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 9e52e28be53..0e033f8aae6 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -619,60 +619,85 @@ impl ServerOptions { /// /// [`PIPE_ACCESS_OUTBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound /// - /// # Examples + /// # Errors + /// + /// Server side prevents connecting by denying outbound access, client + /// errors with [`std::io::ErrorKind::PermissionDenied`] when attempting to + /// create the connection. /// /// ``` /// use std::io; - /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound1"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// // Server side prevents connecting by denying outbound access, client errors - /// // when attempting to create the connection. - /// { - /// let server = ServerOptions::new() - /// .access_outbound(false) - /// .create(PIPE_NAME)?; + /// let server = ServerOptions::new() + /// .access_outbound(false) + /// .create(PIPE_NAME)?; /// - /// let e = ClientOptions::new() - /// .open(PIPE_NAME) - /// .unwrap_err(); + /// let e = ClientOptions::new() + /// .open(PIPE_NAME) + /// .unwrap_err(); /// - /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// # Ok(()) } + /// ``` /// - /// // Disabling reading allows a client to connect, but leads to runtime - /// // error if a read is attempted. - /// let mut client = ClientOptions::new() - /// .read(false) - /// .open(PIPE_NAME)?; + /// Disabling reading allows a client to connect, but attempting to read + /// will error with [`std::io::ErrorKind::PermissionDenied`]. /// - /// server.connect().await?; + /// ``` + /// use std::io; + /// use tokio::io::AsyncReadExt; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// - /// let mut buf = [0u8; 4]; - /// let e = client.read(&mut buf).await.unwrap_err(); - /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - /// } + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound2"; /// - /// // A functional, unidirectional client-to-server only communication. - /// { - /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; - /// let mut client = ClientOptions::new().read(false).open(PIPE_NAME)?; - /// server.connect().await?; + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let server = ServerOptions::new() + /// .access_outbound(false) + /// .create(PIPE_NAME)?; /// - /// let write = client.write_all(b"ping"); + /// let mut client = ClientOptions::new() + /// .read(false) + /// .open(PIPE_NAME)?; /// - /// let mut buf = [0u8; 4]; - /// let read = server.read_exact(&mut buf); + /// server.connect().await?; /// - /// let ((), read) = tokio::try_join!(write, read)?; + /// let mut buf = [0u8; 4]; + /// let e = client.read(&mut buf).await.unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// # Ok(()) } + /// ``` /// - /// println!("done reading and writing"); + /// # Examples /// - /// assert_eq!(read, 4); - /// assert_eq!(&buf[..], b"ping"); - /// } + /// A unidirectional named pipe that only supported client-to-server + /// communication. + /// + /// ``` + /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound3"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; + /// let mut client = ClientOptions::new().read(false).open(PIPE_NAME)?; + /// server.connect().await?; + /// + /// let write = client.write_all(b"ping"); + /// + /// let mut buf = [0u8; 4]; + /// let read = server.read_exact(&mut buf); + /// + /// let ((), read) = tokio::try_join!(write, read)?; + /// + /// println!("done reading and writing"); + /// + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..], b"ping"); /// # Ok(()) } /// ``` pub fn access_outbound(&mut self, allowed: bool) -> &mut Self { From 890d81d6fe78a8348ab357e84e99f28964acbf96 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 2 Jun 2021 14:50:12 +0200 Subject: [PATCH 54/58] separate doctess for errors and examples --- tokio/src/net/windows/named_pipe.rs | 122 ++++++++++++++++++---------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 0e033f8aae6..fe74bce5690 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -553,8 +553,62 @@ impl ServerOptions { /// /// [`PIPE_ACCESS_INBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound /// + /// # Errors + /// + /// Server side prevents connecting by denying inbound access, client errors + /// with [`std::io::ErrorKind::PermissionDenied`] when attempting to create + /// the connection. + /// + /// ``` + /// use std::io; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound-err1"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let _server = ServerOptions::new() + /// .access_inbound(false) + /// .create(PIPE_NAME)?; + /// + /// let e = ClientOptions::new() + /// .open(PIPE_NAME) + /// .unwrap_err(); + /// + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// # Ok(()) } + /// ``` + /// + /// Disabling writing allows a client to connect, but errors with + /// [`std::io::ErrorKind::PermissionDenied`] if a write is attempted. + /// + /// ``` + /// use std::io; + /// use tokio::io::AsyncWriteExt; + /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound-err2"; + /// + /// # #[tokio::main] async fn main() -> io::Result<()> { + /// let server = ServerOptions::new() + /// .access_inbound(false) + /// .create(PIPE_NAME)?; + /// + /// let mut client = ClientOptions::new() + /// .write(false) + /// .open(PIPE_NAME)?; + /// + /// server.connect().await?; + /// + /// let e = client.write(b"ping").await.unwrap_err(); + /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); + /// # Ok(()) } + /// ``` + /// /// # Examples /// + /// A unidirectional named pipe that only supports server-to-client + /// communication. + /// /// ``` /// use std::io; /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -563,49 +617,25 @@ impl ServerOptions { /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-inbound"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { - /// // Server side prevents connecting by denying inbound access, client errors - /// // when attempting to create the connection. - /// { - /// let _server = ServerOptions::new() - /// .access_inbound(false) - /// .create(PIPE_NAME)?; - /// - /// let e = ClientOptions::new() - /// .open(PIPE_NAME) - /// .unwrap_err(); - /// - /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - /// - /// // Disabling writing allows a client to connect, but leads to runtime - /// // error if a write is attempted. - /// let mut client = ClientOptions::new() - /// .write(false) - /// .open(PIPE_NAME)?; - /// - /// let e = client.write(b"ping").await.unwrap_err(); - /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied); - /// } + /// let mut server = ServerOptions::new() + /// .access_inbound(false) + /// .create(PIPE_NAME)?; /// - /// // A functional, unidirectional server-to-client only communication. - /// { - /// let mut server = ServerOptions::new() - /// .access_inbound(false) - /// .create(PIPE_NAME)?; + /// let mut client = ClientOptions::new() + /// .write(false) + /// .open(PIPE_NAME)?; /// - /// let mut client = ClientOptions::new() - /// .write(false) - /// .open(PIPE_NAME)?; + /// server.connect().await?; /// - /// let write = server.write_all(b"ping"); + /// let write = server.write_all(b"ping"); /// - /// let mut buf = [0u8; 4]; - /// let read = client.read_exact(&mut buf); + /// let mut buf = [0u8; 4]; + /// let read = client.read_exact(&mut buf); /// - /// let ((), read) = tokio::try_join!(write, read)?; + /// let ((), read) = tokio::try_join!(write, read)?; /// - /// assert_eq!(read, 4); - /// assert_eq!(&buf[..], b"ping"); - /// } + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..], b"ping"); /// # Ok(()) } /// ``` pub fn access_inbound(&mut self, allowed: bool) -> &mut Self { @@ -629,7 +659,7 @@ impl ServerOptions { /// use std::io; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound1"; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound-err1"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { /// let server = ServerOptions::new() @@ -652,7 +682,7 @@ impl ServerOptions { /// use tokio::io::AsyncReadExt; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound2"; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound-err2"; /// /// # #[tokio::main] async fn main() -> io::Result<()> { /// let server = ServerOptions::new() @@ -673,18 +703,24 @@ impl ServerOptions { /// /// # Examples /// - /// A unidirectional named pipe that only supported client-to-server + /// A unidirectional named pipe that only supports client-to-server /// communication. /// /// ``` /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound3"; + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-access-outbound"; /// /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let mut server = ServerOptions::new().access_outbound(false).create(PIPE_NAME)?; - /// let mut client = ClientOptions::new().read(false).open(PIPE_NAME)?; + /// let mut server = ServerOptions::new() + /// .access_outbound(false) + /// .create(PIPE_NAME)?; + /// + /// let mut client = ClientOptions::new() + /// .read(false) + /// .open(PIPE_NAME)?; + /// /// server.connect().await?; /// /// let write = client.write_all(b"ping"); From 19248e465a4a12b3797efe3422997345b5c709dc Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 7 Jun 2021 13:20:30 +0200 Subject: [PATCH 55/58] try_from_raw_handle to from_raw_handle --- tokio/src/net/windows/named_pipe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index fe74bce5690..f7e16d0433b 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -152,7 +152,7 @@ impl NamedPipe { /// /// [Tokio Runtime]: crate::runtime::Runtime /// [enabled I/O]: crate::runtime::Builder::enable_io - pub unsafe fn try_from_raw_handle(handle: RawHandle) -> io::Result { + pub unsafe fn from_raw_handle(handle: RawHandle) -> io::Result { let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); Ok(NamedPipe { From dc694fd4ab3a871cd0f6182e4be3d26927344fb3 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 7 Jun 2021 13:21:58 +0200 Subject: [PATCH 56/58] vec! instead of array --- examples/named-pipe-multi-client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index 2b45e062230..e4ae3f5fd03 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -37,7 +37,7 @@ async fn windows_main() -> io::Result<()> { server = ServerOptions::new().create(PIPE_NAME)?; let _ = tokio::spawn(async move { - let mut buf = [0u8; 4]; + let mut buf = vec![0u8; 4]; inner.read_exact(&mut buf).await?; inner.write_all(b"pong").await?; Ok::<_, io::Error>(()) From 1f851257fe1c82a6045916cbe02ebc15f8a179da Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 7 Jun 2021 13:59:47 +0200 Subject: [PATCH 57/58] remove peek-related fns --- examples/Cargo.toml | 4 - examples/named-pipe-multi-client.rs | 2 +- examples/named-pipe-peek.rs | 66 ------------ examples/named-pipe.rs | 4 +- tokio/src/net/windows/named_pipe.rs | 154 --------------------------- tokio/tests/named_pipe.rs | 156 +--------------------------- 6 files changed, 5 insertions(+), 381 deletions(-) delete mode 100644 examples/named-pipe-peek.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index eff3636c596..aa5f7b75d98 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -87,7 +87,3 @@ path = "named-pipe.rs" [[example]] name = "named-pipe-multi-client" path = "named-pipe-multi-client.rs" - -[[example]] -name = "named-pipe-peek" -path = "named-pipe-peek.rs" diff --git a/examples/named-pipe-multi-client.rs b/examples/named-pipe-multi-client.rs index e4ae3f5fd03..3231cb678fa 100644 --- a/examples/named-pipe-multi-client.rs +++ b/examples/named-pipe-multi-client.rs @@ -3,7 +3,7 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { use std::time::Duration; - use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; use tokio::time; use winapi::shared::winerror; diff --git a/examples/named-pipe-peek.rs b/examples/named-pipe-peek.rs deleted file mode 100644 index b89f0bc9a7d..00000000000 --- a/examples/named-pipe-peek.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::io; - -#[cfg(windows)] -async fn windows_main() -> io::Result<()> { - use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; - - const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; - const N: usize = 1000; - - let mut server = ServerOptions::new().create(PIPE_NAME)?; - let mut client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - let client = tokio::spawn(async move { - for _ in 0..N { - client.write_all(b"ping").await?; - } - - let mut buf = [0u8; 4]; - client.read_exact(&mut buf).await?; - - Ok::<_, io::Error>(buf) - }); - - let mut buf = [0u8; 4]; - let mut available = 0; - - for n in 0..N { - if available < 4 { - println!("read_exact: (n: {}, available: {})", n, available); - server.read_exact(&mut buf).await?; - assert_eq!(&buf[..], b"ping"); - - let info = server.peek(None)?; - available = info.total_bytes_available; - continue; - } - - println!("read_exact: (n: {}, available: {})", n, available); - server.read_exact(&mut buf).await?; - available -= buf.len(); - assert_eq!(&buf[..], b"ping"); - } - - server.write_all(b"pong").await?; - - let buf = client.await??; - assert_eq!(&buf[..], b"pong"); - Ok(()) -} - -#[tokio::main] -async fn main() -> io::Result<()> { - #[cfg(windows)] - { - windows_main().await?; - } - - #[cfg(not(windows))] - { - println!("Named pipes are only supported on Windows!"); - } - - Ok(()) -} diff --git a/examples/named-pipe.rs b/examples/named-pipe.rs index 76b88846c46..c444c73e31a 100644 --- a/examples/named-pipe.rs +++ b/examples/named-pipe.rs @@ -2,8 +2,8 @@ use std::io; #[cfg(windows)] async fn windows_main() -> io::Result<()> { - use tokio::io::AsyncWriteExt as _; - use tokio::io::{AsyncBufReadExt as _, BufReader}; + use tokio::io::AsyncWriteExt; + use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}; const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client"; diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index f7e16d0433b..733967061cd 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -296,145 +296,6 @@ impl NamedPipe { }) } } - - /// Copies data from a named or anonymous pipe into a buffer without - /// removing it from the pipe. It also returns information about data in the - /// pipe. - /// - /// # Considerations - /// - /// Data reported through peek is sporadic. Once peek returns any data for a - /// given named pipe, further calls to it are not gauranteed to return the - /// same or higher number of bytes available ([`total_bytes_available`]). It - /// might even report a count of `0` even if no data has been read from the - /// named pipe that was previously peeked. - /// - /// Peeking does not update the state of the named pipe, so in order to - /// advance it you have to actively issue reads. A peek reporting a number - /// of bytes available ([`total_bytes_available`]) of `0` does not guarantee - /// that there is no data available to read from the named pipe. Even if a - /// peer is writing data, reads still have to be issued for the state of the - /// named pipe to update. - /// - /// Finally, peeking might report no data available indefinitely if there's - /// too little data in the buffer of the named pipe. - /// - /// You can play around with the [`named-pipe-peek` example] to get a feel - /// for how this function behaves. - /// - /// [`total_bytes_available`]: PipePeekInfo::total_bytes_available - /// [`named-pipe-peek` example]: https://github.com/tokio-rs/tokio/blob/master/examples/named-pipe-peek.rs - /// - /// # Examples - /// - /// ``` - /// use std::io; - /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; - /// use tokio::net::windows::named_pipe::{ServerOptions, ClientOptions}; - /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; - /// const N: usize = 100; - /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let mut server = ServerOptions::new().create(PIPE_NAME)?; - /// let mut client = ClientOptions::new().open(PIPE_NAME)?; - /// server.connect().await?; - /// - /// let client = tokio::spawn(async move { - /// for _ in 0..N { - /// client.write_all(b"ping").await?; - /// } - /// - /// let mut buf = [0u8; 4]; - /// client.read_exact(&mut buf).await?; - /// - /// Ok::<_, io::Error>(buf) - /// }); - /// - /// let mut buf = [0u8; 4]; - /// let mut available = 0; - /// - /// for n in 0..N { - /// if available < 4 { - /// server.read_exact(&mut buf).await?; - /// assert_eq!(&buf[..], b"ping"); - /// - /// let info = server.peek(None)?; - /// available = info.total_bytes_available; - /// continue; - /// } - /// - /// // here we know that at least `available` bytes are immediately - /// // ready to read. - /// server.read_exact(&mut buf).await?; - /// available -= buf.len(); - /// assert_eq!(&buf[..], b"ping"); - /// } - /// - /// server.write_all(b"pong").await?; - /// - /// let buf = client.await??; - /// assert_eq!(&buf[..], b"pong"); - /// # Ok(()) } - /// ``` - pub fn peek(&mut self, mut buf: Option<&mut ReadBuf<'_>>) -> io::Result { - use std::convert::TryFrom; - - unsafe { - let mut n = 0; - let mut total_bytes_available = 0; - let mut bytes_left_this_message = 0; - - let result = { - let (buf, len) = match &mut buf { - Some(buf) => { - let unfilled = buf.unfilled_mut(); - let len = ::try_from(unfilled.len()).unwrap_or(DWORD::MAX); - // NB: The OS has no expectation on whether the buffer - // is initialized or not. - (unfilled.as_mut_ptr() as *mut _, len) - } - None => (ptr::null_mut(), 0), - }; - - namedpipeapi::PeekNamedPipe( - self.io.as_raw_handle(), - buf, - len, - &mut n, - &mut total_bytes_available, - &mut bytes_left_this_message, - ) - }; - - if result == FALSE { - return Err(io::Error::last_os_error()); - } - - // NB: This is either guaranteed to fit within `usize` because of - // the clamping above, or the OS is reporting bogus values. - let n = usize::try_from(n).unwrap(); - - if let Some(buf) = buf { - // Safety: we trust that the OS has initialized up until `n` - // through the call to PeekNamedPipe. - buf.assume_init(n); - buf.advance(n); - } - - let total_bytes_available = - usize::try_from(total_bytes_available).expect("available bytes too large"); - let bytes_left_this_message = - usize::try_from(bytes_left_this_message).expect("bytes left in message too large"); - - let info = PipePeekInfo { - total_bytes_available, - bytes_left_this_message, - }; - - Ok(info) - } - } } impl AsyncRead for NamedPipe { @@ -1229,21 +1090,6 @@ pub struct PipeInfo { pub in_buffer_size: u32, } -/// Information about a pipe gained by peeking it. -/// -/// See [`NamedPipe::peek`]. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct PipePeekInfo { - /// Indicates the total number of bytes available on the pipe. - pub total_bytes_available: usize, - /// Indicates the number of bytes left in the current message. - /// - /// If the pipe mode is not [`PipeMode::Message`], then this is - /// zero. - pub bytes_left_this_message: usize, -} - /// Encode an address so that it is a null-terminated wide string. fn encode_addr(addr: impl AsRef) -> Box<[u16]> { let len = addr.as_ref().encode_wide().count(); diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 8abaa3ea1cd..325231b70bb 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -5,163 +5,11 @@ use std::io; use std::mem; use std::os::windows::io::AsRawHandle; use std::time::Duration; -use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _, ReadBuf}; -use tokio::net::windows::named_pipe::{ClientOptions, NamedPipe, PipeMode, ServerOptions}; +use tokio::io::AsyncWriteExt; +use tokio::net::windows::named_pipe::{ClientOptions, PipeMode, ServerOptions}; use tokio::time; use winapi::shared::winerror; -#[tokio::test] -async fn test_named_pipe_peek_consumed() -> io::Result<()> { - const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-peek-consumed"; - const N: usize = 1000; - - let mut server = ServerOptions::new().create(PIPE_NAME)?; - let mut client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - let client = tokio::spawn(async move { - for _ in 0..N { - client.write_all(b"ping").await?; - } - - let mut buf = [0u8; 4]; - client.read_exact(&mut buf).await?; - - Ok::<_, io::Error>(buf) - }); - - let mut buf = [0u8; 4]; - let mut available = 0; - - for _ in 0..N { - if available < buf.len() { - server.read_exact(&mut buf).await?; - assert_eq!(&buf[..], b"ping"); - - let info = server.peek(Some(&mut ReadBuf::new(&mut buf[..])))?; - available = info.total_bytes_available; - continue; - } - - server.read_exact(&mut buf).await?; - available -= buf.len(); - assert_eq!(&buf[..], b"ping"); - } - - server.write_all(b"pong").await?; - - let buf = client.await??; - assert_eq!(&buf[..], b"pong"); - Ok(()) -} - -async fn peek_ping_pong(n: usize, mut client: NamedPipe, mut server: NamedPipe) -> io::Result<()> { - use rand::Rng as _; - use std::sync::Arc; - - /// A weirdly sized read to induce as many partial reads as possible. - const UNALIGNED: usize = 27; - - let mut data = vec![0; 1024]; - - let mut r = rand::thread_rng(); - r.fill(&mut data[..]); - - let data = Arc::new(data); - let data2 = data.clone(); - - let client = tokio::spawn(async move { - for _ in 0..n { - client.write_all(&data2[..]).await?; - } - - let mut buf = [0u8; 4]; - client.read_exact(&mut buf).await?; - Ok::<_, io::Error>(buf) - }); - - let server = tokio::spawn(async move { - let mut full_buf = vec![0u8; data.len()]; - let mut peeks = 0u32; - - for _ in 0..n { - let mut buf = &mut full_buf[..]; - - while !buf.is_empty() { - let e = usize::min(buf.len(), UNALIGNED); - let r = server.read(&mut buf[..e]).await?; - buf = &mut buf[r..]; - - let info = server.peek(None)?; - - if info.total_bytes_available != 0 { - peeks += 1; - } - } - - assert_eq!(&full_buf[..], &data[..]); - } - - server.write_all(b"pong").await?; - - // NB: this is not at all helpful, but keeping it here because when run - // in release mode we can usually see a couple of hundred peeks go - // through. - assert!(peeks == 0 || peeks > 0); - Ok::<_, io::Error>(()) - }); - - let (client, server) = tokio::try_join!(client, server)?; - assert_eq!(&client?[..], b"pong"); - let _ = server?; - Ok(()) -} - -#[tokio::test] -async fn test_named_pipe_peek() -> io::Result<()> { - { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-small"; - - let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - peek_ping_pong(1, client, server).await?; - } - - { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; - - let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - peek_ping_pong(1, server, client).await?; - } - - { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-server-peek-big"; - - let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - peek_ping_pong(100, client, server).await?; - } - - { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-peek-big"; - - let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - peek_ping_pong(100, server, client).await?; - } - - Ok(()) -} - #[tokio::test] async fn test_named_pipe_client_drop() -> io::Result<()> { const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-drop"; From 960522e50261f2d8db33f4604b5807648de4dbff Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 9 Jun 2021 21:00:25 +0200 Subject: [PATCH 58/58] Separate NamedPipe{Client,Server} --- tokio/src/net/windows/named_pipe.rs | 341 ++++++++++++++++++---------- tokio/tests/named_pipe.rs | 17 -- 2 files changed, 220 insertions(+), 138 deletions(-) diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 733967061cd..8013d6f5588 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -38,49 +38,20 @@ mod doc { use self::doc::*; -/// A [Windows named pipe]. +/// A [Windows named pipe] server. /// -/// Constructed using [`ClientOptions::open`] for clients, or -/// [`ServerOptions::create`] for servers. See their corresponding documentation -/// for examples. +/// Accepting client connections involves creating a server with +/// [`ServerOptions::create`] and waiting for clients to connect using +/// [`NamedPipeServer::connect`]. /// -/// Connecting a client involves a few steps. First we must try to -/// [`ClientOptions::open`], the error typically indicates one of two things: +/// To avoid having clients sporadically fail with +/// [`std::io::ErrorKind::NotFound`] when they connect to a server, we must +/// ensure that at least one server instance is available at all times. This +/// means that the typical listen loop for a server is a bit involved, because +/// we have to ensure that we never drop a server accidentally while a client +/// might connect. /// -/// * [`std::io::ErrorKind::NotFound`] - There is no server available. -/// * [`ERROR_PIPE_BUSY`] - There is a server available, but it is busy. Sleep for -/// a while and try again. -/// -/// So a typical client connect loop will look like the this: -/// -/// ```no_run -/// use std::time::Duration; -/// use tokio::net::windows::named_pipe::ClientOptions; -/// use tokio::time; -/// use winapi::shared::winerror; -/// -/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client"; -/// -/// # #[tokio::main] async fn main() -> std::io::Result<()> { -/// let client = loop { -/// match ClientOptions::new().open(PIPE_NAME) { -/// Ok(client) => break client, -/// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), -/// Err(e) => return Err(e), -/// } -/// -/// time::sleep(Duration::from_millis(50)).await; -/// }; -/// -/// /* use the connected client */ -/// # Ok(()) } -/// ``` -/// -/// To avoid having clients fail with [`std::io::ErrorKind::NotFound`] when -/// connecting to a named pipe server, you must ensure that at least one server -/// instance is up and running at all times. This means that the typical listen -/// loop for a server is a bit involved, because we have to ensure that we never -/// drop a server accidentally while a client might want to connect. +/// So a correctly implemented server looks like this: /// /// ```no_run /// use std::io; @@ -129,12 +100,12 @@ use self::doc::*; /// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY /// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes #[derive(Debug)] -pub struct NamedPipe { +pub struct NamedPipeServer { io: PollEvented, } -impl NamedPipe { - /// Fallibly construct a new named pipe from the specified raw handle. +impl NamedPipeServer { + /// Construct a new named pipe server from the specified raw handle. /// /// This function will consume ownership of the handle given, passing /// responsibility for closing the handle to the returned object. @@ -155,11 +126,37 @@ impl NamedPipe { pub unsafe fn from_raw_handle(handle: RawHandle) -> io::Result { let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); - Ok(NamedPipe { + Ok(Self { io: PollEvented::new(named_pipe)?, }) } + /// Retrieves information about the named pipe the server is associated + /// with. + /// + /// ```no_run + /// use tokio::net::windows::named_pipe::{PipeEnd, PipeMode, ServerOptions}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-server-info"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { + /// let server = ServerOptions::new() + /// .pipe_mode(PipeMode::Message) + /// .max_instances(5) + /// .create(PIPE_NAME)?; + /// + /// let server_info = server.info()?; + /// + /// assert_eq!(server_info.end, PipeEnd::Server); + /// assert_eq!(server_info.mode, PipeMode::Message); + /// assert_eq!(server_info.max_instances, 5); + /// # Ok(()) } + /// ``` + pub fn info(&self) -> io::Result { + // Safety: we're ensuring the lifetime of the named pipe. + unsafe { named_pipe_info(self.io.as_raw_handle()) } + } + /// Enables a named pipe server process to wait for a client process to /// connect to an instance of a named pipe. A client process connects by /// creating a named pipe with the same name. @@ -228,77 +225,146 @@ impl NamedPipe { pub fn disconnect(&self) -> io::Result<()> { self.io.disconnect() } +} + +impl AsyncRead for NamedPipeServer { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + unsafe { self.io.poll_read(cx, buf) } + } +} - /// Retrieves information about the current named pipe. +impl AsyncWrite for NamedPipeServer { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.io.poll_write(cx, buf) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + self.io.poll_write_vectored(cx, bufs) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) + } +} + +impl AsRawHandle for NamedPipeServer { + fn as_raw_handle(&self) -> RawHandle { + self.io.as_raw_handle() + } +} + +/// A [Windows named pipe] client. +/// +/// Constructed using [`ClientOptions::open`]. +/// +/// Connecting a client correctly involves a few steps. When connecting through +/// [`ClientOptions::open`], it might error indicating one of two things: +/// +/// * [`std::io::ErrorKind::NotFound`] - There is no server available. +/// * [`ERROR_PIPE_BUSY`] - There is a server available, but it is busy. Sleep +/// for a while and try again. +/// +/// So a correctly implemented client looks like this: +/// +/// ```no_run +/// use std::time::Duration; +/// use tokio::net::windows::named_pipe::ClientOptions; +/// use tokio::time; +/// use winapi::shared::winerror; +/// +/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client"; +/// +/// # #[tokio::main] async fn main() -> std::io::Result<()> { +/// let client = loop { +/// match ClientOptions::new().open(PIPE_NAME) { +/// Ok(client) => break client, +/// Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), +/// Err(e) => return Err(e), +/// } +/// +/// time::sleep(Duration::from_millis(50)).await; +/// }; +/// +/// /* use the connected client */ +/// # Ok(()) } +/// ``` +/// +/// [`ERROR_PIPE_BUSY`]: crate::winapi::shared::winerror::ERROR_PIPE_BUSY +/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes +#[derive(Debug)] +pub struct NamedPipeClient { + io: PollEvented, +} + +impl NamedPipeClient { + /// Construct a new named pipe client from the specified raw handle. /// - /// ``` - /// use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions, PipeMode, PipeEnd}; + /// This function will consume ownership of the handle given, passing + /// responsibility for closing the handle to the returned object. /// - /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-info"; + /// This function is also unsafe as the primitives currently returned have + /// the contract that they are the sole owner of the file descriptor they + /// are wrapping. Usage of this function could accidentally allow violating + /// this contract which can cause memory unsafety in code that relies on it + /// being true. /// - /// # #[tokio::main] async fn main() -> std::io::Result<()> { - /// let server = ServerOptions::new() - /// .pipe_mode(PipeMode::Message) - /// .max_instances(5) - /// .create(PIPE_NAME)?; + /// # Errors /// + /// This errors if called outside of a [Tokio Runtime], or in a runtime that + /// has not [enabled I/O], or if any OS-specific I/O errors occur. + /// + /// [Tokio Runtime]: crate::runtime::Runtime + /// [enabled I/O]: crate::runtime::Builder::enable_io + pub unsafe fn from_raw_handle(handle: RawHandle) -> io::Result { + let named_pipe = mio_windows::NamedPipe::from_raw_handle(handle); + + Ok(Self { + io: PollEvented::new(named_pipe)?, + }) + } + + /// Retrieves information about the named pipe the client is associated + /// with. + /// + /// ```no_run + /// use tokio::net::windows::named_pipe::{ClientOptions, PipeEnd, PipeMode}; + /// + /// const PIPE_NAME: &str = r"\\.\pipe\tokio-named-pipe-client-info"; + /// + /// # #[tokio::main] async fn main() -> std::io::Result<()> { /// let client = ClientOptions::new() /// .open(PIPE_NAME)?; /// - /// let server_info = server.info()?; /// let client_info = client.info()?; /// - /// assert_eq!(server_info.end, PipeEnd::Server); - /// assert_eq!(server_info.mode, PipeMode::Message); - /// assert_eq!(server_info.max_instances, 5); - /// /// assert_eq!(client_info.end, PipeEnd::Client); /// assert_eq!(client_info.mode, PipeMode::Message); - /// assert_eq!(server_info.max_instances, 5); + /// assert_eq!(client_info.max_instances, 5); /// # Ok(()) } /// ``` pub fn info(&self) -> io::Result { - unsafe { - let mut flags = 0; - let mut out_buffer_size = 0; - let mut in_buffer_size = 0; - let mut max_instances = 0; - - let result = namedpipeapi::GetNamedPipeInfo( - self.io.as_raw_handle(), - &mut flags, - &mut out_buffer_size, - &mut in_buffer_size, - &mut max_instances, - ); - - if result == FALSE { - return Err(io::Error::last_os_error()); - } - - let mut end = PipeEnd::Client; - let mut mode = PipeMode::Byte; - - if flags & winbase::PIPE_SERVER_END != 0 { - end = PipeEnd::Server; - } - - if flags & winbase::PIPE_TYPE_MESSAGE != 0 { - mode = PipeMode::Message; - } - - Ok(PipeInfo { - end, - mode, - out_buffer_size, - in_buffer_size, - max_instances, - }) - } + // Safety: we're ensuring the lifetime of the named pipe. + unsafe { named_pipe_info(self.io.as_raw_handle()) } } } -impl AsyncRead for NamedPipe { +impl AsyncRead for NamedPipeClient { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -308,7 +374,7 @@ impl AsyncRead for NamedPipe { } } -impl AsyncWrite for NamedPipe { +impl AsyncWrite for NamedPipeClient { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -334,7 +400,7 @@ impl AsyncWrite for NamedPipe { } } -impl AsRawHandle for NamedPipe { +impl AsRawHandle for NamedPipeClient { fn as_raw_handle(&self) -> RawHandle { self.io.as_raw_handle() } @@ -767,8 +833,7 @@ impl ServerOptions { /// Create the named pipe identified by `addr` for use as a server. /// - /// This function will call the [`CreateNamedPipe`] function and return the - /// result. + /// This uses the [`CreateNamedPipe`] function. /// /// [`CreateNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea /// @@ -791,7 +856,7 @@ impl ServerOptions { /// let server = ServerOptions::new().create(PIPE_NAME)?; /// # Ok(()) } /// ``` - pub fn create(&self, addr: impl AsRef) -> io::Result { + pub fn create(&self, addr: impl AsRef) -> io::Result { // Safety: We're calling create_with_security_attributes_raw w/ a null // pointer which disables it. unsafe { self.create_with_security_attributes_raw(addr, ptr::null_mut()) } @@ -824,7 +889,7 @@ impl ServerOptions { &self, addr: impl AsRef, attrs: *mut c_void, - ) -> io::Result { + ) -> io::Result { let addr = encode_addr(addr); let h = namedpipeapi::CreateNamedPipeW( @@ -842,10 +907,7 @@ impl ServerOptions { return Err(io::Error::last_os_error()); } - let io = mio_windows::NamedPipe::from_raw_handle(h); - let io = PollEvented::new(io)?; - - Ok(NamedPipe { io }) + NamedPipeServer::from_raw_handle(h) } } @@ -931,7 +993,8 @@ impl ClientOptions { /// Open the named pipe identified by `addr`. /// - /// This constructs the handle using [`CreateFile`]. + /// This opens the client using [`CreateFile`] with the + /// `dwCreationDisposition` option set to `OPEN_EXISTING`. /// /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// @@ -979,7 +1042,7 @@ impl ClientOptions { /// // use the connected client. /// # Ok(()) } /// ``` - pub fn open(&self, addr: impl AsRef) -> io::Result { + pub fn open(&self, addr: impl AsRef) -> io::Result { // Safety: We're calling open_with_security_attributes_raw w/ a null // pointer which disables it. unsafe { self.open_with_security_attributes_raw(addr, ptr::null_mut()) } @@ -1004,7 +1067,7 @@ impl ClientOptions { &self, addr: impl AsRef, attrs: *mut c_void, - ) -> io::Result { + ) -> io::Result { let addr = encode_addr(addr); // NB: We could use a platform specialized `OpenOptions` here, but since @@ -1025,10 +1088,7 @@ impl ClientOptions { return Err(io::Error::last_os_error()); } - let io = mio_windows::NamedPipe::from_raw_handle(h); - let io = PollEvented::new(io)?; - - Ok(NamedPipe { io }) + NamedPipeClient::from_raw_handle(h) } fn get_flags(&self) -> u32 { @@ -1036,7 +1096,7 @@ impl ClientOptions { } } -/// The pipe mode of a [`NamedPipe`]. +/// The pipe mode of a named pipe. /// /// Set through [`ServerOptions::pipe_mode`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -1049,8 +1109,8 @@ pub enum PipeMode { Byte, /// Data is written to the pipe as a stream of messages. The pipe treats the /// bytes written during each write operation as a message unit. Any reading - /// function on [`NamedPipe`] returns [`ERROR_MORE_DATA`] when a message is not - /// read completely. + /// on a named pipe returns [`ERROR_MORE_DATA`] when a message is not read + /// completely. /// /// Corresponds to [`PIPE_TYPE_MESSAGE`][crate::winapi::um::winbase::PIPE_TYPE_MESSAGE]. /// @@ -1058,15 +1118,15 @@ pub enum PipeMode { Message, } -/// Indicates the end of a [`NamedPipe`]. +/// Indicates the end of a named pipe. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum PipeEnd { - /// The [`NamedPipe`] refers to the client end of a named pipe instance. + /// The named pipe refers to the client end of a named pipe instance. /// /// Corresponds to [`PIPE_CLIENT_END`][crate::winapi::um::winbase::PIPE_CLIENT_END]. Client, - /// The [`NamedPipe`] refers to the server end of a named pipe instance. + /// The named pipe refers to the server end of a named pipe instance. /// /// Corresponds to [`PIPE_SERVER_END`][crate::winapi::um::winbase::PIPE_SERVER_END]. Server, @@ -1074,7 +1134,7 @@ pub enum PipeEnd { /// Information about a named pipe. /// -/// Constructed through [`NamedPipe::info`]. +/// Constructed through [`NamedPipeServer::info`] or [`NamedPipeClient::info`]. #[derive(Debug)] #[non_exhaustive] pub struct PipeInfo { @@ -1098,3 +1158,42 @@ fn encode_addr(addr: impl AsRef) -> Box<[u16]> { vec.push(0); vec.into_boxed_slice() } + +/// Internal function to get the info out of a raw named pipe. +unsafe fn named_pipe_info(handle: RawHandle) -> io::Result { + let mut flags = 0; + let mut out_buffer_size = 0; + let mut in_buffer_size = 0; + let mut max_instances = 0; + + let result = namedpipeapi::GetNamedPipeInfo( + handle, + &mut flags, + &mut out_buffer_size, + &mut in_buffer_size, + &mut max_instances, + ); + + if result == FALSE { + return Err(io::Error::last_os_error()); + } + + let mut end = PipeEnd::Client; + let mut mode = PipeMode::Byte; + + if flags & winbase::PIPE_SERVER_END != 0 { + end = PipeEnd::Server; + } + + if flags & winbase::PIPE_TYPE_MESSAGE != 0 { + mode = PipeMode::Message; + } + + Ok(PipeInfo { + end, + mode, + out_buffer_size, + in_buffer_size, + max_instances, + }) +} diff --git a/tokio/tests/named_pipe.rs b/tokio/tests/named_pipe.rs index 325231b70bb..3f267670502 100644 --- a/tokio/tests/named_pipe.rs +++ b/tokio/tests/named_pipe.rs @@ -32,23 +32,6 @@ async fn test_named_pipe_client_drop() -> io::Result<()> { Ok(()) } -// This tests what happens when a client tries to disconnect. -#[tokio::test] -async fn test_named_pipe_client_connect() -> io::Result<()> { - const PIPE_NAME: &str = r"\\.\pipe\test-named-pipe-client-connect"; - - let server = ServerOptions::new().create(PIPE_NAME)?; - let client = ClientOptions::new().open(PIPE_NAME)?; - server.connect().await?; - - let e = client.connect().await.unwrap_err(); - assert_eq!( - e.raw_os_error(), - Some(winerror::ERROR_INVALID_FUNCTION as i32) - ); - Ok(()) -} - #[tokio::test] async fn test_named_pipe_single_client() -> io::Result<()> { use tokio::io::{AsyncBufReadExt as _, BufReader};