use std::ffi::{CStr, CString, c_char, c_int};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use super::{
AuthAdapterError, AuthWorkerEndpoint, AuthWorkerPool, DedicatedChildWaitDomain,
DirectChildAuthWorkerAuthority, DirectChildAuthWorkerError, DirectChildState, ExactAuthWorker,
FreshAuthWorkerGeneration,
};
use crate::backend::macos::supervisor::deployer_helper_path;
use crate::backend::macos::supervisor::spawn_primitives::{
SpawnAttributes, SpawnFileActions, spawn,
};
pub(super) const INSTALLED_AUTH_WORKER_MODE: &str = "--supervisor-auth-worker";
pub(super) const INSTALLED_AUTH_WORKER_REQUEST_ARGUMENT: &str = "--request-fd=3";
pub(super) const INSTALLED_AUTH_WORKER_RESULT_ARGUMENT: &str = "--result-fd=4";
const CANONICAL_PATH: &str = "PATH=/usr/bin:/bin";
const CANONICAL_LANG: &str = "LANG=C";
const CANONICAL_LOCALE: &str = "LC_ALL=C";
pub(super) const AUTH_WORKER_REQUEST_FD: c_int = 3;
pub(super) const AUTH_WORKER_RESULT_FD: c_int = 4;
const STABLE_FD_MINIMUM: c_int = 10;
const F_SETFL: c_int = 4;
const F_SETFD: c_int = 2;
const F_DUPFD_CLOEXEC: c_int = 67;
const F_SETNOSIGPIPE: c_int = 73;
const FD_CLOEXEC: c_int = 1;
const O_NONBLOCK: c_int = 0x0000_0004;
const O_RDONLY: c_int = 0;
const O_WRONLY: c_int = 1;
const ECHILD: c_int = 10;
const DEV_NULL: &CStr = c"/dev/null";
unsafe extern "C" {
fn fcntl(fd: c_int, command: c_int, ...) -> c_int;
fn pipe(descriptors: *mut c_int) -> c_int;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::backend::macos::supervisor) enum AuthWorkerSpawnError {
InvalidFixedImage,
InvalidWaitDomain,
Pipe(c_int),
Descriptor(c_int),
FileActions(c_int),
Attributes(c_int),
Spawn(c_int),
}
pub(in crate::backend::macos::supervisor) struct InstalledAuthWorkerImage {
spawn_path: CString,
argument0: CString,
mode: CString,
request_argument: CString,
result_argument: CString,
environment_path: CString,
environment_lang: CString,
environment_locale: CString,
}
impl InstalledAuthWorkerImage {
pub(in crate::backend::macos::supervisor) unsafe fn from_verified_installation(
path: &CStr,
) -> Result<Self, AuthWorkerSpawnError> {
let spawn_path =
deployer_helper_path(path).ok_or(AuthWorkerSpawnError::InvalidFixedImage)?;
Ok(Self {
argument0: spawn_path.clone(),
spawn_path,
mode: fixed_cstring(INSTALLED_AUTH_WORKER_MODE)?,
request_argument: fixed_cstring(INSTALLED_AUTH_WORKER_REQUEST_ARGUMENT)?,
result_argument: fixed_cstring(INSTALLED_AUTH_WORKER_RESULT_ARGUMENT)?,
environment_path: fixed_cstring(CANONICAL_PATH)?,
environment_lang: fixed_cstring(CANONICAL_LANG)?,
environment_locale: fixed_cstring(CANONICAL_LOCALE)?,
})
}
fn argv(&self) -> [*mut c_char; 5] {
[
self.argument0.as_ptr().cast_mut(),
self.mode.as_ptr().cast_mut(),
self.request_argument.as_ptr().cast_mut(),
self.result_argument.as_ptr().cast_mut(),
std::ptr::null_mut(),
]
}
fn environment(&self) -> [*mut c_char; 4] {
[
self.environment_path.as_ptr().cast_mut(),
self.environment_lang.as_ptr().cast_mut(),
self.environment_locale.as_ptr().cast_mut(),
std::ptr::null_mut(),
]
}
}
#[must_use = "a spawned authentication worker must enter its exact pool slot"]
pub(in crate::backend::macos::supervisor) struct SpawnedAuthWorker {
generation: FreshAuthWorkerGeneration,
worker: ExactAuthWorker<DirectChildAuthWorkerAuthority>,
endpoint: AuthWorkerEndpoint,
}
impl SpawnedAuthWorker {
fn into_parts(
self,
) -> (
FreshAuthWorkerGeneration,
ExactAuthWorker<DirectChildAuthWorkerAuthority>,
AuthWorkerEndpoint,
) {
(self.generation, self.worker, self.endpoint)
}
}
impl AuthWorkerPool<DirectChildAuthWorkerAuthority> {
pub(in crate::backend::macos::supervisor) fn from_spawned_workers(
workers: Vec<SpawnedAuthWorker>,
) -> Result<Self, AuthAdapterError<DirectChildAuthWorkerError>> {
Self::from_precreated_workers(
workers
.into_iter()
.map(SpawnedAuthWorker::into_parts)
.collect(),
)
}
pub(super) fn install_spawned_replacement(
&mut self,
slot_index: u8,
spawned: SpawnedAuthWorker,
) -> Result<super::AuthWorkerIdentity, AuthAdapterError<DirectChildAuthWorkerError>> {
let (generation, worker, endpoint) = spawned.into_parts();
self.install_replacement(slot_index, generation, worker, endpoint)
}
}
pub(in crate::backend::macos::supervisor) fn spawn_installed_auth_worker(
image: &InstalledAuthWorkerImage,
generation: FreshAuthWorkerGeneration,
wait_domain: &mut DedicatedChildWaitDomain,
) -> Result<SpawnedAuthWorker, AuthWorkerSpawnError> {
wait_domain
.verify_single_threaded_spawn()
.map_err(|_| AuthWorkerSpawnError::InvalidWaitDomain)?;
let (request_reader, request_writer) = create_pipe_pair()?;
let (result_reader, result_writer) = create_pipe_pair()?;
set_nonblocking(request_writer.as_raw_fd())?;
set_nonblocking(result_reader.as_raw_fd())?;
set_nosigpipe(request_writer.as_raw_fd())?;
let mut actions = SpawnFileActions::new().map_err(AuthWorkerSpawnError::FileActions)?;
actions
.add_open(0, DEV_NULL, O_RDONLY, 0)
.map_err(AuthWorkerSpawnError::FileActions)?;
actions
.add_open(1, DEV_NULL, O_WRONLY, 0)
.map_err(AuthWorkerSpawnError::FileActions)?;
actions
.add_open(2, DEV_NULL, O_WRONLY, 0)
.map_err(AuthWorkerSpawnError::FileActions)?;
actions
.add_dup2(request_reader.as_raw_fd(), AUTH_WORKER_REQUEST_FD)
.map_err(AuthWorkerSpawnError::FileActions)?;
actions
.add_dup2(result_writer.as_raw_fd(), AUTH_WORKER_RESULT_FD)
.map_err(AuthWorkerSpawnError::FileActions)?;
for descriptor in [
request_reader.as_raw_fd(),
request_writer.as_raw_fd(),
result_reader.as_raw_fd(),
result_writer.as_raw_fd(),
] {
actions
.add_close(descriptor)
.map_err(AuthWorkerSpawnError::FileActions)?;
}
let mut attributes = SpawnAttributes::new().map_err(AuthWorkerSpawnError::Attributes)?;
attributes
.configure_canonical_signals()
.map_err(AuthWorkerSpawnError::Attributes)?;
wait_domain
.verify_single_threaded_spawn()
.map_err(|_| AuthWorkerSpawnError::InvalidWaitDomain)?;
let argv = image.argv();
let environment = image.environment();
let pid = unsafe {
spawn(
image.spawn_path.as_c_str(),
&actions,
&attributes,
&argv,
&environment,
)
}
.map_err(AuthWorkerSpawnError::Spawn)?;
if pid <= 0 {
std::process::abort();
}
let authority = DirectChildAuthWorkerAuthority {
pid,
state: DirectChildState::Unreaped,
};
let worker = unsafe { ExactAuthWorker::from_unreaped_direct_child(authority) };
let endpoint =
unsafe { AuthWorkerEndpoint::from_private_parent_pipe_ends(request_writer, result_reader) };
drop(request_reader);
drop(result_writer);
Ok(SpawnedAuthWorker {
generation,
worker,
endpoint,
})
}
fn fixed_cstring(value: &'static str) -> Result<CString, AuthWorkerSpawnError> {
CString::new(value).map_err(|_| AuthWorkerSpawnError::InvalidFixedImage)
}
fn create_pipe_pair() -> Result<(OwnedFd, OwnedFd), AuthWorkerSpawnError> {
let mut descriptors = [-1; 2];
if unsafe { pipe(descriptors.as_mut_ptr()) } != 0 {
return Err(AuthWorkerSpawnError::Pipe(last_errno()));
}
let reader = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
let writer = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
set_cloexec(reader.as_raw_fd())?;
set_cloexec(writer.as_raw_fd())?;
Ok((
duplicate_cloexec(reader.as_raw_fd())?,
duplicate_cloexec(writer.as_raw_fd())?,
))
}
fn duplicate_cloexec(fd: c_int) -> Result<OwnedFd, AuthWorkerSpawnError> {
let duplicate = unsafe { fcntl(fd, F_DUPFD_CLOEXEC, STABLE_FD_MINIMUM) };
if duplicate < 0 {
Err(AuthWorkerSpawnError::Descriptor(last_errno()))
} else {
Ok(unsafe { OwnedFd::from_raw_fd(duplicate) })
}
}
fn set_cloexec(fd: c_int) -> Result<(), AuthWorkerSpawnError> {
if unsafe { fcntl(fd, F_SETFD, FD_CLOEXEC) } == 0 {
Ok(())
} else {
Err(AuthWorkerSpawnError::Descriptor(last_errno()))
}
}
fn set_nonblocking(fd: c_int) -> Result<(), AuthWorkerSpawnError> {
if unsafe { fcntl(fd, F_SETFL, O_NONBLOCK) } == 0 {
Ok(())
} else {
Err(AuthWorkerSpawnError::Descriptor(last_errno()))
}
}
fn set_nosigpipe(fd: c_int) -> Result<(), AuthWorkerSpawnError> {
if unsafe { fcntl(fd, F_SETNOSIGPIPE, 1) } == 0 {
Ok(())
} else {
Err(AuthWorkerSpawnError::Descriptor(last_errno()))
}
}
fn last_errno() -> c_int {
std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(ECHILD)
}
#[cfg(test)]
#[path = "supervisor_auth_worker_spawn_test.rs"]
mod tests;