Add coordinator-owned interactive session terminals

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

View file

@ -17,8 +17,11 @@ use lazaret_linux::process::{
append_parent_identity, child_broker, current_process_identity, dispatch_private_process_role,
verify_direct_child_pidfd,
};
use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl};
use rustix::pipe::{PipeFlags, pipe_with};
use rustix::process::{PidfdFlags, pidfd_open};
use rustix::pty::{OpenptFlags, grantpt, ioctl_tiocgptpeer, openpt, unlockpt};
use rustix::termios::{Winsize, tcgetwinsize, tcsetwinsize};
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
@ -46,6 +49,7 @@ fn exit(result: std::io::Result<()>) -> ExitCode {
fn run_all() -> std::io::Result<()> {
successful_acquisition()?;
transient_captured_child_preserves_streams_and_exit()?;
terminal_child_restores_slave_stdin_and_merges_output()?;
acknowledged_transition_precedes_exact_wrapper_release()?;
failed_acknowledged_transition_refuses_exact_wrapper_release()?;
lifetime_pipe_descriptor_contract()?;
@ -57,6 +61,75 @@ fn run_all() -> std::io::Result<()> {
concurrent_acquisitions_serialize()
}
fn terminal_child_restores_slave_stdin_and_merges_output() -> std::io::Result<()> {
let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)?;
grantpt(&master)?;
unlockpt(&master)?;
let slave = ioctl_tiocgptpeer(
&master,
OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC,
)?;
tcsetwinsize(
&master,
Winsize {
ws_row: 44,
ws_col: 132,
ws_xpixel: 0,
ws_ypixel: 0,
},
)?;
if tcgetwinsize(&master)?.ws_row != 44 || tcgetwinsize(&master)?.ws_col != 132 {
return Err(std::io::Error::other(
"PTY resize did not update the master",
));
}
let flags = fcntl_getfl(&master)?;
fcntl_setfl(&master, flags | OFlags::NONBLOCK)?;
let slave = File::from(slave);
let target = TargetCommand::new("/bin/sh")
.argument("-c")
.argument("read value; printf 'out:%s\\n' \"$value\"; printf 'err\\n' >&2")
.with_target_stdin(slave.try_clone()?)?;
let expected_parent = current_process_identity()?;
let ownership = ProcessOwnershipContext {
domain_id: DomainId::new([41; 32]),
generation_id: GenerationId::new(42),
coordinator_id: CoordinatorId::new([43; 32]),
slot: HostProcessOwnershipSlot::SessionTerminalExecClient(
lazaret_core::identity::SessionId::new([44; 32]),
),
};
let process = child_broker().acquire_with_stdio(
expected_parent,
ownership,
&target,
ChildStandardIo::terminal(slave)?,
|_| Ok(()),
|staged| Ok(ProcessOwnershipCommitAck::v1(staged)),
)?;
let mut master = File::from(master);
master.write_all(b"hello\n")?;
let mut merged = Vec::new();
while !process.has_exited()? {
let _ = read_available(&mut master, &mut merged);
std::thread::yield_now();
}
let _ = read_available(&mut master, &mut merged);
let exit = child_broker().reap_status(process)?;
let text = String::from_utf8_lossy(&merged);
let out = text.find("out:hello");
let err = text.find("err");
if !matches!((out, err), (Some(out), Some(err)) if out < err)
|| exit.exit_code != Some(0)
|| exit.terminating_signal.is_some()
{
return Err(std::io::Error::other(format!(
"terminal stdio or merged ordering failed: {text:?}, {exit:?}"
)));
}
Ok(())
}
fn transient_captured_child_preserves_streams_and_exit() -> std::io::Result<()> {
let target = TargetCommand::new("/bin/sh")
.argument("-c")