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}