1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. * * Copyright 2021 Robert D. French */ //! Define connection handlers for your PortunusD Apps! //! //! In PortunusD, every incoming connection is forwarded to an external application via //! [illumos Doors][1]. You can use the `derive_server_procedure!` macro defined in this module to //! convert a `Fn: &[u8] -> Vec<u8>` function into a PortunusD function handler. //! //! Below is an example of an application that accepts a user's name in the request body and //! returns a polite greeting: //! ``` //! use portunusd::derive_server_procedure; //! use portunusd::door; //! use std::fmt::format; //! use std::str::from_utf8; //! //! // Consider the function `hello`, which returns a polite greeting to a client: //! fn hello(request: &[u8]) -> Vec<u8> { //! match from_utf8(request) { //! Err(_) => b"I couldn't understand your name!".to_vec(), //! Ok(name) => { //! let response = format!("Hello, {}!", name); //! response.into_bytes() //! } //! } //! } //! //! // We can turn that function into a special type (one that implements ServerProcedure) which //! // knows how to make the function available via a "door" on the filesystem: //! derive_server_procedure!(hello as Hello); //! //! // make the `hello` function available on the filesystem //! let hello_server = Hello::install("portunusd_test.04683b").unwrap(); //! //! // Now a client (even one in another process!) can call this procedure: //! let hello_client = door::Client::new("portunusd_test.04683b").unwrap(); //! let greeting = hello_client.call(b"Portunus").unwrap(); //! //! assert_eq!(greeting, b"Hello, Portunus!"); //! ``` //! //! [1]: https://github.com/robertdfrench/revolving-door use crate::illumos::door_h::{ door_call, door_create, door_arg_t, DOOR_REFUSE_DESC, door_desc_t, door_return, }; use crate::illumos::stropts_h::{ fattach, fdetach }; use crate::illumos::errno; use libc; use std::ffi; use std::ptr; use std::slice; pub struct ClientRef { door_descriptor: libc::c_int } impl ClientRef { pub fn call(&self, request: &[u8]) -> Result<Vec<u8>,Error> { let mut response = Vec::with_capacity(1024); // the vector has length zero, so rsize is zero, so the overflow handling gets triggered // which fucks up alignment so data_ptr > rbuf let mut arg = door_arg_t { data_ptr: request.as_ptr() as *const i8, data_size: request.len(), desc_ptr: ptr::null(), desc_num: 0, rbuf: response.as_mut_ptr() as *const i8, rsize: response.len() }; if unsafe{ door_call(self.door_descriptor, &mut arg) } == -1 { return Err(Error::DoorCall(errno())); } unsafe{ response.set_len(arg.data_size); } let slice = unsafe{ std::slice::from_raw_parts(arg.data_ptr as *const u8, arg.data_size) }; Ok(slice.to_vec()) } } /// A Client handle for a door. Used by PortunusD to call your application. /// /// When your application wants to receive requests from PortunusD, it must create a [Door] on the /// filesystem which points to a [`ServerProcedure`] function. This Client type is the reciprocal /// of a ServerProcedure -- it is PortunusD's way of accessing your application, much like a file /// handle is a means of accessing the bytes which make up a file. /// /// [Door]: https://github.com/robertdfrench/revolving-door#revolving-doors /// [`ServerProcedure`]: trait.ServerProcedure.html pub struct Client { door_descriptor: libc::c_int } impl Client { /// Try to create a new client, given a filesystem to a door. /// /// This may fail if the door does not exist, if the path is not a door, or if some other /// terrible thing has happened. pub fn new(path: &str) -> Result<Self,Error> { let path = ffi::CString::new(path)?; match unsafe{ libc::open(path.as_ptr(), libc::O_RDONLY) } { -1 => return Err(Error::OpenDoor(errno())), door_descriptor => Ok(Self{ door_descriptor }) } } /// Invoke the Server Procedure defined in a PortunusdD application /// /// Forwad a slice of bytes through a door to a PortunusD application. If successful, the /// resulting `Vec<u8>` will contain the bytes returned from the application's server /// procedure. pub fn call(&self, request: &[u8]) -> Result<Vec<u8>,Error> { let cr = self.borrow(); cr.call(request) } pub fn borrow(&self) -> ClientRef { ClientRef{ door_descriptor: self.door_descriptor } } } impl Drop for Client { /// Close PortunusD's connection the remote application fn drop(&mut self) { unsafe{ libc::close(self.door_descriptor); } } } /// A server procedure which has been attached to the filesystem. pub struct Server { jamb_path: ffi::CString, door_descriptor: libc::c_int } /// Door problems. /// /// Two things can go wrong with a door -- its path can be invalid, or a system call can fail. If a /// system call fails, one of this enum's variants will be returned corresponding to the failed /// system call. It will contain the value of `errno` associated with the failed system call. #[derive(Debug)] pub enum Error { InvalidPath(ffi::NulError), InstallJamb(libc::c_int), AttachDoor(libc::c_int), OpenDoor(libc::c_int), DoorCall(libc::c_int), CreateDoor(libc::c_int), } impl From<ffi::NulError> for Error { fn from(other: ffi::NulError) -> Self { Self::InvalidPath(other) } } impl Drop for Server { /// Prevent new client requests /// /// Removes the door from the filesystem, and closes the door, invaliding any active door /// descriptors that client processes may have. This will prevent PortunusD from forwarding /// additional requests to your application. fn drop(&mut self) { // Stop new clients from getting a door descriptor unsafe{ fdetach(self.jamb_path.as_ptr()); } // Remove jamb from filesystem unsafe{ libc::unlink(self.jamb_path.as_ptr()); } // Stop existing clients from issuing new door_call()s unsafe{ libc::close(self.door_descriptor); } } } /// Trait for types derived from the `define_server_procedure!` macro. /// /// Because `define_server_procedure!` creates a new type to "host" each server procedure, we need /// to define a trait so we can work with these types generically. /// pub trait ServerProcedure { /// This is the part you define. The function body you give in `define_server_procedure!` will /// end up as the definition of this `rust` function, which will be called by the associated /// `c_wrapper` function in this trait. fn rust_wrapper(request: &[u8]) -> Vec<u8>; /// This is a wrapper that fits the Doors API All it does is pack and unpack data so that our /// server procedure doesn't have to deal with the doors api directly. Its unusual signature /// comes directly from [`DOOR_CREATE(3C)`]. /// /// [`DOOR_CREATE(3C)`]: https://illumos.org/man/3C/door_create extern "C" fn c_wrapper( _cookie: *const libc::c_void, argp: *const libc::c_char, arg_size: libc::size_t, _dp: *const door_desc_t, _n_desc: libc::c_uint ) { let request = unsafe{ slice::from_raw_parts(argp as *const u8, arg_size) }; let response = Self::rust_wrapper(request); let data_ptr = response.as_ptr(); let data_size = response.len(); unsafe{ door_return(data_ptr as *const libc::c_char, data_size, ptr::null(), 0); } } /// Make this procedure available on the filesystem (as a door). fn install(path: &str) -> Result<Server,Error> { let jamb_path = ffi::CString::new(path)?; // Create door let door_descriptor = unsafe{ door_create(Self::c_wrapper, ptr::null(), DOOR_REFUSE_DESC) }; if door_descriptor == -1 { return Err(Error::CreateDoor(errno())); } // Create jamb let create_new = libc::O_RDWR | libc::O_CREAT | libc::O_EXCL; match unsafe{ libc::open(jamb_path.as_ptr(), create_new, 0400) } { -1 => { // Clean up the door, since we aren't going to finish unsafe{ libc::close(door_descriptor) }; return Err(Error::InstallJamb(errno())) }, jamb_descriptor => unsafe{ libc::close(jamb_descriptor); } } // Attach door to jamb match unsafe{ fattach(door_descriptor, jamb_path.as_ptr()) } { -1 => { // Clean up the door and jamb, since we aren't going to finish unsafe{ libc::close(door_descriptor) }; unsafe{ libc::unlink(jamb_path.as_ptr()); } Err(Error::AttachDoor(errno())) }, _ => Ok(Server{ jamb_path, door_descriptor }) } } } /// Define a function which can respond to [`DOOR_CALL(3C)`]. /// /// This macro turns a function into a type which implements the [`ServerProcedure`] trait. /// The function should accept a `&[u8]` and return a `Vec<u8>`, because the `ServerProcedure` /// trait will expect that signature. /// /// # Example /// ``` /// use portunusd::derive_server_procedure; /// use std::fmt::format; /// use std::str::from_utf8; /// /// // Consider this function, which returns a polite greeting to a client: /// fn hello(request: &[u8]) -> Vec<u8> { /// match from_utf8(request) { /// Err(_) => b"Your name is not valid utf8".to_vec(), /// Ok(name) => { /// let response = format!("Hello, {}!", name); /// response.into_bytes() /// } /// } /// } /// /// // We can use the `derive_server_procedure!` macro to create a /// // `ServerProcedure` type called `Hello`: /// derive_server_procedure!(hello as Hello); /// /// // We can now create a filesystem object known as a "door" which /// // will give PortunusD the ability to invoke the `hello` function /// // (as long as "hello.door" is readable by the `portunus` user): /// Hello::install("hello.door").unwrap(); /// ``` /// /// [`DOOR_CALL(3C)`]: https://illumos.org/man/3C/door_call /// [`ServerProcedure`]: door/trait.ServerProcedure.html #[macro_export] macro_rules! derive_server_procedure { ($function_name:ident as $type_name:ident) => { use portunusd::door::ServerProcedure; struct $type_name; impl ServerProcedure for $type_name { fn rust_wrapper(request: &[u8]) -> Vec<u8> { $function_name(request) } } } }