Skip to main content

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}