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
/*
* 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 2023 Robert D. French
*/
//! Traits for easier Server Procedures
use crate::illumos;
use crate::illumos::door_h::door_desc_t;
use crate::illumos::fattach;
use crate::illumos::DoorAttributes;
use crate::illumos::DoorFd;
use libc;
use std::ffi;
use std::fs::File;
use std::io;
use std::os::fd::RawFd;
use std::path::Path;
/// 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(std::io::Error),
AttachDoor(illumos::Error),
OpenDoor(std::io::Error),
DoorCall(libc::c_int),
CreateDoor(illumos::Error),
}
/// A Descriptor for the Door Server
///
/// When a door is created, the kernel hands us back a reference to it by giving
/// us an index in our descriptor table. This is true even if the door hasn't
/// been attached to the filesystem yet, a la pipes or sockets.
pub struct Door(RawFd);
impl Door {
/// Create a new Door with the specified server procedure. This will not
/// expose the door to the filesystem by default. It will assume that you
/// are not using a door cookie, and that you do not need to set any
/// [`DoorAttributes`].
pub fn create(sp: illumos::ServerProcedure) -> Result<Self, Error> {
let cookie = 0;
let attrs = DoorAttributes::none();
Self::create_with_cookie_and_attributes(sp, cookie, attrs)
}
/// Create a new Door with a Cookie. This will not expose the door to the
/// filesystem by default. It will use the door cookie that you provide, but
/// will assume that you do not need to set any [`DoorAttributes`].
pub fn create_with_cookie(
sp: illumos::ServerProcedure,
cookie: u64,
) -> Result<Self, Error> {
let attrs = DoorAttributes::none();
Self::create_with_cookie_and_attributes(sp, cookie, attrs)
}
/// Create a new Door with Attributes. This will not expose the door to the
/// filesystem by default. It will use the [`DoorAttributes`] that you
/// provide, but will assume that you are not using a door cookie.
pub fn create_with_attributes(
sp: illumos::ServerProcedure,
attrs: DoorAttributes,
) -> Result<Self, Error> {
let cookie = 0;
Self::create_with_cookie_and_attributes(sp, cookie, attrs)
}
/// Create a new Door with Cookie and Attributes. This will not expose the
/// door to the filesystem by default. It will use the [`DoorAttributes`]
/// and cookie that you provide.
pub fn create_with_cookie_and_attributes(
sp: illumos::ServerProcedure,
cookie: u64,
attrs: illumos::DoorAttributes,
) -> Result<Self, Error> {
match illumos::door_create(sp, cookie, attrs) {
Ok(fd) => Ok(Self(fd as RawFd)),
Err(e) => Err(Error::CreateDoor(e)),
}
}
/// Make this door server available on the filesystem. This is necessary if
/// we want other processes to be able to find and call this door server.
pub fn install<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
// Create jamb
let _jamb = match create_new_file(&path) {
Ok(file) => file,
Err(e) => return Err(Error::InstallJamb(e)),
};
// Attach door to jamb
match fattach(self.0, &path) {
Err(e) => {
// Clean up the jamb, since we aren't going to finish
std::fs::remove_file(&path).ok();
Err(Error::AttachDoor(e))
}
Ok(()) => Ok(()),
}
}
/// Make this door available on the filesystem even if there is already a
/// file (possibly leftover from a previous door) as this path.
pub fn force_install<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
if path.as_ref().exists() {
if let Err(e) = std::fs::remove_file(&path) {
return Err(Error::InstallJamb(e));
}
}
self.install(path)
}
}
impl Drop for Door {
fn drop(&mut self) {
unsafe {
illumos::door_h::door_revoke(self.0);
}
}
}
/// Server-Side representation of the client's door arguments
///
/// This type allows us to write server procedures that accept a single argument
/// rather than five separate arguments.
#[derive(Copy, Clone)]
pub struct Request<'a> {
pub cookie: u64,
pub data: &'a [u8],
pub descriptors: &'a [door_desc_t],
}
/// Server-Side representation of the client's door results
///
/// This type can refer to either memory on the stack (which will be cleaned up
/// automatically when door_return is called) or memory on the heap (which
/// will not). If you return an object that refers to memory on the heap, it is
/// your responsibility to free it later.
///
/// Many door servers allocate a per-thread response area so that each thread
/// can re-use this area for every door invocation assigned to it. That way the
/// memory leaked is constant. Typically, applications that take this approach
/// will free these per-thread response areas when the DOOR_UNREF message is
/// sent.
pub struct Response<C: AsRef<[u8]>> {
pub data: Option<C>,
pub num_descriptors: u32,
pub descriptors: [DoorFd; 2],
}
impl<C: AsRef<[u8]>> Response<C> {
pub fn new(data: C) -> Self {
let descriptors = [DoorFd::new(-1, true), DoorFd::new(-1, true)];
let num_descriptors = 0;
Self {
data: Some(data),
descriptors,
num_descriptors,
}
}
pub fn empty() -> Self {
let data = None;
let descriptors = [DoorFd::new(-1, true), DoorFd::new(-1, true)];
let num_descriptors = 0;
Self {
data,
descriptors,
num_descriptors,
}
}
pub fn add_descriptor(mut self, fd: RawFd, release: bool) -> Self {
if self.num_descriptors == 2 {
panic!("Only 2 descriptors are supported")
}
let desc = DoorFd::new(fd, release);
self.descriptors[self.num_descriptors as usize] = desc;
self.num_descriptors += 1;
self
}
}
fn create_new_file<P: AsRef<Path>>(path: P) -> io::Result<File> {
File::options()
.read(true)
.write(true)
.create_new(true)
.open(path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn create_new_fails_if_file_exists() {
match File::create("/tmp/create_new_fail.txt") {
// If we can't create the "original" file, we want the test to fail,
// which means that we *don't* want to panic.
Err(e) => {
eprintln!("{:?}", e);
assert!(true)
}
Ok(_file) => {
create_new_file("/tmp/create_new_fail.txt").unwrap();
}
}
}
}