├── .github └── workflows │ ├── clippy.yml │ ├── rustfmt.yml │ └── tests.yml ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── examples ├── proc.rs └── simple.rs └── src ├── bootstrap.rs ├── lib.rs ├── raw_channel.rs ├── serde.rs └── typed_channel.rs /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run clippy 13 | run: make lint 14 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run rustfmt 13 | run: make style 14 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Test 13 | run: make test 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unix-ipc" 3 | version = "0.2.2" 4 | authors = ["Armin Ronacher "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A minimal abstraction for IPC via unix sockets." 8 | homepage = "https://github.com/mitsuhiko/unix-ipc" 9 | repository = "https://github.com/mitsuhiko/unix-ipc" 10 | keywords = ["ipc", "unix-socket", "subprocess"] 11 | readme = "README.md" 12 | autoexamples = true 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | 17 | [features] 18 | default = ["serde", "bootstrap", "bootstrap-simple"] 19 | serde = ["serde_", "bincode"] 20 | bootstrap = ["serde"] 21 | bootstrap-simple = ["bootstrap", "rand"] 22 | 23 | [dependencies] 24 | libc = "0.2.67" 25 | nix = "0.23.0" 26 | serde_ = { package = "serde", version = "1.0.104", features = ["derive"], optional = true } 27 | bincode = { version = "1.2.1", optional = true } 28 | rand = { version = "0.8.0", optional = true } 29 | 30 | [[example]] 31 | name = "proc" 32 | required-features = ["serde", "bootstrap", "bootstrap-simple"] 33 | 34 | [[example]] 35 | name = "simple" 36 | required-features = ["serde"] 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test style lint 2 | .PHONY: all 3 | 4 | clean: 5 | @cargo clean 6 | .PHONY: clean 7 | 8 | build: 9 | @cargo build 10 | .PHONY: build 11 | 12 | doc: 13 | @cargo doc --all-features 14 | .PHONY: doc 15 | 16 | style: 17 | @rustup component add rustfmt --toolchain stable 2> /dev/null 18 | cargo +stable fmt -- --check 19 | .PHONY: style 20 | 21 | format: 22 | @rustup component add rustfmt --toolchain stable 2> /dev/null 23 | cargo +stable fmt 24 | .PHONY: format 25 | 26 | lint: 27 | @rustup component add clippy --toolchain stable 2> /dev/null 28 | cargo +stable clippy --all-features --tests --all --examples -- -D clippy::all 29 | .PHONY: lint 30 | 31 | test: testall 32 | .PHONY: test 33 | 34 | checkall: 35 | cargo check --all-features 36 | cargo check --no-default-features 37 | .PHONY: checkall 38 | 39 | testall: 40 | cargo test --all-features 41 | cargo test --no-default-features --tests 42 | .PHONY: testall 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unix-ipc 2 | 3 | This crate implements a minimal abstraction over UNIX domain sockets for 4 | the purpose of IPC. It lets you send both file handles and rust objects 5 | between processes. 6 | 7 | ## How it works 8 | 9 | This uses [serde](https://serde.rs/) to serialize data over unix sockets 10 | via [bincode](https://github.com/servo/bincode). Thanks to the 11 | [`Handle`](https://docs.rs/unix-ipc/latest/unix_ipc/struct.Handle.html) 12 | abstraction you can also send any object across that is convertable into a unix 13 | file handle. 14 | 15 | The way this works under the hood is that during serialization and 16 | deserialization encountered file descriptors are tracked. They are then 17 | sent over the unix socket separately. This lets unassociated processes 18 | share file handles. 19 | 20 | If you only want the unix socket abstraction you can disable all default 21 | features and use the raw channels. 22 | 23 | ## Example 24 | 25 | ```rust 26 | use std::env; 27 | use std::process; 28 | use unix_ipc::{channel, Bootstrapper, Receiver, Sender}; 29 | use serde::{Deserialize, Serialize}; 30 | 31 | const ENV_VAR: &str = "PROC_CONNECT_TO"; 32 | 33 | #[derive(Serialize, Deserialize, Debug)] 34 | pub enum Task { 35 | Sum(Vec, Sender), 36 | Shutdown, 37 | } 38 | 39 | if let Ok(path) = env::var(ENV_VAR) { 40 | let receiver = Receiver::::connect(path).unwrap(); 41 | loop { 42 | match receiver.recv().unwrap() { 43 | Task::Sum(values, tx) => { 44 | tx.send(values.into_iter().sum::()).unwrap(); 45 | } 46 | Task::Shutdown => break, 47 | } 48 | } 49 | } else { 50 | let bootstrapper = Bootstrapper::new().unwrap(); 51 | let mut child = process::Command::new(env::current_exe().unwrap()) 52 | .env(ENV_VAR, bootstrapper.path()) 53 | .spawn() 54 | .unwrap(); 55 | 56 | let (tx, rx) = channel().unwrap(); 57 | bootstrapper.send(Task::Sum(vec![23, 42], tx)).unwrap(); 58 | println!("sum: {}", rx.recv().unwrap()); 59 | bootstrapper.send(Task::Shutdown).unwrap(); 60 | } 61 | ``` 62 | 63 | License: MIT/Apache-2.0 64 | -------------------------------------------------------------------------------- /examples/proc.rs: -------------------------------------------------------------------------------- 1 | use serde_::{Deserialize, Serialize}; 2 | use std::env; 3 | use std::process; 4 | use unix_ipc::{channel, Bootstrapper, Receiver, Sender}; 5 | 6 | const ENV_VAR: &str = "PROC_CONNECT_TO"; 7 | 8 | #[derive(Serialize, Deserialize, Debug)] 9 | #[serde(crate = "serde_")] 10 | pub enum Task { 11 | Sum(Vec, Sender), 12 | Shutdown, 13 | } 14 | 15 | fn main() { 16 | if let Ok(path) = env::var(ENV_VAR) { 17 | let receiver = Receiver::::connect(path).unwrap(); 18 | loop { 19 | let task = receiver.recv().unwrap(); 20 | match dbg!(task) { 21 | Task::Sum(values, tx) => { 22 | tx.send(values.into_iter().sum::()).unwrap(); 23 | } 24 | Task::Shutdown => break, 25 | } 26 | } 27 | } else { 28 | let bootstrapper = Bootstrapper::new().unwrap(); 29 | let mut child = process::Command::new(env::current_exe().unwrap()) 30 | .env(ENV_VAR, bootstrapper.path()) 31 | .spawn() 32 | .unwrap(); 33 | 34 | let (tx, rx) = channel().unwrap(); 35 | bootstrapper.send(Task::Sum(vec![23, 42], tx)).unwrap(); 36 | println!("result: {}", rx.recv().unwrap()); 37 | 38 | let (tx, rx) = channel().unwrap(); 39 | bootstrapper.send(Task::Sum((0..10).collect(), tx)).unwrap(); 40 | println!("result: {}", rx.recv().unwrap()); 41 | 42 | bootstrapper.send(Task::Shutdown).unwrap(); 43 | 44 | child.kill().ok(); 45 | child.wait().ok(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use serde_::{Deserialize, Serialize}; 2 | use unix_ipc::{channel, Bootstrapper, Receiver, Sender}; 3 | 4 | #[derive(Serialize, Deserialize, Debug)] 5 | #[serde(crate = "serde_")] 6 | pub enum Task { 7 | Sum(Vec, Sender), 8 | Shutdown, 9 | } 10 | 11 | fn main() { 12 | let bootstrapper = Bootstrapper::new().unwrap(); 13 | let path = bootstrapper.path().to_owned(); 14 | 15 | std::thread::spawn(move || { 16 | let receiver = Receiver::::connect(path).unwrap(); 17 | loop { 18 | let task = receiver.recv().unwrap(); 19 | match task { 20 | Task::Sum(values, tx) => { 21 | tx.send(values.into_iter().sum::()).unwrap(); 22 | } 23 | Task::Shutdown => break, 24 | } 25 | } 26 | }); 27 | 28 | println!("make channel 1"); 29 | let (tx, rx) = channel().unwrap(); 30 | bootstrapper.send(Task::Sum(vec![23, 42], tx)).unwrap(); 31 | println!("result: {}", rx.recv().unwrap()); 32 | 33 | println!("make channel 2"); 34 | let (tx, rx) = channel().unwrap(); 35 | bootstrapper.send(Task::Sum(vec![1, 2, 3], tx)).unwrap(); 36 | println!("result: {}", rx.recv().unwrap()); 37 | 38 | bootstrapper.send(Task::Shutdown).unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /src/bootstrap.rs: -------------------------------------------------------------------------------- 1 | use serde_::de::DeserializeOwned; 2 | use serde_::Serialize; 3 | use std::cell::RefCell; 4 | use std::fs; 5 | use std::io; 6 | use std::os::unix::io::{FromRawFd, IntoRawFd}; 7 | use std::os::unix::net::UnixListener; 8 | use std::path::{Path, PathBuf}; 9 | 10 | use crate::typed_channel::Sender; 11 | 12 | /// A bootstrap helper. 13 | /// 14 | /// This creates a unix socket that is linked to the file system so 15 | /// that a [`Receiver`](struct.Receiver.html) can connect to it. It 16 | /// lets you send one or more messages to the connected receiver. 17 | #[derive(Debug)] 18 | pub struct Bootstrapper { 19 | listener: UnixListener, 20 | sender: RefCell>>, 21 | path: PathBuf, 22 | } 23 | 24 | impl Bootstrapper { 25 | /// Creates a bootstrapper at a random socket in `/tmp`. 26 | #[cfg(feature = "bootstrap-simple")] 27 | pub fn new() -> io::Result> { 28 | use rand::{thread_rng, RngCore}; 29 | use std::time::{SystemTime, UNIX_EPOCH}; 30 | 31 | let mut dir = std::env::temp_dir(); 32 | let mut rng = thread_rng(); 33 | let now = SystemTime::now(); 34 | dir.push(&format!( 35 | ".rust-unix-ipc.{}-{}.sock", 36 | now.duration_since(UNIX_EPOCH).unwrap().as_secs(), 37 | rng.next_u64(), 38 | )); 39 | Bootstrapper::bind(&dir) 40 | } 41 | 42 | /// Creates a bootstrapper at a specific socket path. 43 | pub fn bind>(p: P) -> io::Result> { 44 | fs::remove_file(&p).ok(); 45 | let listener = UnixListener::bind(&p)?; 46 | Ok(Bootstrapper { 47 | listener, 48 | sender: RefCell::new(None), 49 | path: p.as_ref().to_path_buf(), 50 | }) 51 | } 52 | 53 | /// Returns the path of the socket. 54 | pub fn path(&self) -> &Path { 55 | &self.path 56 | } 57 | 58 | /// Consumes the boostrapper and sends a single value in. 59 | /// 60 | /// This can be called multiple times to send more than one value 61 | /// into the inner socket. 62 | pub fn send(&self, val: T) -> io::Result<()> { 63 | if self.sender.borrow().is_none() { 64 | let (sock, _) = self.listener.accept()?; 65 | let sender = unsafe { Sender::from_raw_fd(sock.into_raw_fd()) }; 66 | *self.sender.borrow_mut() = Some(sender); 67 | } 68 | self.sender.borrow().as_ref().unwrap().send(val) 69 | } 70 | } 71 | 72 | impl Drop for Bootstrapper { 73 | fn drop(&mut self) { 74 | fs::remove_file(&self.path).ok(); 75 | } 76 | } 77 | 78 | #[test] 79 | fn test_bootstrap() { 80 | use crate::Receiver; 81 | 82 | let bootstrapper = Bootstrapper::new().unwrap(); 83 | let path = bootstrapper.path().to_owned(); 84 | 85 | let handle = std::thread::spawn(move || { 86 | let receiver = Receiver::::connect(path).unwrap(); 87 | let a = receiver.recv().unwrap(); 88 | let b = receiver.recv().unwrap(); 89 | assert_eq!(a + b, 65); 90 | }); 91 | 92 | bootstrapper.send(42u32).unwrap(); 93 | bootstrapper.send(23u32).unwrap(); 94 | 95 | handle.join().unwrap(); 96 | } 97 | 98 | #[test] 99 | fn test_bootstrap_reverse() { 100 | use crate::{channel, Receiver}; 101 | 102 | let bootstrapper = Bootstrapper::new().unwrap(); 103 | let path = bootstrapper.path().to_owned(); 104 | let (tx, rx) = channel::().unwrap(); 105 | 106 | std::thread::spawn(move || { 107 | let receiver = Receiver::>::connect(path).unwrap(); 108 | let result_sender = receiver.recv().unwrap(); 109 | result_sender.send(42 + 23).unwrap(); 110 | }); 111 | 112 | bootstrapper.send(tx).unwrap(); 113 | assert_eq!(rx.recv().unwrap(), 65); 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements a minimal abstraction over UNIX domain sockets for 2 | //! the purpose of IPC. It lets you send both file handles and rust objects 3 | //! between processes. 4 | //! 5 | //! # How it works 6 | //! 7 | //! This uses [serde](https://serde.rs/) to serialize data over unix sockets 8 | //! via [bincode](https://github.com/servo/bincode). Thanks to the 9 | //! [`Handle`](struct.Handle.html) abstraction you can also send any object 10 | //! across that is convertable into a unix file handle. 11 | //! 12 | //! The way this works under the hood is that during serialization and 13 | //! deserialization encountered file descriptors are tracked. They are then 14 | //! sent over the unix socket separately. This lets unassociated processes 15 | //! share file handles. 16 | //! 17 | //! If you only want the unix socket abstraction you can disable all default 18 | //! features and use the raw channels. 19 | //! 20 | //! # Example 21 | //! 22 | //! ```rust 23 | //! # use ::serde_ as serde; 24 | //! use std::env; 25 | //! use std::process; 26 | //! use unix_ipc::{channel, Bootstrapper, Receiver, Sender}; 27 | //! use serde::{Deserialize, Serialize}; 28 | //! 29 | //! const ENV_VAR: &str = "PROC_CONNECT_TO"; 30 | //! 31 | //! #[derive(Serialize, Deserialize, Debug)] 32 | //! # #[serde(crate = "serde_")] 33 | //! pub enum Task { 34 | //! Sum(Vec, Sender), 35 | //! Shutdown, 36 | //! } 37 | //! 38 | //! if let Ok(path) = env::var(ENV_VAR) { 39 | //! let receiver = Receiver::::connect(path).unwrap(); 40 | //! loop { 41 | //! match receiver.recv().unwrap() { 42 | //! Task::Sum(values, tx) => { 43 | //! tx.send(values.into_iter().sum::()).unwrap(); 44 | //! } 45 | //! Task::Shutdown => break, 46 | //! } 47 | //! } 48 | //! } else { 49 | //! let bootstrapper = Bootstrapper::new().unwrap(); 50 | //! let mut child = process::Command::new(env::current_exe().unwrap()) 51 | //! .env(ENV_VAR, bootstrapper.path()) 52 | //! .spawn() 53 | //! .unwrap(); 54 | //! 55 | //! let (tx, rx) = channel().unwrap(); 56 | //! bootstrapper.send(Task::Sum(vec![23, 42], tx)).unwrap(); 57 | //! println!("sum: {}", rx.recv().unwrap()); 58 | //! bootstrapper.send(Task::Shutdown).unwrap(); 59 | //! } 60 | //! ``` 61 | //! 62 | //! # Feature Flags 63 | //! 64 | //! All features are enabled by default but a lot can be turned off to 65 | //! cut down on dependencies. With all default features enabled only 66 | //! the raw types are available. 67 | //! 68 | //! * `serde`: enables serialization and deserialization. 69 | //! * `bootstrap`: adds the `Bootstrapper` type. 70 | //! * `bootstrap-simple`: adds the default `new` constructor to the 71 | //! bootstrapper. 72 | mod raw_channel; 73 | 74 | #[cfg(feature = "bootstrap")] 75 | mod bootstrap; 76 | #[cfg(feature = "serde")] 77 | mod serde; 78 | #[cfg(feature = "serde")] 79 | mod typed_channel; 80 | 81 | pub use self::raw_channel::*; 82 | 83 | #[cfg(feature = "bootstrap")] 84 | pub use self::bootstrap::*; 85 | 86 | #[cfg(feature = "serde")] 87 | pub use self::{serde::*, typed_channel::*}; 88 | 89 | #[doc(hidden)] 90 | #[cfg(feature = "serde")] 91 | pub use ::serde_ as _serde_ref; 92 | -------------------------------------------------------------------------------- /src/raw_channel.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::mem; 3 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; 4 | use std::os::unix::net::UnixStream; 5 | use std::path::Path; 6 | use std::slice; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | 9 | use nix::sys::socket::{ 10 | c_uint, recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, CMSG_SPACE, 11 | }; 12 | use nix::sys::uio::IoVec; 13 | use nix::unistd; 14 | 15 | #[cfg(target_os = "linux")] 16 | const MSG_FLAGS: MsgFlags = MsgFlags::MSG_CMSG_CLOEXEC; 17 | 18 | #[cfg(target_os = "macos")] 19 | const MSG_FLAGS: MsgFlags = MsgFlags::empty(); 20 | 21 | /// A raw receiver. 22 | #[derive(Debug)] 23 | pub struct RawReceiver { 24 | fd: RawFd, 25 | dead: AtomicBool, 26 | } 27 | 28 | /// A raw sender. 29 | #[derive(Debug)] 30 | pub struct RawSender { 31 | fd: RawFd, 32 | dead: AtomicBool, 33 | } 34 | 35 | /// Creates a raw connected channel. 36 | pub fn raw_channel() -> io::Result<(RawSender, RawReceiver)> { 37 | let (sender, receiver) = UnixStream::pair()?; 38 | unsafe { 39 | Ok(( 40 | RawSender::from_raw_fd(sender.into_raw_fd()), 41 | RawReceiver::from_raw_fd(receiver.into_raw_fd()), 42 | )) 43 | } 44 | } 45 | 46 | #[repr(C)] 47 | #[derive(Default, Debug)] 48 | struct MsgHeader { 49 | payload_len: u32, 50 | fd_count: u32, 51 | } 52 | 53 | macro_rules! fd_impl { 54 | ($ty:ty) => { 55 | #[allow(dead_code)] 56 | impl $ty { 57 | pub(crate) fn extract_raw_fd(&self) -> RawFd { 58 | if self.dead.swap(true, Ordering::SeqCst) { 59 | panic!("handle was moved previously"); 60 | } else { 61 | self.fd 62 | } 63 | } 64 | } 65 | 66 | impl FromRawFd for $ty { 67 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 68 | Self { 69 | fd, 70 | dead: AtomicBool::new(false), 71 | } 72 | } 73 | } 74 | 75 | impl IntoRawFd for $ty { 76 | fn into_raw_fd(self) -> RawFd { 77 | let fd = self.fd; 78 | mem::forget(self); 79 | fd 80 | } 81 | } 82 | 83 | impl AsRawFd for $ty { 84 | fn as_raw_fd(&self) -> RawFd { 85 | self.fd 86 | } 87 | } 88 | 89 | impl Drop for $ty { 90 | fn drop(&mut self) { 91 | unistd::close(self.fd).ok(); 92 | } 93 | } 94 | }; 95 | } 96 | 97 | fd_impl!(RawReceiver); 98 | fd_impl!(RawSender); 99 | 100 | impl RawReceiver { 101 | /// Connects a receiver to a named unix socket. 102 | pub fn connect>(p: P) -> io::Result { 103 | let sock = UnixStream::connect(p)?; 104 | unsafe { Ok(RawReceiver::from_raw_fd(sock.into_raw_fd())) } 105 | } 106 | 107 | pub fn recv(&self) -> io::Result<(Vec, Option>)> { 108 | self.recv_impl(true) 109 | } 110 | 111 | pub fn try_recv(&self) -> io::Result, Option>)>> { 112 | let res = self.recv_impl(false); 113 | 114 | match res { 115 | Ok(res) => Ok(Some(res)), 116 | Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => Ok(None), 117 | Err(err) => Err(err), 118 | } 119 | } 120 | 121 | /// Receives raw bytes from the socket. 122 | fn recv_impl(&self, blocking: bool) -> io::Result<(Vec, Option>)> { 123 | let mut header = MsgHeader::default(); 124 | self.recv_part( 125 | unsafe { 126 | slice::from_raw_parts_mut( 127 | (&mut header as *mut _) as *mut u8, 128 | mem::size_of_val(&header), 129 | ) 130 | }, 131 | 0, 132 | blocking, 133 | )?; 134 | 135 | let mut buf = vec![0u8; header.payload_len as usize]; 136 | // Once the header is received, the body must always follow 137 | let (_, fds) = self.recv_part(&mut buf, header.fd_count as usize, true)?; 138 | Ok((buf, fds)) 139 | } 140 | 141 | fn recv_part( 142 | &self, 143 | buf: &mut [u8], 144 | fd_count: usize, 145 | blocking: bool, 146 | ) -> io::Result<(usize, Option>)> { 147 | let mut pos = 0; 148 | let mut fds = None; 149 | 150 | loop { 151 | let iov = [IoVec::from_mut_slice(&mut buf[pos..])]; 152 | let mut new_fds = None; 153 | let msgspace_size = 154 | unsafe { CMSG_SPACE(mem::size_of::() as c_uint) * fd_count as u32 }; 155 | let mut cmsgspace = vec![0u8; msgspace_size as usize]; 156 | 157 | let flags = if blocking { 158 | MSG_FLAGS 159 | } else { 160 | MSG_FLAGS | MsgFlags::MSG_DONTWAIT 161 | }; 162 | 163 | let msg = recvmsg(self.fd, &iov, Some(&mut cmsgspace), flags)?; 164 | 165 | for cmsg in msg.cmsgs() { 166 | if let ControlMessageOwned::ScmRights(fds) = cmsg { 167 | if !fds.is_empty() { 168 | #[cfg(target_os = "macos")] 169 | unsafe { 170 | for &fd in &fds { 171 | libc::ioctl(fd, libc::FIOCLEX); 172 | } 173 | } 174 | new_fds = Some(fds); 175 | } 176 | } 177 | } 178 | 179 | fds = match (fds, new_fds) { 180 | (None, Some(new)) => Some(new), 181 | (Some(mut old), Some(new)) => { 182 | old.extend(new); 183 | Some(old) 184 | } 185 | (old, None) => old, 186 | }; 187 | 188 | if msg.bytes == 0 { 189 | return Err(io::Error::new( 190 | io::ErrorKind::UnexpectedEof, 191 | "could not read", 192 | )); 193 | } 194 | 195 | pos += msg.bytes; 196 | if pos >= buf.len() { 197 | return Ok((pos, fds)); 198 | } 199 | } 200 | } 201 | } 202 | 203 | impl RawSender { 204 | /// Sends raw bytes and fds. 205 | pub fn send(&self, data: &[u8], fds: &[RawFd]) -> io::Result { 206 | let header = MsgHeader { 207 | payload_len: data.len() as u32, 208 | fd_count: fds.len() as u32, 209 | }; 210 | let header_slice = unsafe { 211 | slice::from_raw_parts( 212 | (&header as *const _) as *const u8, 213 | mem::size_of_val(&header), 214 | ) 215 | }; 216 | 217 | self.send_impl(&header_slice, &[][..])?; 218 | self.send_impl(&data, fds) 219 | } 220 | 221 | fn send_impl(&self, data: &[u8], mut fds: &[RawFd]) -> io::Result { 222 | let mut pos = 0; 223 | loop { 224 | let iov = [IoVec::from_slice(&data[pos..])]; 225 | let sent = if !fds.is_empty() { 226 | sendmsg( 227 | self.fd, 228 | &iov, 229 | &[ControlMessage::ScmRights(fds)], 230 | MsgFlags::empty(), 231 | None, 232 | )? 233 | } else { 234 | sendmsg(self.fd, &iov, &[], MsgFlags::empty(), None)? 235 | }; 236 | if sent == 0 { 237 | return Err(io::Error::new(io::ErrorKind::WriteZero, "could not send")); 238 | } 239 | pos += sent; 240 | fds = &[][..]; 241 | if pos >= data.len() { 242 | return Ok(pos); 243 | } 244 | } 245 | } 246 | } 247 | 248 | #[test] 249 | fn test_basic() { 250 | let (tx, rx) = raw_channel().unwrap(); 251 | 252 | let server = std::thread::spawn(move || { 253 | tx.send(b"Hello World!", &[][..]).unwrap(); 254 | }); 255 | 256 | std::thread::sleep(std::time::Duration::from_millis(10)); 257 | 258 | let client = std::thread::spawn(move || { 259 | let (bytes, fds) = rx.recv().unwrap(); 260 | assert_eq!(bytes, b"Hello World!"); 261 | assert_eq!(fds, None); 262 | }); 263 | 264 | server.join().unwrap(); 265 | client.join().unwrap(); 266 | } 267 | 268 | #[test] 269 | fn test_large_buffer() { 270 | use std::fmt::Write; 271 | 272 | let mut buf = String::new(); 273 | for x in 0..10000 { 274 | write!(&mut buf, "{}", x).ok(); 275 | } 276 | 277 | let (tx, rx) = raw_channel().unwrap(); 278 | 279 | let server_buf = buf.clone(); 280 | let server = std::thread::spawn(move || { 281 | tx.send(server_buf.as_bytes(), &[][..]).unwrap(); 282 | }); 283 | 284 | std::thread::sleep(std::time::Duration::from_millis(10)); 285 | 286 | let client = std::thread::spawn(move || { 287 | let (bytes, fds) = rx.recv().unwrap(); 288 | assert_eq!(bytes, buf.as_bytes()); 289 | assert_eq!(fds, None); 290 | }); 291 | 292 | server.join().unwrap(); 293 | client.join().unwrap(); 294 | } 295 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io; 3 | use std::mem; 4 | use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; 5 | use std::sync::Mutex; 6 | 7 | use serde_::{de, ser}; 8 | use serde_::{de::DeserializeOwned, Deserialize, Serialize}; 9 | 10 | thread_local! { 11 | static IPC_FDS: RefCell>> = RefCell::new(Vec::new()); 12 | } 13 | 14 | /// Can transfer a unix file handle across processes. 15 | /// 16 | /// The basic requirement is that you have an object that can be converted 17 | /// into a raw file handle and back. This for instance is the case for 18 | /// regular file objects, sockets and many more things. 19 | /// 20 | /// Once the handle has been serialized the handle no longer lets you 21 | /// extract the value contained in it. 22 | /// 23 | /// For customizing the serialization of libraries the 24 | /// [`HandleRef`](struct.HandleRef.html) object should be used instead. 25 | pub struct Handle(Mutex>); 26 | 27 | /// A raw reference to a handle. 28 | /// 29 | /// This serializes the same way as a `Handle` but only uses a raw 30 | /// fd to represent it. Useful to implement custom serializers. 31 | pub struct HandleRef(pub RawFd); 32 | 33 | impl Handle { 34 | /// Wraps the value in a handle. 35 | pub fn new(f: F) -> Self { 36 | f.into() 37 | } 38 | 39 | fn extract_raw_fd(&self) -> RawFd { 40 | self.0 41 | .lock() 42 | .unwrap() 43 | .take() 44 | .map(|x| x.into_raw_fd()) 45 | .expect("cannot serialize handle twice") 46 | } 47 | 48 | /// Extracts the internal value. 49 | pub fn into_inner(self) -> F { 50 | self.0.lock().unwrap().take().expect("handle was moved") 51 | } 52 | } 53 | 54 | impl From for Handle { 55 | fn from(f: F) -> Self { 56 | Handle(Mutex::new(Some(f))) 57 | } 58 | } 59 | 60 | impl Serialize for HandleRef { 61 | fn serialize(&self, serializer: S) -> Result 62 | where 63 | S: ser::Serializer, 64 | { 65 | if serde_in_ipc_mode() { 66 | let fd = self.0; 67 | let idx = register_fd(fd); 68 | idx.serialize(serializer) 69 | } else { 70 | Err(ser::Error::custom("can only serialize in ipc mode")) 71 | } 72 | } 73 | } 74 | 75 | impl Serialize for Handle { 76 | fn serialize(&self, serializer: S) -> Result 77 | where 78 | S: ser::Serializer, 79 | { 80 | HandleRef(self.extract_raw_fd()).serialize(serializer) 81 | } 82 | } 83 | 84 | impl<'de, F: FromRawFd + IntoRawFd> Deserialize<'de> for Handle { 85 | fn deserialize(deserializer: D) -> Result, D::Error> 86 | where 87 | D: de::Deserializer<'de>, 88 | { 89 | if serde_in_ipc_mode() { 90 | let idx = u32::deserialize(deserializer)?; 91 | let fd = lookup_fd(idx).ok_or_else(|| de::Error::custom("fd not found in mapping"))?; 92 | unsafe { Ok(Handle(Mutex::new(Some(FromRawFd::from_raw_fd(fd))))) } 93 | } else { 94 | Err(de::Error::custom("can only deserialize in ipc mode")) 95 | } 96 | } 97 | } 98 | 99 | struct ResetIpcSerde; 100 | 101 | impl Drop for ResetIpcSerde { 102 | fn drop(&mut self) { 103 | IPC_FDS.with(|x| x.borrow_mut().pop()); 104 | } 105 | } 106 | 107 | fn enter_ipc_mode R, R>(f: F, fds: &mut Vec) -> R { 108 | IPC_FDS.with(|x| x.borrow_mut().push(fds.clone())); 109 | let reset = ResetIpcSerde; 110 | let rv = f(); 111 | *fds = IPC_FDS.with(|x| x.borrow_mut().pop()).unwrap_or_default(); 112 | mem::forget(reset); 113 | rv 114 | } 115 | 116 | fn register_fd(fd: RawFd) -> u32 { 117 | IPC_FDS.with(|x| { 118 | let mut x = x.borrow_mut(); 119 | let fds = x.last_mut().unwrap(); 120 | let rv = fds.len() as u32; 121 | fds.push(fd); 122 | rv 123 | }) 124 | } 125 | 126 | fn lookup_fd(idx: u32) -> Option { 127 | IPC_FDS.with(|x| x.borrow().last().and_then(|l| l.get(idx as usize).copied())) 128 | } 129 | 130 | /// Checks if serde is in IPC mode. 131 | /// 132 | /// This can be used to customize the behavior of serialization/deserialization 133 | /// implementations for the use with unix-ipc. 134 | pub fn serde_in_ipc_mode() -> bool { 135 | IPC_FDS.with(|x| !x.borrow().is_empty()) 136 | } 137 | 138 | #[allow(clippy::boxed_local)] 139 | fn bincode_to_io_error(err: bincode::Error) -> io::Error { 140 | match *err { 141 | bincode::ErrorKind::Io(err) => err, 142 | err => io::Error::new(io::ErrorKind::Other, err.to_string()), 143 | } 144 | } 145 | 146 | /// Serializes something for IPC communication. 147 | /// 148 | /// This uses bincode for serialization. Because UNIX sockets require that 149 | /// file descriptors are transmitted separately they are accumulated in a 150 | /// separate buffer. 151 | pub fn serialize(s: S) -> io::Result<(Vec, Vec)> { 152 | let mut fds = Vec::new(); 153 | let mut out = Vec::new(); 154 | enter_ipc_mode(|| bincode::serialize_into(&mut out, &s), &mut fds) 155 | .map_err(bincode_to_io_error)?; 156 | Ok((out, fds)) 157 | } 158 | 159 | /// Deserializes something for IPC communication. 160 | /// 161 | /// File descriptors need to be provided for deserialization if handleds are 162 | /// involved. 163 | pub fn deserialize(bytes: &[u8], fds: &[RawFd]) -> io::Result { 164 | let mut fds = fds.to_owned(); 165 | let result = 166 | enter_ipc_mode(|| bincode::deserialize(bytes), &mut fds).map_err(bincode_to_io_error)?; 167 | Ok(result) 168 | } 169 | 170 | macro_rules! implement_handle_serialization { 171 | ($ty:ty) => { 172 | impl $crate::_serde_ref::Serialize for $ty { 173 | fn serialize(&self, serializer: S) -> Result 174 | where 175 | S: $crate::_serde_ref::ser::Serializer, 176 | { 177 | $crate::_serde_ref::Serialize::serialize( 178 | &$crate::HandleRef(self.extract_raw_fd()), 179 | serializer, 180 | ) 181 | } 182 | } 183 | impl<'de> Deserialize<'de> for $ty { 184 | fn deserialize(deserializer: D) -> Result<$ty, D::Error> 185 | where 186 | D: $crate::_serde_ref::de::Deserializer<'de>, 187 | { 188 | let handle: $crate::Handle<$ty> = 189 | $crate::_serde_ref::Deserialize::deserialize(deserializer)?; 190 | Ok(handle.into_inner()) 191 | } 192 | } 193 | }; 194 | } 195 | 196 | implement_handle_serialization!(crate::RawSender); 197 | implement_handle_serialization!(crate::RawReceiver); 198 | 199 | macro_rules! implement_typed_handle_serialization { 200 | ($ty:ty) => { 201 | impl $crate::_serde_ref::Serialize for $ty { 202 | fn serialize(&self, serializer: S) -> Result 203 | where 204 | S: $crate::_serde_ref::ser::Serializer, 205 | { 206 | $crate::_serde_ref::Serialize::serialize( 207 | &$crate::HandleRef(self.extract_raw_fd()), 208 | serializer, 209 | ) 210 | } 211 | } 212 | impl<'de, T: Serialize + DeserializeOwned> Deserialize<'de> for $ty { 213 | fn deserialize(deserializer: D) -> Result<$ty, D::Error> 214 | where 215 | D: $crate::_serde_ref::de::Deserializer<'de>, 216 | { 217 | let handle: $crate::Handle<$ty> = 218 | $crate::_serde_ref::Deserialize::deserialize(deserializer)?; 219 | Ok(handle.into_inner()) 220 | } 221 | } 222 | }; 223 | } 224 | 225 | implement_typed_handle_serialization!(crate::Sender); 226 | implement_typed_handle_serialization!(crate::Receiver); 227 | 228 | #[test] 229 | fn test_basic() { 230 | use std::io::Read; 231 | let f = std::fs::File::open("src/serde.rs").unwrap(); 232 | let handle = Handle::from(f); 233 | let (bytes, fds) = serialize(handle).unwrap(); 234 | let f2: Handle = deserialize(&bytes, &fds).unwrap(); 235 | let mut out = Vec::new(); 236 | f2.into_inner().read_to_end(&mut out).unwrap(); 237 | assert!(out.len() > 100); 238 | } 239 | -------------------------------------------------------------------------------- /src/typed_channel.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; 4 | use std::path::Path; 5 | 6 | use serde_::de::DeserializeOwned; 7 | use serde_::Serialize; 8 | 9 | use crate::raw_channel::{raw_channel, RawReceiver, RawSender}; 10 | use crate::serde::{deserialize, serialize}; 11 | 12 | /// A typed receiver. 13 | pub struct Receiver { 14 | raw_receiver: RawReceiver, 15 | _marker: std::marker::PhantomData, 16 | } 17 | 18 | /// A typed sender. 19 | pub struct Sender { 20 | raw_sender: RawSender, 21 | _marker: std::marker::PhantomData, 22 | } 23 | 24 | impl fmt::Debug for Receiver { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | f.debug_struct("Receiver") 27 | .field("fd", &self.as_raw_fd()) 28 | .finish() 29 | } 30 | } 31 | 32 | impl fmt::Debug for Sender { 33 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 34 | f.debug_struct("Sender") 35 | .field("fd", &self.as_raw_fd()) 36 | .finish() 37 | } 38 | } 39 | 40 | macro_rules! fd_impl { 41 | ($field:ident, $raw_ty:ty, $ty:ty) => { 42 | #[allow(dead_code)] 43 | impl $ty { 44 | pub(crate) fn extract_raw_fd(&self) -> RawFd { 45 | self.$field.extract_raw_fd() 46 | } 47 | } 48 | 49 | impl From<$raw_ty> for $ty { 50 | fn from(value: $raw_ty) -> Self { 51 | Self { 52 | $field: value, 53 | _marker: std::marker::PhantomData, 54 | } 55 | } 56 | } 57 | 58 | impl FromRawFd for $ty { 59 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 60 | Self { 61 | $field: FromRawFd::from_raw_fd(fd), 62 | _marker: std::marker::PhantomData, 63 | } 64 | } 65 | } 66 | 67 | impl IntoRawFd for $ty { 68 | fn into_raw_fd(self) -> RawFd { 69 | self.$field.into_raw_fd() 70 | } 71 | } 72 | 73 | impl AsRawFd for $ty { 74 | fn as_raw_fd(&self) -> RawFd { 75 | self.$field.as_raw_fd() 76 | } 77 | } 78 | }; 79 | } 80 | 81 | fd_impl!(raw_receiver, RawReceiver, Receiver); 82 | fd_impl!(raw_sender, RawSender, Sender); 83 | 84 | /// Creates a typed connected channel. 85 | pub fn channel() -> io::Result<(Sender, Receiver)> { 86 | let (sender, receiver) = raw_channel()?; 87 | Ok((sender.into(), receiver.into())) 88 | } 89 | 90 | impl Receiver { 91 | /// Connects a receiver to a named unix socket. 92 | pub fn connect>(p: P) -> io::Result> { 93 | RawReceiver::connect(p).map(Into::into) 94 | } 95 | 96 | /// Converts the typed receiver into a raw one. 97 | pub fn into_raw_receiver(self) -> RawReceiver { 98 | self.raw_receiver 99 | } 100 | 101 | /// Receives a structured message from the socket if there is a message available. 102 | pub fn try_recv(&self) -> io::Result> { 103 | let res = self.raw_receiver.try_recv()?; 104 | if let Some((buf, fds)) = res { 105 | Ok(Some( 106 | deserialize::<(T, bool)>(&buf, fds.as_deref().unwrap_or_default()).map(|x| x.0)?, 107 | )) 108 | } else { 109 | Ok(None) 110 | } 111 | } 112 | 113 | /// Receives a structured message from the socket. 114 | pub fn recv(&self) -> io::Result { 115 | let (buf, fds) = self.raw_receiver.recv()?; 116 | deserialize::<(T, bool)>(&buf, fds.as_deref().unwrap_or_default()).map(|x| x.0) 117 | } 118 | } 119 | 120 | impl Sender { 121 | /// Converts the typed sender into a raw one. 122 | pub fn into_raw_sender(self) -> RawSender { 123 | self.raw_sender 124 | } 125 | 126 | /// Receives a structured message from the socket. 127 | pub fn send(&self, s: T) -> io::Result<()> { 128 | // we always serialize a dummy bool at the end so that the message 129 | // will not be empty because of zero sized types. 130 | let (payload, fds) = serialize((&s, true))?; 131 | self.raw_sender.send(&payload, &fds)?; 132 | Ok(()) 133 | } 134 | } 135 | 136 | #[test] 137 | fn test_basic() { 138 | use crate::serde::Handle; 139 | use std::io::Read; 140 | 141 | let f = Handle::from(std::fs::File::open("src/serde.rs").unwrap()); 142 | 143 | let (tx, rx) = channel().unwrap(); 144 | 145 | let server = std::thread::spawn(move || { 146 | tx.send(f).unwrap(); 147 | }); 148 | 149 | std::thread::sleep(std::time::Duration::from_millis(10)); 150 | 151 | let client = std::thread::spawn(move || { 152 | let f = rx.recv().unwrap(); 153 | 154 | let mut out = Vec::new(); 155 | f.into_inner().read_to_end(&mut out).unwrap(); 156 | assert!(out.len() > 100); 157 | }); 158 | 159 | server.join().unwrap(); 160 | client.join().unwrap(); 161 | } 162 | 163 | #[test] 164 | fn test_send_channel() { 165 | use crate::serde::Handle; 166 | use std::fs::File; 167 | use std::io::Read; 168 | 169 | let (tx, rx) = channel().unwrap(); 170 | let (sender, receiver) = channel::>().unwrap(); 171 | 172 | let server = std::thread::spawn(move || { 173 | tx.send(sender).unwrap(); 174 | let handle = receiver.recv().unwrap(); 175 | let mut file = handle.into_inner(); 176 | let mut out = Vec::new(); 177 | file.read_to_end(&mut out).unwrap(); 178 | assert!(out.len() > 100); 179 | }); 180 | 181 | std::thread::sleep(std::time::Duration::from_millis(10)); 182 | 183 | let client = std::thread::spawn(move || { 184 | let sender = rx.recv().unwrap(); 185 | sender 186 | .send(Handle::from(File::open("src/serde.rs").unwrap())) 187 | .unwrap(); 188 | }); 189 | 190 | server.join().unwrap(); 191 | client.join().unwrap(); 192 | } 193 | 194 | #[test] 195 | fn test_multiple_fds() { 196 | let (tx1, rx1) = channel().unwrap(); 197 | let (tx2, rx2) = channel::<()>().unwrap(); 198 | let (tx3, rx3) = channel::<()>().unwrap(); 199 | 200 | let a = std::thread::spawn(move || { 201 | tx1.send((tx2, rx2, tx3, rx3)).unwrap(); 202 | }); 203 | 204 | let b = std::thread::spawn(move || { 205 | let _channels = rx1.recv().unwrap(); 206 | }); 207 | 208 | a.join().unwrap(); 209 | b.join().unwrap(); 210 | } 211 | 212 | #[test] 213 | fn test_conversion() { 214 | let (tx, rx) = channel::().unwrap(); 215 | let raw_tx = tx.into_raw_sender(); 216 | let raw_rx = rx.into_raw_receiver(); 217 | let tx = Sender::::from(raw_tx); 218 | let rx = Receiver::::from(raw_rx); 219 | 220 | let a = std::thread::spawn(move || { 221 | tx.send(true).unwrap(); 222 | }); 223 | 224 | let b = std::thread::spawn(move || { 225 | assert_eq!(rx.recv().unwrap(), true); 226 | }); 227 | 228 | a.join().unwrap(); 229 | b.join().unwrap(); 230 | } 231 | 232 | #[test] 233 | fn test_zero_sized_type() { 234 | let (tx, rx) = channel::<()>().unwrap(); 235 | 236 | let a = std::thread::spawn(move || { 237 | tx.send(()).unwrap(); 238 | }); 239 | 240 | let b = std::thread::spawn(move || { 241 | rx.recv().unwrap(); 242 | }); 243 | 244 | a.join().unwrap(); 245 | b.join().unwrap(); 246 | } 247 | 248 | #[test] 249 | fn test_many_nested() { 250 | for _ in 0..2000 { 251 | let (tx, rx) = channel().unwrap(); 252 | let (tx2, rx2) = channel().unwrap(); 253 | 254 | tx.send(tx2).unwrap(); 255 | 256 | let recv = rx.recv().unwrap(); 257 | 258 | recv.send(1).unwrap(); 259 | 260 | rx2.recv().unwrap(); 261 | } 262 | } 263 | 264 | #[test] 265 | fn test_try_recv() { 266 | let (tx, rx) = channel().unwrap(); 267 | 268 | assert!(rx.try_recv().unwrap().is_none()); 269 | 270 | tx.send(1_f32).unwrap(); 271 | 272 | assert!(rx.try_recv().unwrap().is_some()) 273 | } 274 | --------------------------------------------------------------------------------