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 301 302 303 304 305 306 307 308 309 310 311
//! Contexts and errors.
//!
//! Sequoia tries to be useful for a wide variety of applications.
//! Therefore, we need you to provide a little information about the
//! context you are using Sequoia in.
//!
//! # Examples
//!
//! A context with reasonable defaults can be created using
//! `Context::new`:
//!
//! ```no_run
//! # use sequoia_ipc::{Context, Result};
//! # fn main() -> Result<()> {
//! let c = Context::new();
//! # Ok(())
//! # }
//! ```
#![warn(missing_docs)]
use std::path::{Path, PathBuf};
use crate::Result;
/// A `Context` for Sequoia.
///
/// # Examples
///
/// A context with reasonable defaults can be created using
/// `Context::new`:
///
/// ```no_run
/// # use sequoia_ipc::{Context, Result};
/// # fn main() -> Result<()> {
/// let c = Context::new()?;
/// # Ok(())
/// # }
/// ```
///
/// A context can be configured using the builder pattern with
/// `Context::configure`:
///
/// ```
/// # use sequoia_ipc::{Context, IPCPolicy, Result};
/// # fn main() -> Result<()> {
/// let c = Context::configure()
/// # .ephemeral()
/// .ipc_policy(IPCPolicy::Robust)
/// .build()?;
/// # Ok(())
/// # }
/// ```
pub struct Context {
home: PathBuf,
lib: PathBuf,
ipc_policy: IPCPolicy,
ephemeral: bool,
cleanup: bool,
}
impl Clone for Context {
fn clone(&self) -> Self {
Context {
home: self.home.clone(),
lib: self.lib.clone(),
ipc_policy: self.ipc_policy,
ephemeral: self.ephemeral,
cleanup: false, // Prevent cleanup.
}
}
}
impl Drop for Context {
fn drop(&mut self) {
use std::fs::remove_dir_all;
if self.ephemeral && self.cleanup {
let _ = remove_dir_all(&self.home);
}
}
}
/// Returns $PREXIX at compile-time, or a reasonable default prefix.
fn prefix() -> PathBuf {
/* XXX: Windows support. */
PathBuf::from(option_env!("PREFIX").unwrap_or("/usr/local"))
}
impl Context {
/// Creates a Context with reasonable defaults.
pub fn new() -> Result<Self> {
Self::configure().build()
}
/// Creates a Context that can be configured.
///
/// The configuration is seeded like in `Context::new`, but can be
/// modified. A configuration has to be finalized using
/// `.build()` in order to turn it into a Context.
pub fn configure() -> Config {
Config(Context {
home: PathBuf::from(""), // Defer computation of default.
lib: prefix().join("lib").join("sequoia"),
ipc_policy: IPCPolicy::Robust,
ephemeral: false,
cleanup: false,
})
}
/// Returns the directory containing shared state.
pub fn home(&self) -> &Path {
&self.home
}
/// Returns the directory containing backend servers.
pub fn lib(&self) -> &Path {
&self.lib
}
/// Returns the IPC policy.
pub fn ipc_policy(&self) -> &IPCPolicy {
&self.ipc_policy
}
/// Returns whether or not this is an ephemeral context.
pub fn ephemeral(&self) -> bool {
self.ephemeral
}
}
/// Represents a `Context` configuration.
///
/// A context can be configured using the builder pattern with
/// `Context::configure`:
///
/// ```
/// # use sequoia_ipc::{Context, IPCPolicy, Result};
/// # fn main() -> Result<()> {
/// let c = Context::configure()
/// # .ephemeral()
/// .ipc_policy(IPCPolicy::Robust)
/// .build()?;
/// # Ok(())
/// # }
/// ```
///
/// You can create ephemeral context that are useful for tests and
/// one-shot programs:
///
/// ```
/// # use sequoia_ipc::{Context, Result};
/// # use std::path::Path;
/// # fn main() -> Result<()> {
/// let c = Context::configure().ephemeral().build()?;
/// let ephemeral_home = c.home().to_path_buf();
/// // Do some tests.
/// drop(c);
/// assert!(! ephemeral_home.exists());
/// # Ok(())
/// # }
/// ```
pub struct Config(Context);
impl Config {
/// Finalizes the configuration and returns a `Context`.
pub fn build(self) -> Result<Context> {
let mut c = self.0;
// As a special case, we defer the computation of the default
// home, because env::home_dir() may fail.
let home_not_set = c.home == PathBuf::from("");
// If we have an ephemeral home, and home is not explicitly
// set, create a temporary directory. Ephemeral contexts can
// share home directories, e.g. client and server processes
// share one home.
if c.ephemeral && home_not_set {
let tmp = tempfile::Builder::new().prefix("sequoia").tempdir()?;
c.home = tmp.into_path();
c.cleanup = true;
} else {
if home_not_set {
c.home = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Failed to get users home directory"))?
.join(".sequoia");
}
}
Ok(c)
}
/// Sets the directory containing shared state.
pub fn home<P: AsRef<Path>>(mut self, home: P) -> Self {
self.set_home(home);
self
}
/// Sets the directory containing shared state.
pub fn set_home<P: AsRef<Path>>(&mut self, home: P) -> PathBuf {
::std::mem::replace(&mut self.0.home, PathBuf::new().join(home))
}
/// Sets the directory containing backend servers.
pub fn lib<P: AsRef<Path>>(mut self, lib: P) -> Self {
self.set_lib(lib);
self
}
/// Sets the directory containing backend servers.
pub fn set_lib<P: AsRef<Path>>(&mut self, lib: P) -> PathBuf {
::std::mem::replace(&mut self.0.lib, PathBuf::new().join(lib))
}
/// Sets the IPC policy.
pub fn ipc_policy(mut self, policy: IPCPolicy) -> Self {
self.set_ipc_policy(policy);
self
}
/// Sets the IPC policy.
pub fn set_ipc_policy(&mut self, policy: IPCPolicy) -> IPCPolicy {
::std::mem::replace(&mut self.0.ipc_policy, policy)
}
/// Makes this context ephemeral.
pub fn ephemeral(mut self) -> Self {
self.set_ephemeral();
self
}
/// Makes this context ephemeral.
pub fn set_ephemeral(&mut self) -> bool {
::std::mem::replace(&mut self.0.ephemeral, true)
}
}
/* IPC policy. */
/// IPC policy for Sequoia.
///
/// With this policy you can control how Sequoia starts background
/// servers.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum IPCPolicy {
/// External background servers only.
///
/// We will always use external background servers. If starting
/// one fails, the operation will fail.
///
/// The advantage is that we never spawn a thread.
///
/// The disadvantage is that we need to locate the background
/// server to start. If you are distribute Sequoia with your
/// application, make sure to include the binaries, and to
/// configure the Context so that `context.lib()` points to the
/// directory containing the binaries.
External,
/// Internal background servers only.
///
/// We will always use internal background servers. It is very
/// unlikely that this fails.
///
/// The advantage is that this method is very robust. If you
/// distribute Sequoia with your application, you do not need to
/// ship the binary, and it does not matter what `context.lib()`
/// points to. This is very robust and convenient.
///
/// The disadvantage is that we spawn a thread in your
/// application. Threads may play badly with `fork(2)`, file
/// handles, and locks. If you are not doing anything fancy,
/// however, and only use fork-then-exec, you should be okay.
Internal,
/// Prefer external, fall back to internal.
///
/// We will first try to use an external background server, but
/// fall back on an internal one should that fail.
///
/// The advantage is that if Sequoia is properly set up to find
/// the background servers, we will use these and get the
/// advantages of that approach. Because we fail back on using an
/// internal server, we gain the robustness of that approach.
///
/// The disadvantage is that we may or may not spawn a thread in
/// your application. If this is unacceptable in your
/// environment, use the `External` policy.
Robust,
}
impl<'a> From<&'a IPCPolicy> for u8 {
fn from(policy: &IPCPolicy) -> Self {
match policy {
&IPCPolicy::External => 0,
&IPCPolicy::Internal => 1,
&IPCPolicy::Robust => 2,
}
}
}
// XXX: TryFrom would be nice.
impl From<u8> for IPCPolicy {
fn from(policy: u8) -> Self {
match policy {
0 => IPCPolicy::External,
1 => IPCPolicy::Internal,
2 => IPCPolicy::Robust,
n => panic!("Bad IPC policy: {}", n),
}
}
}