#[cfg(all(feature = "escape-probe", target_os = "linux"))]
#[allow(unsafe_code)]
mod escape_probe;
mod http;
mod launch;
mod remote;
mod supervisor;
pub(crate) mod wire;
mod worker;
use std::ffi::OsString;
use std::sync::atomic::{AtomicU8, Ordering};
use serde::{Deserialize, Serialize};
use crate::errors::RuntimeError;
pub(crate) use http::RemoteHttp;
pub(crate) use launch::{Launcher, WorkerSandbox};
pub(crate) use remote::RemoteProcessDriver;
pub use supervisor::GuestWorkers;
pub(crate) use wire::StreamEvent;
pub use worker::RemoteLink;
pub const PROTOCOL_VERSION: u32 = 1;
pub const ENTRY: &str = "__guest-worker";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum WorkerKind {
Driver,
Tools,
Hooks,
}
impl WorkerKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Driver => "driver",
Self::Tools => "tools",
Self::Hooks => "hooks",
}
}
pub fn parse(name: &str) -> Option<Self> {
match name {
"driver" => Some(Self::Driver),
"tools" => Some(Self::Tools),
"hooks" => Some(Self::Hooks),
_ => None,
}
}
pub fn for_runtime(runtime: murmur_artifact::ArtifactRuntime) -> Option<Self> {
use murmur_artifact::ArtifactRuntime;
match runtime {
ArtifactRuntime::Tool => Some(Self::Tools),
ArtifactRuntime::Driver => Some(Self::Driver),
ArtifactRuntime::Hook => Some(Self::Hooks),
ArtifactRuntime::Skill => None,
}
}
fn runtime_threads(self) -> usize {
match self {
Self::Tools => 4,
Self::Driver | Self::Hooks => 2,
}
}
}
const PLACEMENT_UNSPLIT: u8 = 0;
const PLACEMENT_SUPERVISOR: u8 = 1;
const PLACEMENT_WORKER: u8 = 2;
static PLACEMENT: AtomicU8 = AtomicU8::new(PLACEMENT_UNSPLIT);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Placement {
Unsplit,
Supervisor,
Worker,
}
pub fn use_worker_processes() {
let _ = PLACEMENT.compare_exchange(
PLACEMENT_UNSPLIT,
PLACEMENT_SUPERVISOR,
Ordering::SeqCst,
Ordering::SeqCst,
);
}
#[cfg(test)]
thread_local! {
static TEST_PLACEMENT: std::cell::Cell<Option<Placement>> = const { std::cell::Cell::new(None) };
}
#[cfg(test)]
pub(crate) fn with_placement<R>(placement: Placement, f: impl FnOnce() -> R) -> R {
TEST_PLACEMENT.with(|cell| cell.set(Some(placement)));
let result = f();
TEST_PLACEMENT.with(|cell| cell.set(None));
result
}
pub(crate) fn current_placement() -> Placement {
#[cfg(test)]
if let Some(placement) = TEST_PLACEMENT.with(std::cell::Cell::get) {
return placement;
}
match PLACEMENT.load(Ordering::SeqCst) {
PLACEMENT_SUPERVISOR => Placement::Supervisor,
PLACEMENT_WORKER => Placement::Worker,
_ => Placement::Unsplit,
}
}
pub(crate) fn session_launcher() -> Option<Launcher> {
match current_placement() {
Placement::Supervisor => Some(Launcher::own_image()),
Placement::Unsplit | Placement::Worker => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StoreSite {
Tool,
Driver,
Hook,
ProcessDriver,
Capsule,
}
impl StoreSite {
fn describe(self) -> &'static str {
match self {
Self::Tool => "a tool",
Self::Driver => "an inference driver",
Self::Hook => "a hook",
Self::ProcessDriver => "a process driver",
Self::Capsule => "a script capsule's component",
}
}
}
pub(crate) fn store_allowed(placement: Placement, site: StoreSite) -> Result<(), RuntimeError> {
match placement {
Placement::Unsplit | Placement::Worker => Ok(()),
Placement::Supervisor => Err(RuntimeError::Runtime(format!(
"refused to create a store for {} in the supervisor: guests run only in guest workers",
site.describe()
))),
}
}
pub(crate) fn check_store_placement(site: StoreSite) -> Result<(), RuntimeError> {
store_allowed(current_placement(), site)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkerEntry {
Kind(WorkerKind),
Misuse,
}
pub fn entry_kind(args: impl IntoIterator<Item = OsString>) -> Option<WorkerEntry> {
let mut args = args.into_iter().skip(1);
if args.next()?.to_str() != Some(ENTRY) {
return None;
}
let kind = args.next();
if args.next().is_some() {
return Some(WorkerEntry::Misuse);
}
Some(
kind.and_then(|kind| kind.to_str().and_then(WorkerKind::parse))
.map_or(WorkerEntry::Misuse, WorkerEntry::Kind),
)
}
pub fn worker_main(entry: WorkerEntry) -> i32 {
let kind = match entry {
WorkerEntry::Kind(kind) => kind,
WorkerEntry::Misuse => {
crate::diagnostic::to_stderr(&format!(
"mur {ENTRY} is an internal entry started by mur run; it is not a command"
));
return 2;
}
};
let count = std::env::vars_os().count();
if count != 0 {
crate::diagnostic::to_stderr(&format!(
"mur {ENTRY} {kind}: refused to start: its environment holds {count} variable(s); a \
guest worker starts with an empty environment",
kind = kind.as_str()
));
return 2;
}
let Some(stream) = connection_on_fd3() else {
crate::diagnostic::to_stderr(&format!(
"mur {ENTRY} {kind}: refused to start: fd 3 is not its connection; it is an internal \
entry started by mur run",
kind = kind.as_str()
));
return 2;
};
PLACEMENT.store(PLACEMENT_WORKER, Ordering::SeqCst);
worker::serve(kind, stream)
}
#[allow(unsafe_code)]
fn connection_on_fd3() -> Option<std::os::unix::net::UnixStream> {
use std::os::fd::FromRawFd;
let is_socket = unsafe {
let mut stat: libc::stat = std::mem::zeroed();
libc::fstat(3, &mut stat) == 0 && (stat.st_mode & libc::S_IFMT) == libc::S_IFSOCK
};
if !is_socket {
return None;
}
let stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(3) };
unsafe {
let flags = libc::fcntl(3, libc::F_GETFD);
if flags >= 0 {
libc::fcntl(3, libc::F_SETFD, flags | libc::FD_CLOEXEC);
}
}
Some(stream)
}
#[cfg(test)]
mod tests;