compio_fs/named_pipe.rs
1//! [Windows named pipes](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes).
2//!
3//! The infrastructure of the code comes from tokio.
4
5#[cfg(doc)]
6use std::ptr::null_mut;
7use std::{ffi::OsStr, io, os::windows::io::FromRawHandle, ptr::null};
8
9use compio_buf::{BufResult, IoBuf, IoBufMut};
10use compio_driver::{
11 AsRawFd, BufferRef, RawFd, ToSharedFd, impl_raw_fd, op::ConnectNamedPipe, syscall,
12};
13use compio_io::{
14 AsyncRead, AsyncReadAt, AsyncReadManaged, AsyncReadMulti, AsyncWrite, util::Splittable,
15};
16use futures_util::Stream;
17use widestring::U16CString;
18use windows_sys::Win32::{
19 Storage::FileSystem::{
20 FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, PIPE_ACCESS_INBOUND,
21 PIPE_ACCESS_OUTBOUND, WRITE_DAC, WRITE_OWNER,
22 },
23 System::{
24 Pipes::{
25 CreateNamedPipeW, DisconnectNamedPipe, GetNamedPipeInfo, PIPE_ACCEPT_REMOTE_CLIENTS,
26 PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_SERVER_END,
27 PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES,
28 },
29 SystemServices::ACCESS_SYSTEM_SECURITY,
30 },
31};
32
33use crate::{File, OpenOptions};
34
35/// A [Windows named pipe] server.
36///
37/// Accepting client connections involves creating a server with
38/// [`ServerOptions::create`] and waiting for clients to connect using
39/// [`NamedPipeServer::connect`].
40///
41/// To avoid having clients sporadically fail with
42/// [`std::io::ErrorKind::NotFound`] when they connect to a server, we must
43/// ensure that at least one server instance is available at all times. This
44/// means that the typical listen loop for a server is a bit involved, because
45/// we have to ensure that we never drop a server accidentally while a client
46/// might connect.
47///
48/// So a correctly implemented server looks like this:
49///
50/// ```no_run
51/// use std::io;
52///
53/// use compio_fs::named_pipe::ServerOptions;
54///
55/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-server";
56///
57/// # fn main() -> std::io::Result<()> {
58/// // The first server needs to be constructed early so that clients can
59/// // be correctly connected. Otherwise calling .wait will cause the client to
60/// // error.
61/// //
62/// // Here we also make use of `first_pipe_instance`, which will ensure that
63/// // there are no other servers up and running already.
64/// let mut server = ServerOptions::new()
65/// .first_pipe_instance(true)
66/// .create(PIPE_NAME)?;
67///
68/// // Spawn the server loop.
69/// # compio_runtime::Runtime::new().unwrap().block_on(async move {
70/// loop {
71/// // Wait for a client to connect.
72/// let connected = server.connect().await?;
73///
74/// // Construct the next server to be connected before sending the one
75/// // we already have of onto a task. This ensures that the server
76/// // isn't closed (after it's done in the task) before a new one is
77/// // available. Otherwise the client might error with
78/// // `io::ErrorKind::NotFound`.
79/// server = ServerOptions::new().create(PIPE_NAME)?;
80///
81/// let client = compio_runtime::spawn(async move {
82/// // use the connected client
83/// # Ok::<_, std::io::Error>(())
84/// });
85/// # if true { break } // needed for type inference to work
86/// }
87/// # Ok::<_, io::Error>(())
88/// # })
89/// # }
90/// ```
91///
92/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes
93#[derive(Debug, Clone)]
94pub struct NamedPipeServer {
95 handle: File,
96}
97
98impl NamedPipeServer {
99 /// Retrieves information about the named pipe the server is associated
100 /// with.
101 ///
102 /// ```no_run
103 /// use compio_fs::named_pipe::{PipeEnd, PipeMode, ServerOptions};
104 ///
105 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-server-info";
106 ///
107 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
108 /// let server = ServerOptions::new()
109 /// .pipe_mode(PipeMode::Message)
110 /// .max_instances(5)
111 /// .create(PIPE_NAME)?;
112 ///
113 /// let server_info = server.info()?;
114 ///
115 /// assert_eq!(server_info.end, PipeEnd::Server);
116 /// assert_eq!(server_info.mode, PipeMode::Message);
117 /// assert_eq!(server_info.max_instances, 5);
118 /// # std::io::Result::Ok(()) });
119 /// ```
120 pub fn info(&self) -> io::Result<PipeInfo> {
121 // SAFETY: we're ensuring the lifetime of the named pipe.
122 unsafe { named_pipe_info(self.as_raw_fd()) }
123 }
124
125 /// Enables a named pipe server process to wait for a client process to
126 /// connect to an instance of a named pipe. A client process connects by
127 /// creating a named pipe with the same name.
128 ///
129 /// This corresponds to the [`ConnectNamedPipe`] system call.
130 ///
131 /// # Example
132 ///
133 /// ```no_run
134 /// use compio_fs::named_pipe::ServerOptions;
135 ///
136 /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe";
137 ///
138 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
139 /// let pipe = ServerOptions::new().create(PIPE_NAME)?;
140 ///
141 /// // Wait for a client to connect.
142 /// pipe.connect().await?;
143 ///
144 /// // Use the connected client...
145 /// # std::io::Result::Ok(()) });
146 /// ```
147 pub async fn connect(&self) -> io::Result<()> {
148 let op = ConnectNamedPipe::new(self.handle.to_shared_fd());
149 compio_runtime::submit(op).await.0?;
150 Ok(())
151 }
152
153 /// Disconnects the server end of a named pipe instance from a client
154 /// process.
155 ///
156 /// ```
157 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
158 /// use compio_io::AsyncWrite;
159 /// use windows_sys::Win32::Foundation::ERROR_PIPE_NOT_CONNECTED;
160 ///
161 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-disconnect";
162 ///
163 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
164 /// let server = ServerOptions::new().create(PIPE_NAME).unwrap();
165 ///
166 /// let mut client = ClientOptions::new().open(PIPE_NAME).await.unwrap();
167 ///
168 /// // Wait for a client to become connected.
169 /// server.connect().await.unwrap();
170 ///
171 /// // Forcibly disconnect the client.
172 /// server.disconnect().unwrap();
173 ///
174 /// // Write fails with an OS-specific error after client has been
175 /// // disconnected.
176 /// let e = client.write("ping").await.0.unwrap();
177 /// assert_eq!(e, 0);
178 /// # })
179 /// ```
180 pub fn disconnect(&self) -> io::Result<()> {
181 syscall!(BOOL, DisconnectNamedPipe(self.as_raw_fd() as _))?;
182 Ok(())
183 }
184
185 /// Close the server. If the returned future is dropped before polling, the
186 /// server won't be closed.
187 ///
188 /// See [`File::close`] for more details.
189 pub fn close(self) -> impl Future<Output = io::Result<()>> {
190 self.handle.close()
191 }
192}
193
194impl AsyncRead for NamedPipeServer {
195 #[inline]
196 async fn read<B: IoBufMut>(&mut self, buf: B) -> BufResult<usize, B> {
197 (&*self).read(buf).await
198 }
199}
200
201impl AsyncRead for &NamedPipeServer {
202 #[inline]
203 async fn read<B: IoBufMut>(&mut self, buffer: B) -> BufResult<usize, B> {
204 self.handle.read_at(buffer, 0).await
205 }
206}
207
208impl AsyncReadManaged for NamedPipeServer {
209 type Buffer = BufferRef;
210
211 async fn read_managed(&mut self, len: usize) -> io::Result<Option<Self::Buffer>> {
212 (&*self).read_managed(len).await
213 }
214}
215
216impl AsyncReadManaged for &NamedPipeServer {
217 type Buffer = BufferRef;
218
219 async fn read_managed(&mut self, len: usize) -> io::Result<Option<Self::Buffer>> {
220 // The position is ignored.
221 (&self.handle.inner).read_managed(len).await
222 }
223}
224
225impl AsyncReadMulti for NamedPipeServer {
226 fn read_multi(&mut self, len: usize) -> impl Stream<Item = io::Result<Self::Buffer>> {
227 self.handle.inner.read_multi(len)
228 }
229}
230
231impl AsyncReadMulti for &NamedPipeServer {
232 fn read_multi(&mut self, len: usize) -> impl Stream<Item = io::Result<Self::Buffer>> {
233 self.handle.inner.read_multi_shared(len)
234 }
235}
236
237impl AsyncWrite for NamedPipeServer {
238 #[inline]
239 async fn write<T: IoBuf>(&mut self, buf: T) -> BufResult<usize, T> {
240 (&*self).write(buf).await
241 }
242
243 #[inline]
244 async fn flush(&mut self) -> io::Result<()> {
245 (&*self).flush().await
246 }
247
248 #[inline]
249 async fn shutdown(&mut self) -> io::Result<()> {
250 (&*self).shutdown().await
251 }
252}
253
254impl AsyncWrite for &NamedPipeServer {
255 #[inline]
256 async fn write<T: IoBuf>(&mut self, buffer: T) -> BufResult<usize, T> {
257 (&self.handle.inner).write(buffer).await
258 }
259
260 #[inline]
261 async fn flush(&mut self) -> io::Result<()> {
262 Ok(())
263 }
264
265 #[inline]
266 async fn shutdown(&mut self) -> io::Result<()> {
267 Ok(())
268 }
269}
270
271impl Splittable for NamedPipeServer {
272 type ReadHalf = NamedPipeServer;
273 type WriteHalf = NamedPipeServer;
274
275 fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
276 (self.clone(), self)
277 }
278}
279
280impl Splittable for &NamedPipeServer {
281 type ReadHalf = NamedPipeServer;
282 type WriteHalf = NamedPipeServer;
283
284 fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
285 (self.clone(), self.clone())
286 }
287}
288
289impl_raw_fd!(NamedPipeServer, std::fs::File, handle, file);
290
291/// A [Windows named pipe] client.
292///
293/// Constructed using [`ClientOptions::open`].
294///
295/// Connecting a client correctly involves a few steps. When connecting through
296/// [`ClientOptions::open`], it might error indicating one of two things:
297///
298/// * [`std::io::ErrorKind::NotFound`] - There is no server available.
299/// * [`ERROR_PIPE_BUSY`] - There is a server available, but it is busy. Sleep
300/// for a while and try again.
301///
302/// So a correctly implemented client looks like this:
303///
304/// ```no_run
305/// use std::time::Duration;
306///
307/// use compio_fs::named_pipe::ClientOptions;
308/// use compio_runtime::time;
309/// use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
310///
311/// const PIPE_NAME: &str = r"\\.\pipe\named-pipe-idiomatic-client";
312///
313/// # compio_runtime::Runtime::new().unwrap().block_on(async move {
314/// let client = loop {
315/// match ClientOptions::new().open(PIPE_NAME).await {
316/// Ok(client) => break client,
317/// Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
318/// Err(e) => return Err(e),
319/// }
320///
321/// time::sleep(Duration::from_millis(50)).await;
322/// };
323///
324/// // use the connected client
325/// # Ok(()) });
326/// ```
327///
328/// [`ERROR_PIPE_BUSY`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/Foundation/constant.ERROR_PIPE_BUSY.html
329/// [Windows named pipe]: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes
330#[derive(Debug, Clone)]
331pub struct NamedPipeClient {
332 handle: File,
333}
334
335impl NamedPipeClient {
336 /// Retrieves information about the named pipe the client is associated
337 /// with.
338 ///
339 /// ```no_run
340 /// use compio_fs::named_pipe::{ClientOptions, PipeEnd, PipeMode};
341 ///
342 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-client-info";
343 ///
344 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
345 /// let client = ClientOptions::new().open(PIPE_NAME).await?;
346 ///
347 /// let client_info = client.info()?;
348 ///
349 /// assert_eq!(client_info.end, PipeEnd::Client);
350 /// assert_eq!(client_info.mode, PipeMode::Message);
351 /// assert_eq!(client_info.max_instances, 5);
352 /// # std::io::Result::Ok(()) });
353 /// ```
354 pub fn info(&self) -> io::Result<PipeInfo> {
355 // SAFETY: we're ensuring the lifetime of the named pipe.
356 unsafe { named_pipe_info(self.as_raw_fd()) }
357 }
358
359 /// Close the client. If the returned future is dropped before polling, the
360 /// client won't be closed.
361 ///
362 /// See [`File::close`] for more details.
363 pub fn close(self) -> impl Future<Output = io::Result<()>> {
364 self.handle.close()
365 }
366}
367
368impl AsyncRead for NamedPipeClient {
369 #[inline]
370 async fn read<B: IoBufMut>(&mut self, buf: B) -> BufResult<usize, B> {
371 (&*self).read(buf).await
372 }
373}
374
375impl AsyncRead for &NamedPipeClient {
376 #[inline]
377 async fn read<B: IoBufMut>(&mut self, buffer: B) -> BufResult<usize, B> {
378 // The position is ignored.
379 self.handle.read_at(buffer, 0).await
380 }
381}
382
383impl AsyncReadManaged for NamedPipeClient {
384 type Buffer = BufferRef;
385
386 async fn read_managed(&mut self, len: usize) -> io::Result<Option<Self::Buffer>> {
387 (&*self).read_managed(len).await
388 }
389}
390
391impl AsyncReadManaged for &NamedPipeClient {
392 type Buffer = BufferRef;
393
394 async fn read_managed(&mut self, len: usize) -> io::Result<Option<Self::Buffer>> {
395 // The position is ignored.
396 (&self.handle.inner).read_managed(len).await
397 }
398}
399
400impl AsyncReadMulti for NamedPipeClient {
401 fn read_multi(&mut self, len: usize) -> impl Stream<Item = io::Result<Self::Buffer>> {
402 self.handle.inner.read_multi(len)
403 }
404}
405
406impl AsyncReadMulti for &NamedPipeClient {
407 fn read_multi(&mut self, len: usize) -> impl Stream<Item = io::Result<Self::Buffer>> {
408 self.handle.inner.read_multi_shared(len)
409 }
410}
411
412impl AsyncWrite for NamedPipeClient {
413 #[inline]
414 async fn write<T: IoBuf>(&mut self, buf: T) -> BufResult<usize, T> {
415 (&*self).write(buf).await
416 }
417
418 #[inline]
419 async fn flush(&mut self) -> io::Result<()> {
420 (&*self).flush().await
421 }
422
423 #[inline]
424 async fn shutdown(&mut self) -> io::Result<()> {
425 (&*self).shutdown().await
426 }
427}
428
429impl AsyncWrite for &NamedPipeClient {
430 #[inline]
431 async fn write<T: IoBuf>(&mut self, buffer: T) -> BufResult<usize, T> {
432 // The position is ignored.
433 (&self.handle.inner).write(buffer).await
434 }
435
436 #[inline]
437 async fn flush(&mut self) -> io::Result<()> {
438 Ok(())
439 }
440
441 #[inline]
442 async fn shutdown(&mut self) -> io::Result<()> {
443 Ok(())
444 }
445}
446
447impl Splittable for NamedPipeClient {
448 type ReadHalf = NamedPipeClient;
449 type WriteHalf = NamedPipeClient;
450
451 fn split(self) -> (NamedPipeClient, NamedPipeClient) {
452 (self.clone(), self)
453 }
454}
455
456impl Splittable for &NamedPipeClient {
457 type ReadHalf = NamedPipeClient;
458 type WriteHalf = NamedPipeClient;
459
460 fn split(self) -> (NamedPipeClient, NamedPipeClient) {
461 (self.clone(), self.clone())
462 }
463}
464
465impl_raw_fd!(NamedPipeClient, std::fs::File, handle, file);
466
467/// A builder structure for construct a named pipe with named pipe-specific
468/// options. This is required to use for named pipe servers who wants to modify
469/// pipe-related options.
470///
471/// See [`ServerOptions::create`].
472#[derive(Debug, Clone)]
473pub struct ServerOptions {
474 // dwOpenMode
475 access_inbound: bool,
476 access_outbound: bool,
477 first_pipe_instance: bool,
478 write_dac: bool,
479 write_owner: bool,
480 access_system_security: bool,
481 // dwPipeMode
482 pipe_mode: PipeMode,
483 reject_remote_clients: bool,
484 // other options
485 max_instances: u32,
486 out_buffer_size: u32,
487 in_buffer_size: u32,
488 default_timeout: u32,
489}
490
491impl ServerOptions {
492 /// Creates a new named pipe builder with the default settings.
493 ///
494 /// ```
495 /// use compio_fs::named_pipe::ServerOptions;
496 ///
497 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-new";
498 ///
499 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
500 /// let server = ServerOptions::new().create(PIPE_NAME).unwrap();
501 /// # })
502 /// ```
503 pub fn new() -> ServerOptions {
504 ServerOptions {
505 access_inbound: true,
506 access_outbound: true,
507 first_pipe_instance: false,
508 write_dac: false,
509 write_owner: false,
510 access_system_security: false,
511 pipe_mode: PipeMode::Byte,
512 reject_remote_clients: true,
513 max_instances: PIPE_UNLIMITED_INSTANCES,
514 out_buffer_size: 65536,
515 in_buffer_size: 65536,
516 default_timeout: 0,
517 }
518 }
519
520 /// The pipe mode.
521 ///
522 /// The default pipe mode is [`PipeMode::Byte`]. See [`PipeMode`] for
523 /// documentation of what each mode means.
524 ///
525 /// This corresponds to specifying `PIPE_TYPE_` and `PIPE_READMODE_` in
526 /// [`dwPipeMode`].
527 ///
528 /// [`dwPipeMode`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
529 pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self {
530 self.pipe_mode = pipe_mode;
531 self
532 }
533
534 /// The flow of data in the pipe goes from client to server only.
535 ///
536 /// This corresponds to setting [`PIPE_ACCESS_INBOUND`].
537 ///
538 /// [`PIPE_ACCESS_INBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_inbound
539 ///
540 /// # Errors
541 ///
542 /// Server side prevents connecting by denying inbound access, client errors
543 /// with [`std::io::ErrorKind::PermissionDenied`] when attempting to create
544 /// the connection.
545 ///
546 /// ```
547 /// use std::io;
548 ///
549 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
550 ///
551 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-inbound-err1";
552 ///
553 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
554 /// let _server = ServerOptions::new()
555 /// .access_inbound(false)
556 /// .create(PIPE_NAME)
557 /// .unwrap();
558 ///
559 /// let e = ClientOptions::new().open(PIPE_NAME).await.unwrap_err();
560 ///
561 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
562 /// # })
563 /// ```
564 ///
565 /// Disabling writing allows a client to connect, but errors with
566 /// [`std::io::ErrorKind::PermissionDenied`] if a write is attempted.
567 ///
568 /// ```
569 /// use std::io;
570 ///
571 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
572 /// use compio_io::AsyncWrite;
573 ///
574 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-inbound-err2";
575 ///
576 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
577 /// let server = ServerOptions::new()
578 /// .access_inbound(false)
579 /// .create(PIPE_NAME)
580 /// .unwrap();
581 ///
582 /// let mut client = ClientOptions::new()
583 /// .write(false)
584 /// .open(PIPE_NAME)
585 /// .await
586 /// .unwrap();
587 ///
588 /// server.connect().await.unwrap();
589 ///
590 /// let e = client.write("ping").await.0.unwrap_err();
591 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
592 /// # })
593 /// ```
594 ///
595 /// # Examples
596 ///
597 /// A unidirectional named pipe that only supports server-to-client
598 /// communication.
599 ///
600 /// ```
601 /// use std::io;
602 ///
603 /// use compio_buf::BufResult;
604 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
605 /// use compio_io::{AsyncReadExt, AsyncWriteExt};
606 ///
607 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-inbound";
608 ///
609 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
610 /// let mut server = ServerOptions::new()
611 /// .access_inbound(false)
612 /// .create(PIPE_NAME)
613 /// .unwrap();
614 ///
615 /// let mut client = ClientOptions::new()
616 /// .write(false)
617 /// .open(PIPE_NAME)
618 /// .await
619 /// .unwrap();
620 ///
621 /// server.connect().await.unwrap();
622 ///
623 /// let write = server.write_all("ping");
624 ///
625 /// let buf = Vec::with_capacity(4);
626 /// let read = client.read_exact(buf);
627 ///
628 /// let (BufResult(write, _), BufResult(read, buf)) = futures_util::join!(write, read);
629 /// write.unwrap();
630 /// read.unwrap();
631 ///
632 /// assert_eq!(&buf[..], b"ping");
633 /// # })
634 /// ```
635 pub fn access_inbound(&mut self, allowed: bool) -> &mut Self {
636 self.access_inbound = allowed;
637 self
638 }
639
640 /// The flow of data in the pipe goes from server to client only.
641 ///
642 /// This corresponds to setting [`PIPE_ACCESS_OUTBOUND`].
643 ///
644 /// [`PIPE_ACCESS_OUTBOUND`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_access_outbound
645 ///
646 /// # Errors
647 ///
648 /// Server side prevents connecting by denying outbound access, client
649 /// errors with [`std::io::ErrorKind::PermissionDenied`] when attempting to
650 /// create the connection.
651 ///
652 /// ```
653 /// use std::io;
654 ///
655 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
656 ///
657 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-outbound-err1";
658 ///
659 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
660 /// let server = ServerOptions::new()
661 /// .access_outbound(false)
662 /// .create(PIPE_NAME)
663 /// .unwrap();
664 ///
665 /// let e = ClientOptions::new().open(PIPE_NAME).await.unwrap_err();
666 ///
667 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
668 /// # })
669 /// ```
670 ///
671 /// Disabling reading allows a client to connect, but attempting to read
672 /// will error with [`std::io::ErrorKind::PermissionDenied`].
673 ///
674 /// ```
675 /// use std::io;
676 ///
677 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
678 /// use compio_io::AsyncRead;
679 ///
680 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-outbound-err2";
681 ///
682 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
683 /// let server = ServerOptions::new()
684 /// .access_outbound(false)
685 /// .create(PIPE_NAME)
686 /// .unwrap();
687 ///
688 /// let mut client = ClientOptions::new()
689 /// .read(false)
690 /// .open(PIPE_NAME)
691 /// .await
692 /// .unwrap();
693 ///
694 /// server.connect().await.unwrap();
695 ///
696 /// let buf = Vec::with_capacity(4);
697 /// let e = client.read(buf).await.0.unwrap_err();
698 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
699 /// # })
700 /// ```
701 ///
702 /// # Examples
703 ///
704 /// A unidirectional named pipe that only supports client-to-server
705 /// communication.
706 ///
707 /// ```
708 /// use compio_buf::BufResult;
709 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
710 /// use compio_io::{AsyncReadExt, AsyncWriteExt};
711 ///
712 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-access-outbound";
713 ///
714 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
715 /// let mut server = ServerOptions::new()
716 /// .access_outbound(false)
717 /// .create(PIPE_NAME)
718 /// .unwrap();
719 ///
720 /// let mut client = ClientOptions::new()
721 /// .read(false)
722 /// .open(PIPE_NAME)
723 /// .await
724 /// .unwrap();
725 ///
726 /// server.connect().await.unwrap();
727 ///
728 /// let write = client.write_all("ping");
729 ///
730 /// let buf = Vec::with_capacity(4);
731 /// let read = server.read_exact(buf);
732 ///
733 /// let (BufResult(write, _), BufResult(read, buf)) = futures_util::join!(write, read);
734 /// write.unwrap();
735 /// read.unwrap();
736 ///
737 /// println!("done reading and writing");
738 ///
739 /// assert_eq!(&buf[..], b"ping");
740 /// # })
741 /// ```
742 pub fn access_outbound(&mut self, allowed: bool) -> &mut Self {
743 self.access_outbound = allowed;
744 self
745 }
746
747 /// If you attempt to create multiple instances of a pipe with this flag
748 /// set, creation of the first server instance succeeds, but creation of any
749 /// subsequent instances will fail with
750 /// [`std::io::ErrorKind::PermissionDenied`].
751 ///
752 /// This option is intended to be used with servers that want to ensure that
753 /// they are the only process listening for clients on a given named pipe.
754 /// This is accomplished by enabling it for the first server instance
755 /// created in a process.
756 ///
757 /// This corresponds to setting [`FILE_FLAG_FIRST_PIPE_INSTANCE`].
758 ///
759 /// # Errors
760 ///
761 /// If this option is set and more than one instance of the server for a
762 /// given named pipe exists, calling [`create`] will fail with
763 /// [`std::io::ErrorKind::PermissionDenied`].
764 ///
765 /// ```
766 /// use std::io;
767 ///
768 /// use compio_fs::named_pipe::ServerOptions;
769 ///
770 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-first-instance-error";
771 ///
772 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
773 /// let server1 = ServerOptions::new()
774 /// .first_pipe_instance(true)
775 /// .create(PIPE_NAME)
776 /// .unwrap();
777 ///
778 /// // Second server errs, since it's not the first instance.
779 /// let e = ServerOptions::new()
780 /// .first_pipe_instance(true)
781 /// .create(PIPE_NAME)
782 /// .unwrap_err();
783 ///
784 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
785 /// # })
786 /// ```
787 ///
788 /// # Examples
789 ///
790 /// ```
791 /// use std::io;
792 ///
793 /// use compio_fs::named_pipe::ServerOptions;
794 ///
795 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-first-instance";
796 ///
797 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
798 /// let mut builder = ServerOptions::new();
799 /// builder.first_pipe_instance(true);
800 ///
801 /// let server = builder.create(PIPE_NAME).unwrap();
802 /// let e = builder.create(PIPE_NAME).unwrap_err();
803 /// assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
804 /// drop(server);
805 ///
806 /// // OK: since, we've closed the other instance.
807 /// let _server2 = builder.create(PIPE_NAME).unwrap();
808 /// # })
809 /// ```
810 ///
811 /// [`create`]: ServerOptions::create
812 /// [`FILE_FLAG_FIRST_PIPE_INSTANCE`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_first_pipe_instance
813 pub fn first_pipe_instance(&mut self, first: bool) -> &mut Self {
814 self.first_pipe_instance = first;
815 self
816 }
817
818 /// Requests permission to modify the pipe's discretionary access control
819 /// list.
820 ///
821 /// This corresponds to setting [`WRITE_DAC`] in dwOpenMode.
822 ///
823 /// # Examples
824 ///
825 /// ```
826 /// use std::{io, ptr};
827 ///
828 /// use compio_driver::AsRawFd;
829 /// use compio_fs::named_pipe::ServerOptions;
830 /// use windows_sys::Win32::{
831 /// Foundation::ERROR_SUCCESS,
832 /// Security::{
833 /// Authorization::{SE_KERNEL_OBJECT, SetSecurityInfo},
834 /// DACL_SECURITY_INFORMATION,
835 /// },
836 /// };
837 ///
838 /// const PIPE_NAME: &str = r"\\.\pipe\write_dac_pipe";
839 ///
840 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
841 /// let mut pipe_template = ServerOptions::new();
842 /// pipe_template.write_dac(true);
843 /// let pipe = pipe_template.create(PIPE_NAME).unwrap();
844 ///
845 /// unsafe {
846 /// assert_eq!(
847 /// ERROR_SUCCESS,
848 /// SetSecurityInfo(
849 /// pipe.as_raw_fd() as _,
850 /// SE_KERNEL_OBJECT,
851 /// DACL_SECURITY_INFORMATION,
852 /// ptr::null_mut(),
853 /// ptr::null_mut(),
854 /// ptr::null_mut(),
855 /// ptr::null_mut(),
856 /// )
857 /// );
858 /// }
859 ///
860 /// # })
861 /// ```
862 /// ```
863 /// use std::{io, ptr};
864 ///
865 /// use compio_driver::AsRawFd;
866 /// use compio_fs::named_pipe::ServerOptions;
867 /// use windows_sys::Win32::{
868 /// Foundation::ERROR_ACCESS_DENIED,
869 /// Security::{
870 /// Authorization::{SE_KERNEL_OBJECT, SetSecurityInfo},
871 /// DACL_SECURITY_INFORMATION,
872 /// },
873 /// };
874 ///
875 /// const PIPE_NAME: &str = r"\\.\pipe\write_dac_pipe_fail";
876 ///
877 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
878 /// let mut pipe_template = ServerOptions::new();
879 /// pipe_template.write_dac(false);
880 /// let pipe = pipe_template.create(PIPE_NAME).unwrap();
881 ///
882 /// unsafe {
883 /// assert_eq!(
884 /// ERROR_ACCESS_DENIED,
885 /// SetSecurityInfo(
886 /// pipe.as_raw_fd() as _,
887 /// SE_KERNEL_OBJECT,
888 /// DACL_SECURITY_INFORMATION,
889 /// ptr::null_mut(),
890 /// ptr::null_mut(),
891 /// ptr::null_mut(),
892 /// ptr::null_mut(),
893 /// )
894 /// );
895 /// }
896 ///
897 /// # })
898 /// ```
899 ///
900 /// [`WRITE_DAC`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
901 pub fn write_dac(&mut self, requested: bool) -> &mut Self {
902 self.write_dac = requested;
903 self
904 }
905
906 /// Requests permission to modify the pipe's owner.
907 ///
908 /// This corresponds to setting [`WRITE_OWNER`] in dwOpenMode.
909 ///
910 /// [`WRITE_OWNER`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
911 pub fn write_owner(&mut self, requested: bool) -> &mut Self {
912 self.write_owner = requested;
913 self
914 }
915
916 /// Requests permission to modify the pipe's system access control list.
917 ///
918 /// This corresponds to setting [`ACCESS_SYSTEM_SECURITY`] in dwOpenMode.
919 ///
920 /// [`ACCESS_SYSTEM_SECURITY`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
921 pub fn access_system_security(&mut self, requested: bool) -> &mut Self {
922 self.access_system_security = requested;
923 self
924 }
925
926 /// Indicates whether this server can accept remote clients or not. Remote
927 /// clients are disabled by default.
928 ///
929 /// This corresponds to setting [`PIPE_REJECT_REMOTE_CLIENTS`].
930 ///
931 /// [`PIPE_REJECT_REMOTE_CLIENTS`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#pipe_reject_remote_clients
932 pub fn reject_remote_clients(&mut self, reject: bool) -> &mut Self {
933 self.reject_remote_clients = reject;
934 self
935 }
936
937 /// The maximum number of instances that can be created for this pipe. The
938 /// first instance of the pipe can specify this value; the same number must
939 /// be specified for other instances of the pipe. Acceptable values are in
940 /// the range 1 through 254. The default value is unlimited.
941 ///
942 /// This corresponds to specifying [`nMaxInstances`].
943 ///
944 /// [`nMaxInstances`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
945 ///
946 /// # Errors
947 ///
948 /// The same numbers of `max_instances` have to be used by all servers. Any
949 /// additional servers trying to be built which uses a mismatching value
950 /// might error.
951 ///
952 /// ```
953 /// use std::io;
954 ///
955 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
956 /// use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
957 ///
958 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-max-instances";
959 ///
960 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
961 /// let mut server = ServerOptions::new();
962 /// server.max_instances(2);
963 ///
964 /// let s1 = server.create(PIPE_NAME).unwrap();
965 /// let c1 = ClientOptions::new().open(PIPE_NAME).await.unwrap();
966 ///
967 /// let s2 = server.create(PIPE_NAME).unwrap();
968 /// let c2 = ClientOptions::new().open(PIPE_NAME).await.unwrap();
969 ///
970 /// // Too many servers!
971 /// let e = server.create(PIPE_NAME).unwrap_err();
972 /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32));
973 ///
974 /// // Still too many servers even if we specify a higher value!
975 /// let e = server.max_instances(100).create(PIPE_NAME).unwrap_err();
976 /// assert_eq!(e.raw_os_error(), Some(ERROR_PIPE_BUSY as i32));
977 /// # })
978 /// ```
979 ///
980 /// # Panics
981 ///
982 /// This function will panic if more than 254 instances are specified. If
983 /// you do not wish to set an instance limit, leave it unspecified.
984 ///
985 /// ```should_panic
986 /// use compio_fs::named_pipe::ServerOptions;
987 ///
988 /// let builder = ServerOptions::new().max_instances(255);
989 /// ```
990 #[track_caller]
991 pub fn max_instances(&mut self, instances: usize) -> &mut Self {
992 assert!(instances < 255, "cannot specify more than 254 instances");
993 self.max_instances = instances as u32;
994 self
995 }
996
997 /// The number of bytes to reserve for the output buffer.
998 ///
999 /// This corresponds to specifying [`nOutBufferSize`].
1000 ///
1001 /// [`nOutBufferSize`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
1002 pub fn out_buffer_size(&mut self, buffer: u32) -> &mut Self {
1003 self.out_buffer_size = buffer;
1004 self
1005 }
1006
1007 /// The number of bytes to reserve for the input buffer.
1008 ///
1009 /// This corresponds to specifying [`nInBufferSize`].
1010 ///
1011 /// [`nInBufferSize`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
1012 pub fn in_buffer_size(&mut self, buffer: u32) -> &mut Self {
1013 self.in_buffer_size = buffer;
1014 self
1015 }
1016
1017 /// Creates the named pipe identified by `addr` for use as a server.
1018 ///
1019 /// This uses the [`CreateNamedPipe`] function.
1020 ///
1021 /// [`CreateNamedPipe`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
1022 ///
1023 /// # Examples
1024 ///
1025 /// ```
1026 /// use compio_fs::named_pipe::ServerOptions;
1027 ///
1028 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-create";
1029 ///
1030 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
1031 /// let server = ServerOptions::new().create(PIPE_NAME).unwrap();
1032 /// # })
1033 /// ```
1034 pub fn create(&self, addr: impl AsRef<OsStr>) -> io::Result<NamedPipeServer> {
1035 let addr = U16CString::from_os_str(addr)
1036 .map_err(|e| io::Error::new(std::io::ErrorKind::InvalidData, e))?;
1037
1038 let pipe_mode = {
1039 let mut mode = if matches!(self.pipe_mode, PipeMode::Message) {
1040 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE
1041 } else {
1042 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
1043 };
1044 if self.reject_remote_clients {
1045 mode |= PIPE_REJECT_REMOTE_CLIENTS;
1046 } else {
1047 mode |= PIPE_ACCEPT_REMOTE_CLIENTS;
1048 }
1049 mode
1050 };
1051 let open_mode = {
1052 let mut mode = FILE_FLAG_OVERLAPPED;
1053 if self.access_inbound {
1054 mode |= PIPE_ACCESS_INBOUND;
1055 }
1056 if self.access_outbound {
1057 mode |= PIPE_ACCESS_OUTBOUND;
1058 }
1059 if self.first_pipe_instance {
1060 mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
1061 }
1062 if self.write_dac {
1063 mode |= WRITE_DAC;
1064 }
1065 if self.write_owner {
1066 mode |= WRITE_OWNER;
1067 }
1068 if self.access_system_security {
1069 mode |= ACCESS_SYSTEM_SECURITY;
1070 }
1071 mode
1072 };
1073
1074 let h = syscall!(
1075 HANDLE,
1076 CreateNamedPipeW(
1077 addr.as_ptr(),
1078 open_mode,
1079 pipe_mode,
1080 self.max_instances,
1081 self.out_buffer_size,
1082 self.in_buffer_size,
1083 self.default_timeout,
1084 null(),
1085 )
1086 )?;
1087
1088 Ok(NamedPipeServer {
1089 handle: File::from_std(unsafe { std::fs::File::from_raw_handle(h as _) })?,
1090 })
1091 }
1092}
1093
1094impl Default for ServerOptions {
1095 fn default() -> Self {
1096 Self::new()
1097 }
1098}
1099
1100/// A builder suitable for building and interacting with named pipes from the
1101/// client side.
1102///
1103/// See [`ClientOptions::open`].
1104#[derive(Debug, Clone)]
1105pub struct ClientOptions {
1106 options: OpenOptions,
1107 pipe_mode: PipeMode,
1108}
1109
1110impl ClientOptions {
1111 /// Creates a new named pipe builder with the default settings.
1112 ///
1113 /// ```
1114 /// use compio_fs::named_pipe::{ClientOptions, ServerOptions};
1115 ///
1116 /// const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe-client-new";
1117 ///
1118 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
1119 /// // Server must be created in order for the client creation to succeed.
1120 /// let server = ServerOptions::new().create(PIPE_NAME).unwrap();
1121 /// let client = ClientOptions::new().open(PIPE_NAME).await.unwrap();
1122 /// # })
1123 /// ```
1124 pub fn new() -> Self {
1125 use windows_sys::Win32::Storage::FileSystem::SECURITY_IDENTIFICATION;
1126
1127 let mut options = OpenOptions::new();
1128 options
1129 .read(true)
1130 .write(true)
1131 .security_qos_flags(SECURITY_IDENTIFICATION);
1132 Self {
1133 options,
1134 pipe_mode: PipeMode::Byte,
1135 }
1136 }
1137
1138 /// If the client supports reading data. This is enabled by default.
1139 pub fn read(&mut self, allowed: bool) -> &mut Self {
1140 self.options.read(allowed);
1141 self
1142 }
1143
1144 /// If the created pipe supports writing data. This is enabled by default.
1145 pub fn write(&mut self, allowed: bool) -> &mut Self {
1146 self.options.write(allowed);
1147 self
1148 }
1149
1150 /// Sets qos flags which are combined with other flags and attributes in the
1151 /// call to [`CreateFile`].
1152 ///
1153 /// When `security_qos_flags` is not set, a malicious program can gain the
1154 /// elevated privileges of a privileged Rust process when it allows opening
1155 /// user-specified paths, by tricking it into opening a named pipe. So
1156 /// arguably `security_qos_flags` should also be set when opening arbitrary
1157 /// paths. However the bits can then conflict with other flags, specifically
1158 /// `FILE_FLAG_OPEN_NO_RECALL`.
1159 ///
1160 /// For information about possible values, see [Impersonation Levels] on the
1161 /// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set
1162 /// automatically when using this method.
1163 ///
1164 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
1165 /// [`SECURITY_IDENTIFICATION`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/Storage/FileSystem/constant.SECURITY_IDENTIFICATION.html
1166 /// [Impersonation Levels]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
1167 pub fn security_qos_flags(&mut self, flags: u32) -> &mut Self {
1168 self.options.security_qos_flags(flags);
1169 self
1170 }
1171
1172 /// The pipe mode.
1173 ///
1174 /// The default pipe mode is [`PipeMode::Byte`]. See [`PipeMode`] for
1175 /// documentation of what each mode means.
1176 pub fn pipe_mode(&mut self, pipe_mode: PipeMode) -> &mut Self {
1177 self.pipe_mode = pipe_mode;
1178 self
1179 }
1180
1181 /// Opens the named pipe identified by `addr`.
1182 ///
1183 /// This opens the client using [`CreateFile`] with the
1184 /// `dwCreationDisposition` option set to `OPEN_EXISTING`.
1185 ///
1186 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
1187 ///
1188 /// # Errors
1189 ///
1190 /// There are a few errors you need to take into account when creating a
1191 /// named pipe on the client side:
1192 ///
1193 /// * [`std::io::ErrorKind::NotFound`] - This indicates that the named pipe
1194 /// does not exist. Presumably the server is not up.
1195 /// * [`ERROR_PIPE_BUSY`] - This error is raised when the named pipe exists,
1196 /// but the server is not currently waiting for a connection. Please see
1197 /// the examples for how to check for this error.
1198 ///
1199 /// [`ERROR_PIPE_BUSY`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/Foundation/constant.ERROR_PIPE_BUSY.html
1200 ///
1201 /// A connect loop that waits until a pipe becomes available looks like
1202 /// this:
1203 ///
1204 /// ```no_run
1205 /// use std::time::Duration;
1206 ///
1207 /// use compio_fs::named_pipe::ClientOptions;
1208 /// use compio_runtime::time;
1209 /// use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
1210 ///
1211 /// const PIPE_NAME: &str = r"\\.\pipe\mynamedpipe";
1212 ///
1213 /// # compio_runtime::Runtime::new().unwrap().block_on(async move {
1214 /// let client = loop {
1215 /// match ClientOptions::new().open(PIPE_NAME).await {
1216 /// Ok(client) => break client,
1217 /// Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
1218 /// Err(e) => return Err(e),
1219 /// }
1220 ///
1221 /// time::sleep(Duration::from_millis(50)).await;
1222 /// };
1223 ///
1224 /// // use the connected client.
1225 /// # Ok(()) });
1226 /// ```
1227 pub async fn open(&self, addr: impl AsRef<OsStr>) -> io::Result<NamedPipeClient> {
1228 use windows_sys::Win32::System::Pipes::SetNamedPipeHandleState;
1229
1230 let file = self.options.open(addr.as_ref()).await?;
1231
1232 if matches!(self.pipe_mode, PipeMode::Message) {
1233 let mode = PIPE_READMODE_MESSAGE;
1234 syscall!(
1235 BOOL,
1236 SetNamedPipeHandleState(file.as_raw_fd() as _, &mode, null(), null())
1237 )?;
1238 }
1239
1240 Ok(NamedPipeClient { handle: file })
1241 }
1242}
1243
1244impl Default for ClientOptions {
1245 fn default() -> Self {
1246 Self::new()
1247 }
1248}
1249
1250/// The pipe mode of a named pipe.
1251///
1252/// Set through [`ServerOptions::pipe_mode`].
1253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1254pub enum PipeMode {
1255 /// Data is written to the pipe as a stream of bytes. The pipe does not
1256 /// distinguish bytes written during different write operations.
1257 ///
1258 /// Corresponds to [`PIPE_TYPE_BYTE`].
1259 ///
1260 /// [`PIPE_TYPE_BYTE`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Pipes/constant.PIPE_TYPE_BYTE.html
1261 Byte,
1262 /// Data is written to the pipe as a stream of messages. The pipe treats the
1263 /// bytes written during each write operation as a message unit. Any reading
1264 /// on a named pipe returns [`ERROR_MORE_DATA`] when a message is not read
1265 /// completely.
1266 ///
1267 /// Corresponds to [`PIPE_TYPE_MESSAGE`].
1268 ///
1269 /// [`ERROR_MORE_DATA`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/Foundation/constant.ERROR_MORE_DATA.html
1270 /// [`PIPE_TYPE_MESSAGE`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Pipes/constant.PIPE_TYPE_MESSAGE.html
1271 Message,
1272}
1273
1274/// Indicates the end of a named pipe.
1275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1276pub enum PipeEnd {
1277 /// The named pipe refers to the client end of a named pipe instance.
1278 ///
1279 /// Corresponds to [`PIPE_CLIENT_END`].
1280 ///
1281 /// [`PIPE_CLIENT_END`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Pipes/constant.PIPE_CLIENT_END.html
1282 Client,
1283 /// The named pipe refers to the server end of a named pipe instance.
1284 ///
1285 /// Corresponds to [`PIPE_SERVER_END`].
1286 ///
1287 /// [`PIPE_SERVER_END`]: https://docs.rs/windows-sys/latest/windows_sys/Win32/System/Pipes/constant.PIPE_SERVER_END.html
1288 Server,
1289}
1290
1291/// Information about a named pipe.
1292///
1293/// Constructed through [`NamedPipeServer::info`] or [`NamedPipeClient::info`].
1294#[derive(Debug)]
1295pub struct PipeInfo {
1296 /// Indicates the mode of a named pipe.
1297 pub mode: PipeMode,
1298 /// Indicates the end of a named pipe.
1299 pub end: PipeEnd,
1300 /// The maximum number of instances that can be created for this pipe.
1301 pub max_instances: u32,
1302 /// The number of bytes to reserve for the output buffer.
1303 pub out_buffer_size: u32,
1304 /// The number of bytes to reserve for the input buffer.
1305 pub in_buffer_size: u32,
1306}
1307
1308/// Internal function to get the info out of a raw named pipe.
1309unsafe fn named_pipe_info(handle: RawFd) -> io::Result<PipeInfo> {
1310 let mut flags = 0;
1311 let mut out_buffer_size = 0;
1312 let mut in_buffer_size = 0;
1313 let mut max_instances = 0;
1314
1315 syscall!(
1316 BOOL,
1317 GetNamedPipeInfo(
1318 handle as _,
1319 &mut flags,
1320 &mut out_buffer_size,
1321 &mut in_buffer_size,
1322 &mut max_instances,
1323 )
1324 )?;
1325
1326 let mut end = PipeEnd::Client;
1327 let mut mode = PipeMode::Byte;
1328
1329 if flags & PIPE_SERVER_END != 0 {
1330 end = PipeEnd::Server;
1331 }
1332
1333 if flags & PIPE_TYPE_MESSAGE != 0 {
1334 mode = PipeMode::Message;
1335 }
1336
1337 Ok(PipeInfo {
1338 end,
1339 mode,
1340 out_buffer_size,
1341 in_buffer_size,
1342 max_instances,
1343 })
1344}