use std::ffi::OsString;
#[cfg(not(windows))]
use std::path::Component;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
#[must_use]
pub fn intercept_from_env() -> Option<Result<()>> {
let mut args = std::env::args_os();
let _argv0 = args.next();
match args.next() {
Some(first) if first == rmux_client::INTERNAL_DAEMON_FLAG => {
Some(run_internal_daemon(args))
}
_ => {
point_sdk_daemon_at_self();
None
}
}
}
fn point_sdk_daemon_at_self() {
let Ok(exe) = std::env::current_exe() else {
return;
};
unsafe { point_sdk_daemon_at(&exe) }
}
pub unsafe fn point_sdk_daemon_at(binary: &std::path::Path) {
unsafe {
std::env::set_var(
rmux_sdk::bootstrap::discovery::SDK_DAEMON_BINARY_ENV,
binary,
);
}
}
pub fn run_internal_daemon<I>(args: I) -> Result<()>
where
I: IntoIterator<Item = OsString>,
{
let socket_path = parse_socket_path(args)
.context("the embedded rmux daemon requires a socket path argument")?;
validate_socket_path(&socket_path)?;
let config = rmux_server::DaemonConfig::new(socket_path);
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.context("build tokio runtime for embedded rmux daemon")?;
runtime.block_on(async move {
let server = rmux_server::ServerDaemon::new(config)
.bind()
.await
.context("bind embedded rmux daemon socket")?;
server
.wait()
.await
.context("embedded rmux daemon wait loop")?;
Ok::<(), anyhow::Error>(())
})
}
fn parse_socket_path<I>(args: I) -> Option<PathBuf>
where
I: IntoIterator<Item = OsString>,
{
for arg in args {
if !arg.as_encoded_bytes().starts_with(b"--") {
return Some(PathBuf::from(arg));
}
}
None
}
pub(crate) fn validate_socket_path(path: &Path) -> Result<()> {
#[cfg(windows)]
{
const PIPE_PREFIX: &str = r"\\.\pipe\";
let display = path.to_string_lossy();
if !display.starts_with(PIPE_PREFIX) {
bail!(
"embedded rmux daemon named-pipe path must start with `{PIPE_PREFIX}`, got {display}"
);
}
let name = &display[PIPE_PREFIX.len()..];
if name.is_empty() || name.contains('\\') || name.contains('/') {
bail!(
"embedded rmux daemon named-pipe name is empty or contains a separator: {display}"
);
}
Ok(())
}
#[cfg(not(windows))]
{
if !path.is_absolute() {
bail!(
"embedded rmux daemon socket path must be absolute, got {}",
path.display()
);
}
if path
.components()
.any(|component| matches!(component, Component::ParentDir))
{
bail!(
"embedded rmux daemon socket path must not contain `..`, got {}",
path.display()
);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(windows))]
#[test]
fn validate_socket_path_accepts_absolute_traversal_free_path() {
assert!(validate_socket_path(Path::new("/tmp/basemind/shells/rmux.sock")).is_ok());
}
#[cfg(not(windows))]
#[test]
fn validate_socket_path_rejects_relative_path() {
let err = validate_socket_path(Path::new("relative/evil.sock"))
.expect_err("relative path must be rejected");
assert!(err.to_string().contains("must be absolute"), "{err}");
}
#[cfg(not(windows))]
#[test]
fn validate_socket_path_rejects_parent_dir_traversal() {
let err = validate_socket_path(Path::new("/var/run/../../evil.sock"))
.expect_err("`..` component must be rejected");
assert!(err.to_string().contains("must not contain `..`"), "{err}");
}
#[cfg(windows)]
#[test]
fn validate_socket_path_accepts_named_pipe_path() {
assert!(validate_socket_path(Path::new(r"\\.\pipe\basemind-shells-alice")).is_ok());
}
#[cfg(windows)]
#[test]
fn validate_socket_path_rejects_non_pipe_path() {
let err = validate_socket_path(Path::new(r"C:\Windows\Temp\evil.sock"))
.expect_err("a non-pipe path must be rejected on Windows");
assert!(err.to_string().contains(r"\\.\pipe\"), "{err}");
}
#[cfg(windows)]
#[test]
fn validate_socket_path_rejects_pipe_name_with_separator() {
let err = validate_socket_path(Path::new(r"\\.\pipe\evil\..\escape"))
.expect_err("a pipe name with a separator must be rejected");
assert!(err.to_string().contains("separator"), "{err}");
}
#[test]
fn parses_first_positional_as_socket_path() {
let args = vec![OsString::from("/tmp/basemind-shells.sock")];
assert_eq!(
parse_socket_path(args),
Some(PathBuf::from("/tmp/basemind-shells.sock"))
);
}
#[test]
fn skips_leading_config_flags_and_finds_socket() {
let args = vec![
OsString::from("/tmp/sock"),
OsString::from("--config-quiet"),
];
assert_eq!(parse_socket_path(args), Some(PathBuf::from("/tmp/sock")));
}
#[test]
fn returns_none_when_only_flags_present() {
let args = vec![OsString::from("--config-quiet")];
assert_eq!(parse_socket_path(args), None);
}
}