Bind cold start to production authorities
This commit is contained in:
parent
a9c9cf7ab3
commit
b2f178d25e
2 changed files with 816 additions and 30 deletions
|
|
@ -328,6 +328,40 @@ impl ChildBroker {
|
|||
}
|
||||
}
|
||||
|
||||
/// Starts a non-durable direct child with split captured output and exact retained authority.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses invalid parent-edge or SIGCHLD state and cleans up on spawn, pidfd, barrier, or
|
||||
/// pipe-acquisition failure.
|
||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||
pub fn run_transient_captured(&self, target: &TargetCommand) -> io::Result<CapturedProcess> {
|
||||
let _guard = self
|
||||
.exclusion
|
||||
.lock()
|
||||
.map_err(|_| io::Error::other("child broker lock poisoned"))?;
|
||||
require_captured_parent_edge(target.parent_edge())?;
|
||||
require_default_sigchld()?;
|
||||
require_reapable_sigchld()?;
|
||||
let parent = current_process_identity()?;
|
||||
let (release, wait) = launch_barrier()?;
|
||||
let mut command = private_role_command(WRAPPER_ROLE);
|
||||
append_parent_identity(&mut command, parent);
|
||||
target.append_to(&mut command);
|
||||
command
|
||||
.stdin(wait.into_stdio())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
let mut child = command.spawn()?;
|
||||
let process = match acquire_spawned_child(child.id()) {
|
||||
Ok(process) => process,
|
||||
Err(error) => return fail_before_pidfd(release, &mut child, error),
|
||||
};
|
||||
if let Err(error) = release.release() {
|
||||
return fail_after_pidfd_closed(process, error);
|
||||
}
|
||||
captured_process(process, child)
|
||||
}
|
||||
|
||||
/// Spawns the repository wrapper, stages exact ownership, accepts its acknowledgment,
|
||||
/// and only then releases target execution.
|
||||
///
|
||||
|
|
@ -530,34 +564,7 @@ impl ChildBroker {
|
|||
if let Err(error) = release.release() {
|
||||
return fail_after_pidfd_closed(process, error);
|
||||
}
|
||||
let Some(stdout) = child.stdout.take() else {
|
||||
return fail_running_captured(
|
||||
process,
|
||||
child,
|
||||
io::Error::other("captured child stdout is unavailable"),
|
||||
);
|
||||
};
|
||||
let Some(stderr) = child.stderr.take() else {
|
||||
return fail_running_captured(
|
||||
process,
|
||||
child,
|
||||
io::Error::other("captured child stderr is unavailable"),
|
||||
);
|
||||
};
|
||||
let stdout = match child_pipe_file(stdout.into()) {
|
||||
Ok(stdout) => stdout,
|
||||
Err(error) => return fail_running_captured(process, child, error),
|
||||
};
|
||||
let stderr = match child_pipe_file(stderr.into()) {
|
||||
Ok(stderr) => stderr,
|
||||
Err(error) => return fail_running_captured(process, child, error),
|
||||
};
|
||||
drop(child);
|
||||
Ok(CapturedProcess {
|
||||
process,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
captured_process(process, child)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
|
|
@ -587,6 +594,41 @@ impl ChildBroker {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_types)]
|
||||
fn captured_process(
|
||||
process: RetainedProcess,
|
||||
mut child: std::process::Child,
|
||||
) -> io::Result<CapturedProcess> {
|
||||
let Some(stdout) = child.stdout.take() else {
|
||||
return fail_running_captured(
|
||||
process,
|
||||
child,
|
||||
io::Error::other("captured child stdout is unavailable"),
|
||||
);
|
||||
};
|
||||
let Some(stderr) = child.stderr.take() else {
|
||||
return fail_running_captured(
|
||||
process,
|
||||
child,
|
||||
io::Error::other("captured child stderr is unavailable"),
|
||||
);
|
||||
};
|
||||
let stdout = match child_pipe_file(stdout.into()) {
|
||||
Ok(stdout) => stdout,
|
||||
Err(error) => return fail_running_captured(process, child, error),
|
||||
};
|
||||
let stderr = match child_pipe_file(stderr.into()) {
|
||||
Ok(stderr) => stderr,
|
||||
Err(error) => return fail_running_captured(process, child, error),
|
||||
};
|
||||
drop(child);
|
||||
Ok(CapturedProcess {
|
||||
process,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
}
|
||||
|
||||
fn child_pipe_file(fd: OwnedFd) -> io::Result<File> {
|
||||
let flags = fcntl_getfl(fd.as_fd())?;
|
||||
fcntl_setfl(fd.as_fd(), flags | OFlags::NONBLOCK)?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, Write};
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::os::fd::AsFd;
|
||||
use std::process::{ExitCode, Stdio};
|
||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||
|
|
@ -45,6 +45,7 @@ fn exit(result: std::io::Result<()>) -> ExitCode {
|
|||
|
||||
fn run_all() -> std::io::Result<()> {
|
||||
successful_acquisition()?;
|
||||
transient_captured_child_preserves_streams_and_exit()?;
|
||||
acknowledged_transition_precedes_exact_wrapper_release()?;
|
||||
failed_acknowledged_transition_refuses_exact_wrapper_release()?;
|
||||
lifetime_pipe_descriptor_contract()?;
|
||||
|
|
@ -56,6 +57,47 @@ fn run_all() -> std::io::Result<()> {
|
|||
concurrent_acquisitions_serialize()
|
||||
}
|
||||
|
||||
fn transient_captured_child_preserves_streams_and_exit() -> std::io::Result<()> {
|
||||
let target = TargetCommand::new("/bin/sh")
|
||||
.argument("-c")
|
||||
.argument("printf transient-out; printf transient-err >&2; exit 7");
|
||||
let mut child = child_broker().run_transient_captured(&target)?;
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
loop {
|
||||
let (out, err) = child.streams();
|
||||
let out_done = read_available(out, &mut stdout)?;
|
||||
let err_done = read_available(err, &mut stderr)?;
|
||||
if out_done && err_done && child.process().has_exited()? {
|
||||
break;
|
||||
}
|
||||
std::thread::yield_now();
|
||||
}
|
||||
let exit = child_broker().reap_status(child.into_process())?;
|
||||
if stdout != b"transient-out"
|
||||
|| stderr != b"transient-err"
|
||||
|| exit.exit_code != Some(7)
|
||||
|| exit.terminating_signal.is_some()
|
||||
{
|
||||
return Err(std::io::Error::other(
|
||||
"transient captured child changed streams or exit",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_available(file: &mut File, output: &mut Vec<u8>) -> std::io::Result<bool> {
|
||||
let mut buffer = [0; 4096];
|
||||
loop {
|
||||
match file.read(&mut buffer) {
|
||||
Ok(0) => return Ok(true),
|
||||
Ok(length) => output.extend_from_slice(&buffer[..length]),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => return Ok(false),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||
fn failed_acknowledged_transition_refuses_exact_wrapper_release() -> std::io::Result<()> {
|
||||
let target_marker = marker("failed-transition-target");
|
||||
|
|
|
|||
Loading…
Reference in a new issue