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}