doors/
lib.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 2023 Robert D. French
7 */
8//! A Rust-friendly interface for [illumos Doors][1].
9//!
10//! [Doors][2] are a high-speed, RPC-style interprocess communication facility
11//! for the [illumos][3] operating system. They enable rapid dialogue between
12//! client and server without giving up the CPU timeslice, and are an excellent
13//! alternative to pipes or UNIX domain sockets in situations where IPC latency
14//! matters.
15//!
16//! This crate makes it easier to interact with the Doors API from Rust. It can
17//! help you create clients, define server procedures, and open or create doors
18//! on the filesystem.
19//!
20//! ## Example
21//! ```
22//! // In the Server --------------------------------------- //
23//! use doors::server::Door;
24//! use doors::server::Request;
25//! use doors::server::Response;
26//!
27//! #[doors::server_procedure]
28//! fn double(x: Request) -> Response<[u8; 1]> {
29//!   if x.data.len() > 0 {
30//!     return Response::new([x.data[0] * 2]);
31//!   } else {
32//!     // We were given nothing, and 2 times nothing is zero...
33//!     return Response::new([0]);
34//!   }
35//! }
36//!
37//! let door = Door::create(double).unwrap();
38//! door.force_install("/tmp/double.door").unwrap();
39//!
40//! // In the Client --------------------------------------- //
41//! use doors::Client;
42//!
43//! let client = Client::open("/tmp/double.door").unwrap();
44//!
45//! let response = client.call_with_data(&[111]).unwrap();
46//! assert_eq!(response.data()[0], 222);
47//! ```
48//!
49//! [1]: https://github.com/robertdfrench/revolving-doors
50//! [2]: https://illumos.org/man/3C/door_create
51//! [3]: https://illumos.org
52pub use door_macros::server_procedure;
53
54pub mod illumos;
55pub mod server;
56
57use crate::illumos::door_h::door_arg_t;
58use crate::illumos::door_h::door_call;
59use crate::illumos::errno_h::errno;
60use crate::illumos::DoorArg;
61use crate::illumos::DoorFd;
62use std::fs::File;
63use std::io;
64use std::os::fd::FromRawFd;
65use std::os::fd::IntoRawFd;
66use std::os::fd::RawFd;
67use std::path::Path;
68
69/// Failure conditions for [`door_call`].
70///
71/// According to [`door_call(3C)`], if a [`door_call`] fails, errno will be set
72/// to one of these values. While this enum is not strictly derived from
73/// anything in [doors.h][1], it is spelled out in the man page.
74///
75/// [`door_call(3C)`]: https://illumos.org/man/3C/door_call
76/// [1]: https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/sys/door.h
77#[derive(Debug, PartialEq)]
78pub enum DoorCallError {
79    /// Arguments were too big for server thread stack.
80    E2BIG,
81
82    /// Server was out of available resources.
83    EAGAIN,
84
85    /// Invalid door descriptor was passed.
86    EBADF,
87
88    /// Argument pointers pointed outside the allocated address space.
89    EFAULT,
90
91    /// A signal was caught in the client, the client called [`fork(2)`], or the
92    /// server exited during invocation.
93    ///
94    /// [`fork(2)`]: https://illumos.org/man/2/fork
95    EINTR,
96
97    /// Bad arguments were passed.
98    EINVAL,
99
100    /// The client or server has too many open descriptors.
101    EMFILE,
102
103    /// The desc_num argument is larger than the door's `DOOR_PARAM_DESC_MAX`
104    /// parameter (see [`door_getparam(3C)`]), and the door does not have the
105    /// [`DOOR_REFUSE_DESC`][crate::illumos::door_h::DOOR_REFUSE_DESC] set.
106    ///
107    /// [`door_getparam(3C)`]: https://illumos.org/man/3C/door_getparam
108    ENFILE,
109
110    /// The data_size argument is larger than the door's `DOOR_PARAM_DATA_MAX`
111    /// parameter, or smaller than the door's `DOOR_PARAM_DATA_MIN` parameter
112    /// (see [`door_getparam(3C)`]).
113    ///
114    /// [`door_getparam(3C)`]: https://illumos.org/man/3C/door_getparam
115    ENOBUFS,
116
117    /// The desc_num argument is non-zero and the door has the
118    /// [`DOOR_REFUSE_DESC`][crate::illumos::door_h::DOOR_REFUSE_DESC] flag set.
119    ENOTSUP,
120
121    /// System could not create overflow area in caller for results.
122    EOVERFLOW,
123}
124
125/// Less unsafe door client (compared to raw file descriptors)
126///
127/// Clients are automatically closed when they go out of scope. Errors detected
128/// on closing are ignored by the implementation of `Drop`, just like in
129/// [`File`].
130pub struct Client(RawFd);
131
132impl FromRawFd for Client {
133    unsafe fn from_raw_fd(raw: RawFd) -> Self {
134        Self(raw)
135    }
136}
137
138impl Drop for Client {
139    /// Automatically close the door on your way out.
140    ///
141    /// This will close the file descriptor associated with this door, so that
142    /// this process will no longer be able to call this door. For that reason,
143    /// it is a programming error to [`Clone`] this type.
144    fn drop(&mut self) {
145        unsafe { libc::close(self.0) };
146    }
147}
148
149pub enum DoorArgument {
150    BorrowedRbuf(DoorArg),
151    OwnedRbuf(DoorArg),
152}
153
154impl DoorArgument {
155    pub fn new(
156        data: &[u8],
157        descriptors: &[DoorFd],
158        response: &mut [u8],
159    ) -> Self {
160        Self::borrowed_rbuf(data, descriptors, response)
161    }
162
163    pub fn borrowed_rbuf(
164        data: &[u8],
165        descriptors: &[DoorFd],
166        response: &mut [u8],
167    ) -> Self {
168        Self::BorrowedRbuf(DoorArg::new(data, descriptors, response))
169    }
170
171    pub fn owned_rbuf(
172        data: &[u8],
173        descriptors: &[DoorFd],
174        response: &mut [u8],
175    ) -> Self {
176        Self::OwnedRbuf(DoorArg::new(data, descriptors, response))
177    }
178
179    fn inner(&self) -> &DoorArg {
180        match self {
181            Self::BorrowedRbuf(inner) => inner,
182            Self::OwnedRbuf(inner) => inner,
183        }
184    }
185
186    fn inner_mut(&mut self) -> &mut DoorArg {
187        match self {
188            Self::BorrowedRbuf(inner) => inner,
189            Self::OwnedRbuf(inner) => inner,
190        }
191    }
192
193    pub fn as_door_arg_t(&self) -> &door_arg_t {
194        self.inner().as_door_arg_t()
195    }
196
197    pub fn data(&self) -> &[u8] {
198        self.inner().data()
199    }
200
201    pub fn rbuf(&self) -> &[u8] {
202        self.inner().rbuf()
203    }
204}
205
206impl Drop for DoorArgument {
207    fn drop(&mut self) {
208        if let Self::OwnedRbuf(arg) = self {
209            // If munmap fails, we do want to panic, because it means we've
210            // tried to munmap something that wasn't mapped into our address
211            // space. That should never happen, but if it does, it's worth
212            // crashing, because something else is seriously wrong.
213            arg.munmap_rbuf().unwrap()
214        }
215    }
216}
217
218impl Client {
219    /// Open a door client like you would a file
220    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
221        let file = File::open(path)?;
222        Ok(Self(file.into_raw_fd()))
223    }
224
225    /// Issue a door call
226    ///
227    /// You are responsible for managing this memory. See [`DOOR_CALL(3C)`].
228    /// Particularly, if, after a `door_call`, the `rbuf` property of
229    /// [`door_arg_t`] is different than what it was before the `door_call`, you
230    /// are responsible for reclaiming this area with [`MUNMAP(2)`] when you are
231    /// done with it.
232    ///
233    /// This crate cannot yet handle this for you. See [Issue
234    /// #11](https://github.com/robertdfrench/rusty-doors/issues/11).
235    ///
236    /// [`DOOR_CALL(3C)`]: https://illumos.org/man/3C/door_call
237    /// [`MUNMAP(2)`]: https://illumos.org/man/2/munmap
238    pub fn call(
239        &self,
240        mut arg: DoorArgument,
241    ) -> Result<DoorArgument, DoorCallError> {
242        let a = arg.inner().rbuf_addr();
243        let x = arg.inner_mut().as_mut_door_arg_t();
244        match unsafe { door_call(self.0, x) } {
245            0 => match (x.rbuf as u64) == a {
246                true => Ok(arg),
247                false => {
248                    let data = unsafe {
249                        std::slice::from_raw_parts(
250                            x.data_ptr as *const u8,
251                            x.data_size,
252                        )
253                    };
254                    let desc = unsafe {
255                        std::slice::from_raw_parts(
256                            x.desc_ptr as *const DoorFd,
257                            x.desc_num.try_into().unwrap(),
258                        )
259                    };
260                    let rbuf = unsafe {
261                        std::slice::from_raw_parts_mut(
262                            x.rbuf as *mut u8,
263                            x.rsize,
264                        )
265                    };
266                    Ok(DoorArgument::owned_rbuf(data, desc, rbuf))
267                }
268            },
269            _ => Err(match errno() {
270                libc::E2BIG => DoorCallError::E2BIG,
271                libc::EAGAIN => DoorCallError::EAGAIN,
272                libc::EBADF => DoorCallError::EBADF,
273                libc::EFAULT => DoorCallError::EFAULT,
274                libc::EINTR => DoorCallError::EINTR,
275                libc::EINVAL => DoorCallError::EINVAL,
276                libc::EMFILE => DoorCallError::EMFILE,
277                libc::ENFILE => DoorCallError::ENFILE,
278                libc::ENOBUFS => DoorCallError::ENOBUFS,
279                libc::ENOTSUP => DoorCallError::ENOTSUP,
280                libc::EOVERFLOW => DoorCallError::EOVERFLOW,
281                _ => unreachable!(),
282            }),
283        }
284    }
285
286    /// Issue a door call with Data only
287    ///
288    /// ## Example
289    ///
290    /// ```rust
291    /// use doors::Client;
292    /// use std::ffi::CString;
293    /// use std::ffi::CStr;
294    ///
295    /// let capitalize = Client::open("/tmp/barebones_capitalize.door")
296    ///     .unwrap();
297    /// let text = CString::new("Hello, World!").unwrap();
298    /// let response = capitalize.call_with_data(text.as_bytes()).unwrap();
299    /// let caps = unsafe {
300    ///     CStr::from_ptr(response.data().as_ptr() as *const i8)
301    /// };
302    /// assert_eq!(caps.to_str(), Ok("HELLO, WORLD!"));
303    /// ```
304    pub fn call_with_data(
305        &self,
306        data: &[u8],
307    ) -> Result<DoorArgument, DoorCallError> {
308        let arg = DoorArgument::new(data, &[], &mut []);
309        self.call(arg)
310    }
311}