Add coordinator-owned interactive session terminals

This commit is contained in:
ms2k 2026-07-17 05:33:22 +09:00
commit 51ff63a3aa

View file

@ -31,6 +31,7 @@ pub struct TargetCommand {
arguments: Vec<OsString>,
parent_edge: ParentEdge,
executable: Option<OwnedFd>,
stdin: Option<File>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -47,6 +48,7 @@ impl TargetCommand {
arguments: Vec::new(),
parent_edge: ParentEdge::ParentDeathKill,
executable: None,
stdin: None,
}
}
@ -64,6 +66,7 @@ impl TargetCommand {
arguments: arguments.into_iter().collect(),
parent_edge: ParentEdge::ParentDeathKill,
executable: Some(executable),
stdin: None,
})
}
@ -85,6 +88,16 @@ impl TargetCommand {
self
}
/// Attaches the target's standard input after the private wrapper consumes its barrier.
///
/// # Errors
/// Returns an error when the descriptor cannot be inherited by the wrapper.
pub fn with_target_stdin(mut self, stdin: File) -> io::Result<Self> {
fcntl_setfd(stdin.as_fd(), FdFlags::empty())?;
self.stdin = Some(stdin);
Ok(self)
}
#[must_use]
pub const fn parent_edge(&self) -> ParentEdge {
self.parent_edge
@ -109,6 +122,18 @@ impl TargetCommand {
ParentEdge::ParentDeathKill => "kill",
ParentEdge::LifetimePipe => "lifetime-pipe",
})
.args(
self.stdin
.as_ref()
.map(|stdin| {
[
OsString::from("--target-stdin-fd"),
OsString::from(stdin.as_raw_fd().to_string()),
]
})
.into_iter()
.flatten(),
)
.arg("--")
.arg(&self.program)
.args(&self.arguments);
@ -126,6 +151,7 @@ pub enum ChildStandardIoPolicy {
Inherit,
PrivateControlWithLifetimePipe,
Silenced,
Terminal,
}
impl ChildStandardIo {
@ -159,6 +185,17 @@ impl ChildStandardIo {
}
}
/// # Errors
/// Returns an error when the output descriptor cannot be duplicated for merged terminal I/O.
pub fn terminal(output: File) -> io::Result<Self> {
let error = output.try_clone()?;
Ok(Self {
stdout: Some(Stdio::from(output)),
stderr: Some(Stdio::from(error)),
policy: ChildStandardIoPolicy::Terminal,
})
}
#[must_use]
pub const fn policy(&self) -> ChildStandardIoPolicy {
self.policy
@ -296,7 +333,9 @@ fn require_parent_edge_standard_io(
(ParentEdge::LifetimePipe, ChildStandardIoPolicy::PrivateControlWithLifetimePipe)
| (
ParentEdge::ParentDeathKill,
ChildStandardIoPolicy::Inherit | ChildStandardIoPolicy::Silenced,
ChildStandardIoPolicy::Inherit
| ChildStandardIoPolicy::Silenced
| ChildStandardIoPolicy::Terminal,
) => Ok(()),
(ParentEdge::LifetimePipe, _) => Err(io::Error::other(
"lifetime-pipe parent edge requires private control stdout and lifetime-pipe stderr",
@ -778,6 +817,9 @@ fn run_wrapper_role(arguments: Vec<OsString>) -> io::Result<()> {
require_lifetime_pipe_and_recheck(expected_parent)?;
}
}
if let Some(stdin) = target.stdin.as_ref() {
rustix::stdio::dup2_stdin(stdin)?;
}
Err(exec_target(target))
}
@ -1245,9 +1287,22 @@ pub fn parse_wrapper_arguments(
Some("lifetime-pipe") => ParentEdge::LifetimePipe,
_ => return Err(io::Error::other("wrapper parent action is invalid")),
};
if arguments.next().as_deref() != Some(OsStr::new("--")) {
let next = arguments.next();
let stdin = if next.as_deref() == Some(OsStr::new("--target-stdin-fd")) {
let descriptor = arguments
.next()
.and_then(|value| value.to_str().and_then(|value| value.parse::<i32>().ok()))
.filter(|descriptor| *descriptor > 2)
.ok_or_else(|| io::Error::other("wrapper target stdin descriptor is invalid"))?;
if arguments.next().as_deref() != Some(OsStr::new("--")) {
return Err(io::Error::other("wrapper target separator is missing"));
}
Some(File::open(format!("/proc/self/fd/{descriptor}"))?)
} else if next.as_deref() == Some(OsStr::new("--")) {
None
} else {
return Err(io::Error::other("wrapper target separator is missing"));
}
};
let program = arguments
.next()
.ok_or_else(|| io::Error::other("wrapper target is missing"))?;
@ -1256,6 +1311,7 @@ pub fn parse_wrapper_arguments(
arguments: arguments.collect(),
parent_edge,
executable: None,
stdin,
};
let boot = parse_boot_hex(&boot)?;
Ok((