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