├── .gitignore ├── Cargo.toml ├── tests └── duplex.rs ├── src ├── static_pipe.rs ├── handle.rs ├── tests.rs ├── pipe_unix.rs ├── lib.rs └── pipe_windows.rs ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | todo.md -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ipipe" 3 | version = "0.11.7" 4 | license = "MIT OR Apache-2.0" 5 | readme = "README.md" 6 | authors = ["Griffin O'Neill "] 7 | edition = "2018" 8 | description = """ 9 | Cross-platform named-pipe API. 10 | """ 11 | keywords = ["pipe", "ipc", "fifo"] 12 | homepage = "https://github.com/Eolu/ipipe" 13 | repository = "https://github.com/Eolu/ipipe" 14 | categories = ["filesystem", "os::unix-apis", "os::windows-apis", "api-bindings"] 15 | documentation = "https://docs.rs/ipipe" 16 | 17 | [features] 18 | default = ["static_pipe", "rand"] 19 | static_pipe = ["lazy_static", "flurry"] 20 | channels = [] 21 | tokio_channels = ["tokio"] 22 | 23 | [dependencies] 24 | rand = { version = "0.5", optional = true } 25 | lazy_static = { version = "1.4", optional = true } 26 | flurry = { version = "0.3.1", optional = true } 27 | tokio = { version = "1.14", optional = true, features = ["rt", "sync"] } 28 | 29 | [target.'cfg(unix)'.dependencies] 30 | nix = { version = "0.24.1", default-features = false, features = ["fs", "term"] } 31 | 32 | [target.'cfg(windows)'.dependencies] 33 | winapi = { version = "0.3", features = ["winerror", "winbase", "handleapi", "fileapi", "namedpipeapi"]} 34 | windows = { version = "0.33", features = ["Win32_Security", "Win32_Foundation"]} 35 | -------------------------------------------------------------------------------- /tests/duplex.rs: -------------------------------------------------------------------------------- 1 | use ipipe::Pipe; 2 | use std::io::stdout; 3 | use std::io::BufRead; 4 | use std::io::BufReader; 5 | use std::thread; 6 | 7 | use std::io::Write; 8 | 9 | fn print_line(buf_reader: &mut BufReader) 10 | { 11 | let mut buf = vec![]; 12 | buf_reader.read_until(b'\n', &mut buf).unwrap(); 13 | print!("{}", String::from_utf8(buf).unwrap()); 14 | stdout().flush().unwrap(); 15 | } 16 | 17 | fn client_server1(mut pipe: Pipe) 18 | { 19 | writeln!(pipe, "test1").unwrap(); 20 | writeln!(pipe, "test2").unwrap(); 21 | writeln!(pipe, "test3").unwrap(); 22 | let mut buf_reader = BufReader::new(pipe); 23 | print_line(&mut buf_reader); 24 | print_line(&mut buf_reader); 25 | print_line(&mut buf_reader); 26 | } 27 | 28 | fn client_server2(pipe: Pipe) 29 | { 30 | let mut buf_reader = BufReader::new(pipe); 31 | print_line(&mut buf_reader); 32 | print_line(&mut buf_reader); 33 | print_line(&mut buf_reader); 34 | let mut pipe = buf_reader.into_inner(); 35 | writeln!(pipe, "test4").unwrap(); 36 | writeln!(pipe, "test5").unwrap(); 37 | writeln!(pipe, "test6").unwrap(); 38 | } 39 | 40 | #[test] 41 | fn duplex_test() 42 | { 43 | let pipe = Pipe::with_name("test2").unwrap(); 44 | let pipe_clone = pipe.clone(); 45 | let t1 = thread::spawn(|| client_server1(pipe)); 46 | let t2 = thread::spawn(|| client_server2(pipe_clone)); 47 | t1.join().unwrap(); 48 | t2.join().unwrap(); 49 | } 50 | -------------------------------------------------------------------------------- /src/static_pipe.rs: -------------------------------------------------------------------------------- 1 | use crate::Pipe; 2 | use lazy_static::lazy_static; 3 | use std::{io::Write, sync::Mutex}; 4 | use flurry::*; 5 | 6 | // FIXME: The inconsistent use of mutex should be cleaned up here 7 | 8 | // TODO: Accept non-stringly-typed keys somehow 9 | lazy_static! 10 | { 11 | static ref PIPES: HashMap> = HashMap::new(); 12 | } 13 | 14 | /// Print a string to a static pipe 15 | #[macro_export] 16 | macro_rules! pprint 17 | { 18 | ($name:tt, $($arg:tt)*) => ($crate::print($name, format!($($arg)*).as_str())); 19 | } 20 | 21 | /// Print a string and a trailing newline to a static pipe 22 | #[macro_export] 23 | macro_rules! pprintln 24 | { 25 | ($name:tt) => ($crate::print($name, "\n")); 26 | ($name:tt, $($arg:tt)*) => ($crate::print($name, {let mut s = format!($($arg)*); s.push('\n'); s}.as_str())) 27 | } 28 | 29 | /// Initialize a static pipe and return a handle to it. 30 | pub fn init(name: &str) -> crate::Result 31 | { 32 | let pipe = Pipe::with_name(name)?; 33 | let reader = pipe.clone(); 34 | PIPES.insert(name.to_string(), Mutex::from(pipe), &PIPES.guard()); 35 | Ok(reader) 36 | } 37 | 38 | /// Get a handle to an existing static pipe 39 | pub fn get(name: &str) -> Option 40 | { 41 | PIPES.get(name, &PIPES.guard()).map(|pipe| pipe.lock().unwrap().clone()) 42 | } 43 | 44 | /// Closes a static pipe 45 | pub fn close(name: &str) 46 | { 47 | PIPES.remove(name, &PIPES.guard()); 48 | } 49 | 50 | /// Closes all static pipes 51 | pub fn close_all() 52 | { 53 | PIPES.clear(&PIPES.guard()) 54 | } 55 | 56 | /// The lowest-level static-pipe print function. Panics if pipe is not 57 | /// initialized. 58 | #[inline] 59 | pub fn print(name: &str, s: &str) -> crate::Result 60 | { 61 | match PIPES.get(name, &PIPES.guard()) 62 | { 63 | None => Err(crate::Error::Ipipe("Pipe not initialized")), 64 | Some(pipe) => 65 | { 66 | let mut pipe = pipe.lock()?; 67 | match pipe.write(s.as_bytes()) 68 | { 69 | Ok(written) => Ok(written), 70 | Err(e) => Err(crate::Error::from(e)) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/handle.rs: -------------------------------------------------------------------------------- 1 | //! This module contains a wrapper for the raw device handle. 2 | 3 | use std::sync::{Arc, Weak}; 4 | use crate::HandleType; 5 | 6 | #[cfg(unix)] 7 | type RawHandle = std::os::unix::io::RawFd; 8 | #[cfg(windows)] 9 | type RawHandle = winapi::um::winnt::HANDLE; 10 | 11 | #[derive(Debug)] 12 | pub(crate) enum Handle 13 | { 14 | Arc(Arc, HandleType), 15 | Weak(Weak, HandleType) 16 | } 17 | 18 | // TODO: Determine if this is reasonable 19 | unsafe impl Sync for Handle {} 20 | unsafe impl Send for Handle {} 21 | 22 | impl Clone for Handle 23 | { 24 | fn clone(&self) -> Self 25 | { 26 | match self 27 | { 28 | Self::Arc(arc, ty) => Self::Weak(Arc::downgrade(arc), *ty), 29 | Self::Weak(weak, ty) => Self::Weak(weak.clone(), *ty) 30 | } 31 | } 32 | } 33 | 34 | impl Handle 35 | { 36 | pub fn raw(&self) -> Option 37 | { 38 | match self 39 | { 40 | Handle::Arc(arc, _) => Some(**arc), 41 | Handle::Weak(weak, _) => weak.upgrade().map(|arc| *arc), 42 | } 43 | } 44 | 45 | pub fn handle_type(&self) -> HandleType 46 | { 47 | match self 48 | { 49 | Handle::Arc(_, inner_ty) | Handle::Weak(_, inner_ty) => *inner_ty, 50 | } 51 | } 52 | 53 | pub fn set_type(&mut self, ty: HandleType) 54 | { 55 | match self 56 | { 57 | Handle::Arc(_, inner_ty) | Handle::Weak(_, inner_ty) => *inner_ty = ty, 58 | } 59 | } 60 | } 61 | 62 | impl PartialEq for Handle 63 | { 64 | fn eq(&self, other: &Self) -> bool 65 | { 66 | match self 67 | { 68 | Self::Arc(arc, ty) => 69 | { 70 | match other 71 | { 72 | Handle::Arc(arc2, ty2) => ty == ty2 && arc == arc2, 73 | Handle::Weak(weak2, ty2) => ty == ty2 && weak2.upgrade().filter(|arc2| arc == arc2).is_some(), 74 | } 75 | }, 76 | Self::Weak(weak, ty) => 77 | { 78 | match other 79 | { 80 | Handle::Arc(arc2, ty2) => ty == ty2 && weak.upgrade().filter(|arc| arc == arc2).is_some(), 81 | Handle::Weak(weak2, ty2) => ty == ty2 && weak.upgrade().map(|arc| weak2.upgrade().filter(|arc2| arc == *arc2)).is_some(), 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 0.11.3 2 | - Fixed a permissions issue with Windows pipes. 3 | - Documented some non-obvious behavior regarding pipe cloning. 4 | 5 | # Version 0.10.0 6 | - Internally to the Pipe struct, raw handles are now wrapped in Arc. Cloning a Pipe results in a weak reference rather than a naive copy of the raw handle. This allows for all clones to be properly invalidated when a handle is closed. Note that this does NOT contain any internal Mutex. 7 | - Minor breaking change - `Pipe::close()` now takes `self` instead of `&mut self`, ensuring that references to closed pipes can't sit around. 8 | 9 | # Version 0.9.0 10 | - Added the `tokio_channels` feature, which provides exactly the same functionality as channels using tokio's async API instead of std threads. 11 | 12 | # Version 0.8.2 13 | - No longer delete autocreated pipes on Unix by default. This creates more consistent behavior between unix and Windows. 14 | 15 | # Version 0.8 16 | - Added the `channels` feature. 17 | 18 | # Version 0.7.5 19 | - Removed a rogue print statement when closing a static pipe. 20 | 21 | # Version 0.7.4 22 | - Windows pipes will now attempt to recover from a disconnected client. 23 | 24 | # Version 0.7.3 25 | - Fixed a possible deadlock scenario by using a lock-free hashmap. 26 | - Added a close function that can be called explicitly. Pipes are not longer implicitly closed on drop. 27 | 28 | # Version 0.7.2 29 | - Unix version will now create pipes if they don't exist when calling `open`. This is to be more consistent with the Windows implementation. 30 | 31 | # Version 0.7.1 32 | - Fixed a double flush 33 | 34 | # Version 0.7.0 35 | - Finally got reads working correctly for windows and linux pipes. The solution is complex so more testing is stil needed. 36 | - Static pipe print macros now return results. It's better than panicking. 37 | 38 | # Version 0.6.1 39 | - Fixed bug that prevented writers from being initialized before readers on Windows. 40 | - General stability improvements. 41 | 42 | # Version 0.6 43 | - Removed dependence on the `windows_named_pipe` crate. Everything is done through winapi on the windows side now. Will likely keep nix on the Linux side as it's sufficiently low-level for any potential purposes of this crate. 44 | - Fixed a bug where compilation failed if the `rand` feature was disabled. 45 | 46 | # Version 0.5 47 | - Removed the `Pipe::close()` method. The drop trait now does all the work that once did. 48 | - Renamed some internal interfaces to be more clear. 49 | - Added the `Pipe::name()` method 50 | - `rand` is now optional (default) feature, as its only purpose is a single method that generates a pipe with a random name. 51 | - Documentation updates. 52 | 53 | # Version 0.4.2 54 | - Windows paths now use raw strings. 55 | - Documentation cleanup. 56 | 57 | # Version 0.4.1 58 | - Made static_pipe a default feature. 59 | 60 | # Version 0.4 61 | - Replaced all non-idiomatic I/O interfaces with standard traits (Read, Write, etc). Interfaces should be much more stable now. Static pipes are the exception here, more changes will likely come down the road. 62 | - Slightly better testing and documentation. 63 | 64 | # Version 0.3.2 65 | - Fixed a bug with static pipes that prevented their use entirely. 66 | 67 | # Version 0.3 68 | - API improvements, better tests, bug fixes, documentation fixes. 69 | 70 | # Version 0.2 71 | - Implemented static pipes. 72 | 73 | # Version 0.1.1 74 | - Added a Pipe::with_name function that allows for better platform independance. 75 | - Added more documentation. 76 | 77 | # Version 0.1 78 | - Initial commit. Lacks features, and likely contains significant bugs. More testing is needed. -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::Pipe; 2 | use std::{io::{Read, Write}, thread}; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | #[test] 6 | fn test_pipe() -> crate::Result<()> 7 | { 8 | fn write_nums(pipe: &mut Pipe, max: i32) -> crate::Result 9 | { 10 | let mut written = 0; 11 | for i in 1..=max 12 | { 13 | written += pipe.write(&format!("{}\n", i).as_bytes())?; 14 | } 15 | written += pipe.write(&['X' as u8])?; 16 | Ok(written) 17 | } 18 | let mut pipe = Pipe::create()?; 19 | println!("Pipe path: {}", pipe.path().display()); 20 | let writer = Arc::new(Mutex::from(pipe.clone())); 21 | let thread_writer = writer.clone(); 22 | 23 | let thread = thread::spawn(move || write_nums(&mut thread_writer.lock().as_mut().unwrap(), 10)); 24 | 25 | let result = read_until_x(&mut pipe).unwrap(); 26 | print!("{}", result); 27 | assert_eq!("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", result); 28 | println!("Bytes sent through the pipe: {:?}", thread.join().unwrap()); 29 | 30 | let thread_writer = writer.clone(); 31 | let thread = thread::spawn(move || write_nums(&mut thread_writer.lock().as_mut().unwrap(), 3)); 32 | 33 | let result = read_until_x(&mut pipe).unwrap(); 34 | print!("{}", result); 35 | assert_eq!("1\n2\n3\n", result); 36 | println!("Bytes sent through the pipe: {:?}", thread.join().unwrap()); 37 | 38 | Ok(()) 39 | } 40 | 41 | 42 | #[test] 43 | fn test_pipe_2() -> crate::Result<()> 44 | { 45 | use std::io::{BufRead, BufWriter}; 46 | let pipe = Pipe::create()?; 47 | let mut writer = BufWriter::new(pipe.clone()); 48 | thread::spawn(move || -> std::io::Result<()> 49 | { 50 | for i in 1..5 51 | { 52 | writeln!(&mut writer, "This is line #{}", i)?; 53 | } 54 | Ok(()) 55 | }); 56 | for (i, line) in std::io::BufReader::new(pipe).lines().enumerate() 57 | { 58 | let line = line?; 59 | println!("{}", line); 60 | assert_eq!(format!("This is line #{}", i + 1), line); 61 | if i == 3 62 | { 63 | break; 64 | } 65 | } 66 | Ok(()) 67 | } 68 | 69 | #[cfg(feature="static_pipe")] 70 | #[test] 71 | fn test_static() -> Result<(), Box> 72 | { 73 | const X: char = 'X'; 74 | use crate::static_pipe; 75 | 76 | let mut reader = static_pipe::init("test_pipe")?; 77 | let thread = thread::spawn(move || read_until_x(&mut reader).unwrap()); 78 | 79 | thread::sleep(std::time::Duration::from_millis(100)); 80 | 81 | pprint!("test_pipe", "This came ")?; 82 | pprintln!("test_pipe", "through the pipe.")?; 83 | pprintln!("test_pipe", "{}", X)?; 84 | let result = thread.join().unwrap(); 85 | println!("String sent through the pipe: {:?}", result); 86 | assert_eq!("This came through the pipe.\n", result); 87 | static_pipe::close("test_pipe"); 88 | 89 | Ok(()) 90 | } 91 | 92 | #[cfg(feature="static_pipe")] 93 | #[test] 94 | fn test_write_first() -> Result<(), Box> 95 | { 96 | const X: char = 'X'; 97 | use crate::static_pipe; 98 | 99 | let mut reader = static_pipe::init("test_pipe2")?; 100 | thread::spawn(move || 101 | { 102 | pprintln!("test_pipe2", "This came through the pipe.").unwrap(); 103 | pprintln!("test_pipe2", "{}", X).unwrap(); 104 | }); 105 | 106 | let result = read_until_x(&mut reader)?; 107 | println!("String sent through the pipe: {:?}", result); 108 | assert_eq!("This came through the pipe.\n", result); 109 | static_pipe::close("test_pipe2"); 110 | 111 | Ok(()) 112 | } 113 | 114 | fn read_until_x(pipe: &mut Pipe) -> std::io::Result 115 | { 116 | let mut buf: [u8; 1] = [0]; 117 | let mut container = String::new(); 118 | loop 119 | { 120 | match pipe.read(&mut buf) 121 | { 122 | Ok(_) if buf[0] != 'X' as u8 => container.push(buf[0] as char), 123 | Ok(_) => { break Ok(container); } 124 | Err(e) => { break Err(e); } 125 | } 126 | } 127 | } 128 | 129 | #[test] 130 | fn test_name() 131 | { 132 | let pipe = Pipe::with_name("test_name").unwrap(); 133 | assert_eq!(pipe.name().unwrap(), "test_name"); 134 | } 135 | 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT IS DEAD 2 | 3 | I haven't had time to work on this in years and what is there is not usable. 4 | 5 | ### This project is in peril! 6 | 7 | The right solution is to create (or find) unique and complete crates that wrap the Windows and the Unix functionality separately, and then to turn this into (one or more) crates that leverage those into specific opinionated, cross-platform implementations. I just don't have the time to prioritize working on this at the moment, so I'm looking for someone interested in adopting this project. I have a reasonably good idea of what needs to happen at both a high and a low-level, and would at least be willing to spend the time to transfer the knowledge I have. Otherwise, unless something changes for me and I find myself with a chunk more free time, this is at risk of becoming a failed project. Contact me through github, reddit `/u/eolu`, or discord `Eolu#3431` if interested. 8 | 9 | # ipipe - A cross-platform named-pipe library for Rust 10 | 11 | This library allows the creation of platform-independant named pipes. Standard Read/Write traits are implemented. APIs and performance will be improved in future versions. Issues and PRs welcome. 12 | 13 | Example: 14 | ```rust 15 | 16 | use ipipe::Pipe; 17 | use std::thread; 18 | use std::io::{BufRead, BufWriter}; 19 | 20 | const CANCEL: u8 = 24; 21 | 22 | fn main() 23 | { 24 | let mut pipe = Pipe::create().unwrap(); 25 | println!("Name: {}", pipe.path().display()); 26 | 27 | let writer = pipe.clone(); 28 | thread::spawn(move || print_nums(writer)); 29 | for line in BufReader::new(pipe).lines() 30 | { 31 | println!("{}", line.unwrap()); 32 | } 33 | } 34 | 35 | fn print_nums(mut pipe: Pipe) 36 | { 37 | for i in 1..=10 38 | { 39 | writeln!(&mut pipe, "{}", i).unwrap(); 40 | } 41 | write!(&mut pipe, "{}", CANCEL as char).unwrap(); 42 | } 43 | ``` 44 | 45 | Running the above example program will output: 46 | ``` 47 | 1 48 | 2 49 | 3 50 | 4 51 | 5 52 | 6 53 | 7 54 | 8 55 | 9 56 | 10 57 | ``` 58 | 59 | `Pipe::create` generates a random pipe name in a temporary location. 60 | Example path (Windows): 61 | `\\.\pipe\pipe_23676_xMvclVhNKcg6iGf` 62 | Example path (Unix): 63 | `/tmp/pipe_1230_mFP8dx8uVl` 64 | 65 | `Pipe::with_name` allows a pipe name to be specified. 66 | 67 | # A note on reading/writing 68 | 69 | To read or write to the same pipe in multiple places, `Pipe::clone` should be used to share the pipe. Pipe instances contain an internal `Arc` which will maintain the raw handle to the pipe until the last instance is dropped. Creating 2 separate handles to the same pipe is currently undefined behavior. This is an issue currently under investigation. 70 | 71 | # Features 72 | - ### static_pipe 73 | The `static_pipe` default feature allows the creation of mutex-protected static pipes that can be written to from anywhere in a way that mimics stdout. Here's an example: 74 | 75 | ```rust 76 | use ipipe::*; 77 | use std::io::{BufRead, BufWriter}; 78 | 79 | let mut reader = ipipe::init("my_out").unwrap(); 80 | 81 | // You can get a handle to an already-initialized pipe like this: 82 | // let mut reader = ipipe::get("my_pipe"); 83 | let s = BufReader::new(pipe).lines().next().unwrap(); 84 | println!("String received: {}", s); 85 | 86 | // Drops the static pipe. Can also call `ipipe::close_all()` to drop all static pipes. 87 | ipipe::close("my_out"); 88 | ``` 89 | Then anywhere your program (or another program with enough permission to access the pipe) can write code like this: 90 | 91 | ```rust 92 | pprintln!("my_pipe", "This text will be sent over the pipe!"); 93 | ``` 94 | 95 | Lower level as well as more complete/intuitive APIs to the static pipes are also planned for a future release. 96 | 97 | - ### rand 98 | The `rand` default feature will allow calling `Pipe::create()` to open a pipe with a randomly-generated name. The generated name will have the following format: `pipe_[process pid]_[15 random alphnumeric characters]`. Equivalent to `Pipe::with_name(&str)` in every other way. 99 | 100 | - ### channels 101 | The `channels` feature will allow calling `pipe.receiver()` and `pipe.sender()` to generate a channel. One end of the channel will be sent to a thread to watch either input or output from the pipe, and the other end of the channel will be returned. 102 | 103 | - ### tokio_channels 104 | Equivalent to the `channels` feature, but uses `tokio::task` in place of `std::thread`. 105 | -------------------------------------------------------------------------------- /src/pipe_unix.rs: -------------------------------------------------------------------------------- 1 | use super::{Result, Error, OnCleanup, Handle}; 2 | use std::path::{Path, PathBuf}; 3 | use std::sync::{Arc, Weak}; 4 | use fcntl::OFlag; 5 | use nix::{fcntl, unistd}; 6 | use nix::sys::stat::{stat, Mode, SFlag}; 7 | use nix::errno::Errno; 8 | use nix::sys::termios::{tcflush, FlushArg}; 9 | 10 | #[cfg(feature="rand")] 11 | use rand::{thread_rng, Rng, distributions::Alphanumeric}; 12 | 13 | /// Abstraction over a named pipe 14 | pub struct Pipe 15 | { 16 | handle1: Handle, 17 | handle2: Option, 18 | pub(super) path: PathBuf, 19 | pub(super) is_slave: bool, 20 | delete: OnCleanup 21 | } 22 | 23 | impl Pipe 24 | { 25 | /// Open or create a pipe. If on_cleanup is set to 'DeleteOnDrop' the named 26 | /// pipe will be deleted when the returned struct is deallocated. 27 | /// Note that this function is not platform-agnostic as unix pipe paths and 28 | /// Windows pipe paths are formatted differnetly. 29 | pub fn open(path: &Path, on_cleanup: OnCleanup) -> Result 30 | { 31 | let mode = Mode::S_IWUSR | Mode::S_IRUSR 32 | | Mode::S_IRGRP | Mode::S_IWGRP; 33 | 34 | if let Some(_) = path.parent() 35 | { 36 | match stat(path) 37 | { 38 | Ok(file_stat) => 39 | { 40 | // Error out if file is not a named pipe 41 | if file_stat.st_mode & SFlag::S_IFIFO.bits() == 0 42 | { 43 | Err(Error::InvalidPath)?; 44 | } 45 | }, 46 | Err(Errno::ENOENT) => 47 | { 48 | unistd::mkfifo(path, mode)?; 49 | }, 50 | err => 51 | { 52 | err?; 53 | } 54 | } 55 | 56 | Pipe::init_handle(path) 57 | .map(|handle| Pipe 58 | { 59 | handle1: handle, 60 | handle2: None, 61 | path: path.to_path_buf(), 62 | is_slave: false, 63 | delete: on_cleanup 64 | }) 65 | } 66 | else 67 | { 68 | Err(Error::InvalidPath) 69 | } 70 | } 71 | 72 | /// Open or create a pipe with the given name. Note that this is just a 73 | /// string name, not a path. 74 | pub fn with_name(name: &str) -> Result 75 | { 76 | let path = PathBuf::from(format!("/tmp/{}", name)); 77 | Pipe::open(&path, OnCleanup::NoDelete) 78 | } 79 | 80 | /// Create a pipe with a randomly generated name in a tempory directory. 81 | #[cfg(feature="rand")] 82 | pub fn create() -> Result 83 | { 84 | // Generate a random path name 85 | let path = PathBuf::from(format!("/tmp/pipe_{}_{}", std::process::id(), thread_rng() 86 | .sample_iter(&Alphanumeric) 87 | .take(10) 88 | .collect::())); 89 | 90 | Pipe::open(&path, OnCleanup::NoDelete) 91 | } 92 | 93 | /// Close a named pipe 94 | pub fn close(self) -> Result<()> 95 | { 96 | if let Some(raw) = self.handle1.raw() 97 | { 98 | unistd::close(raw).map_err(Error::from) 99 | } 100 | else 101 | { 102 | Ok(()) 103 | } 104 | } 105 | 106 | fn init_handle(path: &Path) -> Result 107 | { 108 | let mode = Mode::S_IWUSR | Mode::S_IRUSR 109 | | Mode::S_IRGRP | Mode::S_IWGRP; 110 | 111 | if let Some(_) = path.parent() 112 | { 113 | match stat(path) 114 | { 115 | Ok(file_stat) => 116 | { 117 | // Error out if file is not a named pipe 118 | if file_stat.st_mode & SFlag::S_IFIFO.bits() == 0 119 | { 120 | Err(nix::Error::ENOENT)?; 121 | } 122 | }, 123 | err => 124 | { 125 | err?; 126 | } 127 | } 128 | 129 | fcntl::open(path, OFlag::O_RDWR | OFlag::O_NOCTTY, mode) 130 | .map(|handle| Handle::Arc(Arc::new(handle), HandleType::Unknown)) 131 | .map_err(Error::from) 132 | } 133 | else 134 | { 135 | Err(Error::InvalidPath) 136 | } 137 | } 138 | 139 | fn init_handle_type(&mut self, handle_type: HandleType) -> Result 140 | { 141 | if self.handle1.handle_type() == HandleType::Unknown 142 | { 143 | self.handle1.set_type(handle_type); 144 | } 145 | if self.handle1.handle_type() == handle_type 146 | { 147 | self.handle1.raw() 148 | } 149 | else 150 | { 151 | if let None = self.handle2 152 | { 153 | let mut handle = Pipe::init_handle(&self.path)?; 154 | handle.set_type(handle_type); 155 | self.handle2 = Some(handle); 156 | } 157 | self.handle2.as_ref().unwrap().raw() 158 | }.ok_or(nix::errno::Errno::EBADF).map_err(|e| e.into()) 159 | } 160 | } 161 | 162 | impl std::io::Write for Pipe 163 | { 164 | fn write(&mut self, bytes: &[u8]) -> std::io::Result 165 | { 166 | let handle = self.init_handle_type(HandleType::Write)?; 167 | unistd::write(handle, bytes).map_err(Error::from).map_err(std::io::Error::from) 168 | } 169 | 170 | fn flush(&mut self) -> std::io::Result<()> 171 | { 172 | let handle = self.init_handle_type(HandleType::Write)?; 173 | tcflush(handle, FlushArg::TCOFLUSH).map_err(Error::from).map_err(std::io::Error::from) 174 | } 175 | } 176 | 177 | impl std::io::Read for Pipe 178 | { 179 | fn read(&mut self, bytes: &mut [u8]) -> std::io::Result 180 | { 181 | let handle = self.init_handle_type(HandleType::Read)?; 182 | unistd::read(handle, bytes) 183 | .map_err(Error::from) 184 | .map_err(std::io::Error::from) 185 | } 186 | } 187 | 188 | impl Drop for Pipe 189 | { 190 | fn drop(&mut self) 191 | { 192 | if !self.is_slave 193 | { 194 | self.handle1 = Handle::Weak(Weak::new(), HandleType::Unknown); 195 | self.handle2 = None; 196 | if let OnCleanup::Delete = self.delete 197 | { 198 | std::fs::remove_file(&self.path).unwrap(); 199 | } 200 | } 201 | } 202 | } 203 | 204 | impl Clone for Pipe 205 | { 206 | /// Cloning a pipe creates a slave which points to the same path but does not 207 | /// close the pipe when dropped. 208 | fn clone(&self) -> Self 209 | { 210 | Pipe 211 | { 212 | handle1: self.handle1.clone(), 213 | handle2: self.handle2.clone(), 214 | path: self.path.clone(), 215 | is_slave: true, 216 | delete: OnCleanup::NoDelete 217 | } 218 | } 219 | } 220 | 221 | #[derive(Debug, PartialEq, Clone, Copy)] 222 | pub(crate) enum HandleType 223 | { 224 | Read, Write, Unknown 225 | } 226 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cross-platform named-pipe API. 2 | //! 3 | //! # Quick Start 4 | //! 5 | //! To get started quickly, try using Pipe::with_name to create a pipe with a 6 | //! given name. 7 | //! ``` 8 | //! use ipipe::Pipe; 9 | //! use std::io::BufRead; 10 | //! fn reader() 11 | //! { 12 | //! let mut pipe = Pipe::with_name("test_pipe").unwrap(); 13 | //! println!("Pipe path: {}", pipe.path().display()); 14 | //! 15 | //! // Read lines 16 | //! for line in std::io::BufReader::new(pipe).lines() 17 | //! { 18 | //! println!("{}", line.unwrap()); 19 | //! } 20 | //! } 21 | //! ``` 22 | //! 23 | //! Then in another program or thread: 24 | //! ``` 25 | //! use ipipe::Pipe; 26 | //! use std::io::Write; 27 | //! fn writer() 28 | //! { 29 | //! let mut pipe = Pipe::with_name("test_pipe").unwrap(); 30 | //! writeln!(&mut pipe, "This is only a test.").unwrap(); 31 | //! } 32 | //! ``` 33 | //! You can also use `Pipe::create` to open a pipe with a randomly-generated 34 | //! name, which can then be accessed by calling Pipe::path. 35 | //! 36 | //! Lastly, Pipe::open can be used to specify an exact path. This is not 37 | //! platform agnostic, however, as Windows pipe paths require a special 38 | //! format. 39 | //! 40 | //! Calling `clone()` on a pipe will create a pipe who's handle exists as a Weak 41 | //! reference to the original pipe. That means dropping the original pipe will 42 | //! also close all of its clones. If a clone is in the middle of a read or write 43 | //! when the drop of the original pipe happens, the pipe will not be closed 44 | //! until that read or write is complete. 45 | 46 | #[cfg(unix)] 47 | mod pipe_unix; 48 | #[cfg(unix)] 49 | pub use pipe_unix::*; 50 | 51 | #[cfg(windows)] 52 | mod pipe_windows; 53 | #[cfg(windows)] 54 | pub use pipe_windows::*; 55 | 56 | #[cfg(feature="static_pipe")] 57 | #[macro_use] 58 | mod static_pipe; 59 | #[cfg(feature="static_pipe")] 60 | pub use static_pipe::*; 61 | 62 | #[cfg(test)] 63 | mod tests; 64 | mod handle; 65 | pub(crate) use handle::*; 66 | 67 | #[cfg(all(feature="channels", not(feature="tokio_channels")))] 68 | use std::sync::mpsc; 69 | 70 | #[cfg(all(feature="tokio_channels", not(feature="channels")))] 71 | use tokio::sync::mpsc; 72 | 73 | #[derive(Clone, Copy)] 74 | pub enum OnCleanup 75 | { 76 | Delete, 77 | NoDelete 78 | } 79 | 80 | impl Pipe 81 | { 82 | /// Return the path to this named pipe 83 | pub fn path(&self) -> &std::path::Path 84 | { 85 | &self.path 86 | } 87 | 88 | /// Gets the name of this pipe 89 | pub fn name(&self) -> Option<&std::ffi::OsStr> 90 | { 91 | self.path().file_name() 92 | } 93 | 94 | /// Creates a receiver which all output from this pipe is directed into. A 95 | /// thread is spawned to read from the pipe, which will shutdown when the 96 | /// receiver is dropped. Note that the thread blocks, and may attempt to read 97 | /// from the pipe one time after the receiver is dropped. 98 | #[cfg(all(feature="channels", not(feature="tokio_channels")))] 99 | pub fn receiver(mut self) -> (mpsc::Receiver, std::thread::JoinHandle<()>) 100 | { 101 | use std::io::Read; 102 | let (tx, rx) = mpsc::channel(); 103 | (rx, 104 | std::thread::spawn(move || 105 | { 106 | loop 107 | { 108 | for byte in (&mut self).bytes() 109 | { 110 | tx.send(byte.unwrap()).unwrap() 111 | } 112 | } 113 | })) 114 | } 115 | 116 | /// Creates a receiver which all output from this pipe is directed into. A 117 | /// task is spawned to read from the pipe, which will shutdown when the 118 | /// receiver is dropped. Note that the task blocks, and may attempt to read 119 | /// from the pipe one time after the receiver is dropped. 120 | #[cfg(all(feature="tokio_channels", not(feature="channels")))] 121 | pub async fn receiver(mut self) -> (mpsc::UnboundedReceiver, tokio::task::JoinHandle<()>) 122 | { 123 | use std::io::Read; 124 | let (tx, rx) = mpsc::unbounded_channel(); 125 | (rx, 126 | tokio::task::spawn(async move 127 | { 128 | loop 129 | { 130 | for byte in (&mut self).bytes() 131 | { 132 | tx.send(byte.unwrap()).unwrap() 133 | } 134 | } 135 | })) 136 | } 137 | 138 | /// Creates a sender which outputs all input into this pipe. A 139 | /// thread is spawned to write into the pipe, which will shutdown when the 140 | /// sender is dropped. 141 | #[cfg(all(feature="channels", not(feature="tokio_channels")))] 142 | pub fn sender(mut self) -> (mpsc::Sender, std::thread::JoinHandle<()>) 143 | { 144 | use std::io::Write; 145 | let (tx, rx) = mpsc::channel(); 146 | (tx, 147 | std::thread::spawn(move || 148 | { 149 | loop 150 | { 151 | (&mut self).write(&[rx.recv().unwrap()]).unwrap(); 152 | } 153 | })) 154 | } 155 | 156 | /// Creates a sender which outputs all input into this pipe. A 157 | /// thread is spawned to write into the pipe, which will shutdown when the 158 | /// sender is dropped. 159 | #[cfg(all(feature="tokio_channels", not(feature="channels")))] 160 | pub fn sender(mut self) -> (mpsc::UnboundedSender, tokio::task::JoinHandle<()>) 161 | { 162 | use std::io::Write; 163 | let (tx, mut rx) = mpsc::unbounded_channel(); 164 | (tx, 165 | tokio::task::spawn(async move 166 | { 167 | loop 168 | { 169 | (&mut self).write(&[rx.recv().await.unwrap()]).unwrap(); 170 | } 171 | })) 172 | } 173 | } 174 | 175 | /// Standard error type used by this library 176 | #[derive(Debug)] 177 | pub enum Error 178 | { 179 | Ipipe(&'static str), 180 | InvalidPath, 181 | InvalidUtf8, 182 | Io(std::io::Error), 183 | Native(&'static str, u32, String), 184 | Misc(String) 185 | } 186 | 187 | pub type Result = std::result::Result; 188 | 189 | impl std::fmt::Display for Error 190 | { 191 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result 192 | { 193 | match self 194 | { 195 | Error::Ipipe(s) => s.fmt(f), 196 | Error::InvalidPath => write!(f, "Invalid path"), 197 | Error::InvalidUtf8 => write!(f, "Invalid Utf8"), 198 | Error::Io(err) => err.fmt(f), 199 | Error::Native(text, code, oss) => write!(f, "{}: {} - {}", text, code, oss), 200 | Error::Misc(s) => s.fmt(f), 201 | } 202 | } 203 | } 204 | impl std::error::Error for Error{} 205 | 206 | impl From for std::io::Error 207 | { 208 | fn from(err: Error) -> std::io::Error 209 | { 210 | match err 211 | { 212 | Error::Io(err) => err, 213 | e => std::io::Error::new(std::io::ErrorKind::Other, e) 214 | } 215 | } 216 | } 217 | 218 | impl From for Error 219 | { 220 | fn from(err: std::io::Error) -> Error 221 | { 222 | Error::Io(err) 223 | } 224 | } 225 | 226 | impl<'a> From>> for Error 227 | { 228 | fn from(err: std::sync::PoisonError>) -> Error 229 | { 230 | Error::Misc(err.to_string()) 231 | } 232 | } 233 | 234 | impl From for Error 235 | { 236 | fn from(_: std::string::FromUtf8Error) -> Error 237 | { 238 | Error::InvalidUtf8 239 | } 240 | } 241 | 242 | #[cfg(unix)] 243 | impl From for Error 244 | { 245 | fn from(error: nix::Error) -> Error 246 | { 247 | Error::Native("", error as u32, error.desc().to_string()) 248 | } 249 | } 250 | 251 | impl From for Error 252 | { 253 | fn from(error: std::ffi::NulError) -> Error 254 | { 255 | Error::Native("Interior null character found", error.nul_position() as u32, format!("{}", error)) 256 | } 257 | } 258 | 259 | -------------------------------------------------------------------------------- /src/pipe_windows.rs: -------------------------------------------------------------------------------- 1 | use super::{Result, OnCleanup, Handle}; 2 | use std::path::Path; 3 | use std::io::{self, Read, Write}; 4 | use std::os::windows::prelude::*; 5 | use std::ffi::OsString; 6 | use std::sync::Arc; 7 | use winapi:: 8 | { 9 | um::winbase::*, 10 | um::fileapi::*, 11 | um::handleapi::*, 12 | um::namedpipeapi::*, 13 | um::winnt::{GENERIC_READ, GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL}, 14 | shared::winerror::{ERROR_PIPE_NOT_CONNECTED, ERROR_NO_DATA}, 15 | shared::minwindef::{DWORD, LPCVOID, LPVOID} 16 | }; 17 | 18 | #[cfg(feature="rand")] 19 | use rand::{thread_rng, Rng, distributions::Alphanumeric}; 20 | 21 | /// Abstraction over a named pipe 22 | #[derive(Debug, Clone)] 23 | pub struct Pipe 24 | { 25 | handle: Option, 26 | pub(super) path: std::path::PathBuf, 27 | } 28 | 29 | impl Pipe 30 | { 31 | /// Open a pipe at an existing path. Note that this function is not 32 | /// platform-agnostic as unix pipe paths and Windows pipe paths are are 33 | /// formatted differently. The second parameter is unused on Windows. 34 | pub fn open(path: &Path, _: OnCleanup) -> Result 35 | { 36 | Ok(Pipe 37 | { 38 | handle: None, 39 | path: path.to_path_buf() 40 | }) 41 | } 42 | 43 | /// Open a pipe with the given name. Note that this is just a string name, 44 | /// not a path. 45 | pub fn with_name(name: &str) -> Result 46 | { 47 | let path_string = format!(r"\\.\pipe\{}", name); 48 | Pipe::open(&Path::new(&path_string), OnCleanup::Delete) 49 | } 50 | 51 | /// Open a pipe with a randomly generated name. 52 | #[cfg(feature="rand")] 53 | pub fn create() -> Result 54 | { 55 | // Generate a random path name 56 | let path_string = format!(r"\\.\pipe\pipe_{}_{}", std::process::id(),thread_rng() 57 | .sample_iter(&Alphanumeric) 58 | .take(15) 59 | .collect::()); 60 | 61 | Pipe::open(&Path::new(&path_string), OnCleanup::Delete) 62 | } 63 | 64 | /// Close a named pipe 65 | pub fn close(self) -> Result<()> 66 | { 67 | if let Some(mut handle) = self.handle 68 | { 69 | if handle.handle_type() == HandleType::Server 70 | { 71 | if let Some(raw) = handle.raw() 72 | { 73 | unsafe 74 | { 75 | if DisconnectNamedPipe(raw) == 0 76 | { 77 | Err(io::Error::last_os_error())?; 78 | } 79 | } 80 | } 81 | // Server handles are disconnected when dropped, while client 82 | // handles are not. This line prevents a double-disconnect. 83 | handle.set_type(HandleType::Client); 84 | } 85 | } 86 | Ok(()) 87 | } 88 | 89 | /// Creates a new pipe handle 90 | fn create_pipe(path: &Path) -> io::Result 91 | { 92 | let mut os_str: OsString = path.as_os_str().into(); 93 | os_str.push("\x00"); 94 | let u16_slice = os_str.encode_wide().collect::>(); 95 | 96 | unsafe 97 | { 98 | while WaitNamedPipeW(u16_slice.as_ptr(), 0xffffffff) == 0 99 | { 100 | let error = io::Error::last_os_error(); 101 | match error.raw_os_error() 102 | { 103 | None => { break; } 104 | Some(2) => {} 105 | Some(_) => Err(error)? 106 | } 107 | } 108 | } 109 | let handle = unsafe 110 | { 111 | CreateFileW(u16_slice.as_ptr(), 112 | GENERIC_READ | GENERIC_WRITE, 113 | 0, 114 | std::ptr::null_mut(), 115 | OPEN_EXISTING, 116 | FILE_ATTRIBUTE_NORMAL, 117 | std::ptr::null_mut()) 118 | }; 119 | 120 | if handle != INVALID_HANDLE_VALUE 121 | { 122 | Ok(Handle::Arc(Arc::new(handle), HandleType::Client)) 123 | } 124 | else 125 | { 126 | Err(io::Error::last_os_error()) 127 | } 128 | } 129 | 130 | /// Creates a pipe listener 131 | fn create_listener(path: &Path, first: bool) -> io::Result 132 | { 133 | let mut os_str: OsString = path.as_os_str().into(); 134 | os_str.push("\x00"); 135 | let u16_slice = os_str.encode_wide().collect::>(); 136 | let access_flags = if first 137 | { 138 | PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE 139 | } 140 | else 141 | { 142 | PIPE_ACCESS_DUPLEX 143 | }; 144 | let handle = unsafe 145 | { 146 | CreateNamedPipeW(u16_slice.as_ptr(), 147 | access_flags, 148 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 149 | PIPE_UNLIMITED_INSTANCES, 150 | 65536, 151 | 65536, 152 | 50, 153 | std::ptr::null_mut()) 154 | }; 155 | 156 | if handle != INVALID_HANDLE_VALUE 157 | { 158 | Ok(Handle::Arc(Arc::new(handle), HandleType::Server)) 159 | } 160 | else 161 | { 162 | Err(io::Error::last_os_error()) 163 | } 164 | } 165 | 166 | /// Initializes the pipe for writing 167 | fn init_writer(&mut self) -> Result<()> 168 | { 169 | if self.handle.is_none() 170 | { 171 | self.handle = Some(Pipe::create_pipe(&self.path)?); 172 | } 173 | Ok(()) 174 | } 175 | } 176 | 177 | impl std::io::Write for Pipe 178 | { 179 | fn write(&mut self, bytes: &[u8]) -> std::io::Result 180 | { 181 | self.init_writer()?; 182 | let result = match &mut self.handle 183 | { 184 | None => unreachable!(), 185 | Some(handle) => handle.write(bytes) 186 | }; 187 | 188 | // Try again if pipe is closed 189 | match result 190 | { 191 | Ok(r) => {return Ok(r);} 192 | Err(e) if e.raw_os_error().unwrap() as u32 == ERROR_NO_DATA => 193 | { 194 | self.handle = None; 195 | self.init_writer()?; 196 | match &mut self.handle 197 | { 198 | None => unreachable!(), 199 | Some(handle) => handle.write(bytes) 200 | } 201 | } 202 | Err(e) => { Err(e)? } 203 | } 204 | } 205 | 206 | fn flush(&mut self) -> std::io::Result<()> 207 | { 208 | match &mut self.handle 209 | { 210 | None => self.init_writer().map_err(std::io::Error::from), 211 | Some(handle) => 212 | { 213 | handle.flush()?; 214 | self.handle = None; 215 | Ok(()) 216 | } 217 | } 218 | } 219 | } 220 | 221 | impl std::io::Read for Pipe 222 | { 223 | fn read(&mut self, bytes: &mut [u8]) -> std::io::Result 224 | { 225 | loop 226 | { 227 | let handle = match &mut self.handle 228 | { 229 | None => 230 | { 231 | let listener = Pipe::create_listener(&self.path, true)?; 232 | // Unwrap is safe because handle was just created 233 | if unsafe { ConnectNamedPipe(listener.raw().unwrap(), std::ptr::null_mut()) } == 0 234 | { 235 | match io::Error::last_os_error().raw_os_error().map(|x| x as u32) 236 | { 237 | Some(ERROR_PIPE_NOT_CONNECTED) => {}, 238 | Some(err) => Err(io::Error::from_raw_os_error(err as i32))?, 239 | _ => unreachable!(), 240 | } 241 | } 242 | self.handle = Some(listener); 243 | self.handle.as_mut().unwrap() 244 | } 245 | Some(read_handle) => 246 | { 247 | if let None = read_handle.raw() 248 | { 249 | let listener = Pipe::create_listener(&self.path, false)?; 250 | // Unwrap is safe because handle was just created 251 | if unsafe { ConnectNamedPipe(listener.raw().unwrap(), std::ptr::null_mut()) } == 0 252 | { 253 | match io::Error::last_os_error().raw_os_error().map(|x| x as u32) 254 | { 255 | Some(ERROR_PIPE_NOT_CONNECTED) => {}, 256 | Some(err) => Err(io::Error::from_raw_os_error(err as i32))?, 257 | _ => unreachable!(), 258 | } 259 | } 260 | self.handle = Some(listener); 261 | self.handle.as_mut().unwrap() 262 | } 263 | else 264 | { 265 | read_handle 266 | } 267 | } 268 | }; 269 | 270 | match handle.read(bytes) 271 | { 272 | Err(e) => 273 | { 274 | if let Some(err) = e.raw_os_error() 275 | { 276 | if err as u32 != 109 277 | { 278 | Err(std::io::Error::from(e))?; 279 | } 280 | else 281 | { 282 | continue; 283 | } 284 | } 285 | else 286 | { 287 | break Ok(0); 288 | } 289 | }, 290 | bytes_read => { break bytes_read; } 291 | } 292 | } 293 | } 294 | } 295 | 296 | impl Read for Handle 297 | { 298 | fn read(&mut self, buf: &mut [u8]) -> io::Result 299 | { 300 | if let Some(raw) = self.raw() 301 | { 302 | let mut bytes_read = 0; 303 | let ok = unsafe 304 | { 305 | ReadFile(raw, 306 | buf.as_mut_ptr() as LPVOID, 307 | buf.len() as DWORD, 308 | &mut bytes_read, 309 | std::ptr::null_mut()) 310 | }; 311 | 312 | if ok != 0 313 | { 314 | Ok(bytes_read as usize) 315 | } 316 | else 317 | { 318 | match io::Error::last_os_error().raw_os_error().map(|x| x as u32) 319 | { 320 | Some(ERROR_PIPE_NOT_CONNECTED) => Ok(0), 321 | Some(err) => Err(io::Error::from_raw_os_error(err as i32)), 322 | _ => unreachable!(), 323 | } 324 | } 325 | } 326 | else 327 | { 328 | Ok(0) 329 | } 330 | } 331 | } 332 | 333 | impl Write for Handle 334 | { 335 | fn write(&mut self, buf: &[u8]) -> io::Result 336 | { 337 | if let Some(raw) = self.raw() 338 | { 339 | let mut bytes_written = 0; 340 | let status = unsafe 341 | { 342 | WriteFile(raw, 343 | buf.as_ptr() as LPCVOID, 344 | buf.len() as DWORD, 345 | &mut bytes_written, 346 | std::ptr::null_mut()) 347 | }; 348 | 349 | if status != 0 350 | { 351 | Ok(bytes_written as usize) 352 | } 353 | else 354 | { 355 | Err(io::Error::last_os_error()) 356 | } 357 | } 358 | else 359 | { 360 | Err(io::Error::from_raw_os_error(ERROR_PIPE_NOT_CONNECTED as i32)) 361 | } 362 | } 363 | 364 | fn flush(&mut self) -> io::Result<()> 365 | { 366 | if let Some(raw) = self.raw() 367 | { 368 | if unsafe { FlushFileBuffers(raw) } != 0 369 | { 370 | Ok(()) 371 | } 372 | else 373 | { 374 | Err(io::Error::last_os_error()) 375 | } 376 | } 377 | else 378 | { 379 | Err(io::Error::from_raw_os_error(ERROR_PIPE_NOT_CONNECTED as i32)) 380 | } 381 | } 382 | } 383 | 384 | #[derive(Debug, PartialEq, Clone, Copy)] 385 | pub(crate) enum HandleType 386 | { 387 | Server, Client 388 | } 389 | 390 | impl Drop for Handle 391 | { 392 | fn drop(&mut self) 393 | { 394 | if let Self::Arc(arc, ty) = self 395 | { 396 | let deref = **arc; 397 | unsafe { FlushFileBuffers(deref); } 398 | if *ty == HandleType::Server 399 | { 400 | unsafe { DisconnectNamedPipe(deref); } 401 | } 402 | unsafe { CloseHandle(deref); } 403 | } 404 | } 405 | } 406 | 407 | --------------------------------------------------------------------------------