use crate::MicrosandboxResult;
use super::exec::Rlimit;
#[derive(Debug, Clone, Default)]
pub struct AttachOptions {
pub(crate) args: Vec<String>,
pub(crate) env: Vec<(String, String)>,
pub(crate) cwd: Option<String>,
pub(crate) user: Option<String>,
pub(crate) detach_keys: Option<String>,
pub(crate) rlimits: Vec<Rlimit>,
}
#[derive(Default)]
pub struct AttachOptionsBuilder {
options: AttachOptions,
}
pub(crate) struct DetachKeys {
sequence: Vec<u8>,
}
impl AttachOptionsBuilder {
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.options.args.push(arg.into());
self
}
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.options.args.extend(args.into_iter().map(Into::into));
self
}
pub fn cwd(mut self, cwd: impl Into<String>) -> Self {
self.options.cwd = Some(cwd.into());
self
}
pub fn user(mut self, user: impl Into<String>) -> Self {
self.options.user = Some(user.into());
self
}
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.env.push((key.into(), value.into()));
self
}
pub fn envs(
mut self,
vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
self.options
.env
.extend(vars.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
self.options.detach_keys = Some(keys.into());
self
}
pub fn rlimit(mut self, resource: super::exec::RlimitResource, limit: u64) -> Self {
self.options.rlimits.push(Rlimit {
resource,
soft: limit,
hard: limit,
});
self
}
pub fn rlimit_range(
mut self,
resource: super::exec::RlimitResource,
soft: u64,
hard: u64,
) -> Self {
self.options.rlimits.push(Rlimit {
resource,
soft,
hard,
});
self
}
pub fn build(self) -> AttachOptions {
self.options
}
}
impl DetachKeys {
const DEFAULT: u8 = 0x1d;
pub fn parse(spec: &str) -> MicrosandboxResult<Self> {
let mut sequence = Vec::new();
for part in spec.split(',') {
let part = part.trim();
if let Some(ch) = part.strip_prefix("ctrl-") {
let byte = match ch {
"]" => 0x1d,
"[" => 0x1b,
"\\" => 0x1c,
"^" => 0x1e,
"_" => 0x1f,
"@" => 0x00,
c if c.len() == 1 => {
let b = c.as_bytes()[0];
if b.is_ascii_lowercase() {
b - b'a' + 1
} else if b.is_ascii_uppercase() {
b - b'A' + 1
} else {
return Err(crate::MicrosandboxError::InvalidConfig(format!(
"invalid detach key: {part}"
)));
}
}
_ => {
return Err(crate::MicrosandboxError::InvalidConfig(format!(
"invalid detach key: {part}"
)));
}
};
sequence.push(byte);
} else if part.len() == 1 {
sequence.push(part.as_bytes()[0]);
} else {
return Err(crate::MicrosandboxError::InvalidConfig(format!(
"invalid detach key: {part}"
)));
}
}
if sequence.is_empty() {
sequence.push(Self::DEFAULT);
}
Ok(Self { sequence })
}
pub fn default_keys() -> Self {
Self {
sequence: vec![Self::DEFAULT],
}
}
pub fn sequence(&self) -> &[u8] {
&self.sequence
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detach_keys_default() {
let keys = DetachKeys::default_keys();
assert_eq!(keys.sequence(), &[0x1d]);
}
#[test]
fn test_detach_keys_ctrl_bracket() {
let keys = DetachKeys::parse("ctrl-]").unwrap();
assert_eq!(keys.sequence(), &[0x1d]);
}
#[test]
fn test_detach_keys_ctrl_letter() {
let keys = DetachKeys::parse("ctrl-a").unwrap();
assert_eq!(keys.sequence(), &[0x01]);
let keys = DetachKeys::parse("ctrl-z").unwrap();
assert_eq!(keys.sequence(), &[0x1a]);
}
#[test]
fn test_detach_keys_multi_sequence() {
let keys = DetachKeys::parse("ctrl-p,ctrl-q").unwrap();
assert_eq!(keys.sequence(), &[0x10, 0x11]);
}
#[test]
fn test_detach_keys_single_char() {
let keys = DetachKeys::parse("q").unwrap();
assert_eq!(keys.sequence(), &[b'q']);
}
#[test]
fn test_detach_keys_invalid() {
assert!(DetachKeys::parse("ctrl-").is_err());
assert!(DetachKeys::parse("ctrl-ab").is_err());
}
}