Gate terminal group cleanup on live authority
This commit is contained in:
parent
4dc5daa57b
commit
c5ed009fb8
1 changed files with 355 additions and 48 deletions
|
|
@ -57,6 +57,12 @@ pub enum TerminalLoss {
|
|||
HostCoordinatorLost,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum GuestReleaseAuthority {
|
||||
Validate,
|
||||
HostOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TerminalError {
|
||||
Io(io::Error),
|
||||
|
|
@ -279,6 +285,57 @@ impl HostCoordinatorTerminalRecorder {
|
|||
Ok(route)
|
||||
}
|
||||
|
||||
fn require_guest_signal_authority(
|
||||
&self,
|
||||
request: &TerminalAcquisition,
|
||||
terminal: &SessionTerminal,
|
||||
) -> Result<(), TerminalError> {
|
||||
let snapshot = self.store.snapshot()?;
|
||||
let current = snapshot
|
||||
.generations
|
||||
.iter()
|
||||
.all(|candidate| candidate.generation_id <= request.generation_id);
|
||||
let generation = snapshot.generations.iter().any(|generation| {
|
||||
generation.generation_id == request.generation_id
|
||||
&& generation.coordinator_id == request.coordinator_id
|
||||
&& generation.durable_decision == DurableDecision::Published
|
||||
});
|
||||
let session = snapshot.sessions.iter().any(|session| {
|
||||
session.generation_id == request.generation_id
|
||||
&& session.session_id == request.session_id
|
||||
&& matches!(
|
||||
session.session_phase,
|
||||
SessionPhase::Attached | SessionPhase::Closed
|
||||
)
|
||||
});
|
||||
let sandbox = snapshot.participants.iter().any(|participant| {
|
||||
participant.generation_id() == request.generation_id
|
||||
&& participant.role() == ParticipantRole::Sandbox
|
||||
&& participant.state() == ParticipantState::Committed
|
||||
&& matches!(
|
||||
participant.ownership(),
|
||||
Some(ParticipantOwnership::Sandbox(sandbox))
|
||||
if sandbox.container_id == Some(request.container_id)
|
||||
&& sandbox.host_coordinator.serviceable_pidfs().ok()
|
||||
== Some(&request.host_coordinator)
|
||||
)
|
||||
});
|
||||
let terminal = snapshot.session_terminals.iter().any(|record| {
|
||||
record == terminal && record.state() == SessionTerminalState::ReleaseIntent
|
||||
});
|
||||
if snapshot.domain.domain_id == request.domain_id
|
||||
&& current
|
||||
&& generation
|
||||
&& session
|
||||
&& sandbox
|
||||
&& terminal
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TerminalError::AuthorityMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_release(&self, terminal: SessionTerminal) -> Result<(), TerminalError> {
|
||||
let snapshot = self.store.snapshot()?;
|
||||
let mut session = snapshot
|
||||
|
|
@ -426,7 +483,13 @@ impl SessionTerminalEngine {
|
|||
SessionTerminalEvent::Close(event) => event.session_id,
|
||||
};
|
||||
if matches!(event, SessionTerminalEvent::Close(_)) {
|
||||
return self.close(session_id, generation_id, None, true);
|
||||
return self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
None,
|
||||
true,
|
||||
GuestReleaseAuthority::Validate,
|
||||
);
|
||||
}
|
||||
let authoritative = self.refresh_route(session_id, generation_id)?;
|
||||
if authoritative != TerminalRoute::Sandbox {
|
||||
|
|
@ -478,7 +541,13 @@ impl SessionTerminalEngine {
|
|||
let authoritative = self.refresh_route(session_id, generation_id)?;
|
||||
let terminal = self.active_mut(session_id, generation_id)?;
|
||||
if terminal.process.exec_exited()? {
|
||||
return self.close(session_id, generation_id, None, true);
|
||||
return self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
None,
|
||||
true,
|
||||
GuestReleaseAuthority::Validate,
|
||||
);
|
||||
}
|
||||
if authoritative != TerminalRoute::Sandbox || terminal.releasing {
|
||||
return Ok(Vec::new());
|
||||
|
|
@ -486,13 +555,25 @@ impl SessionTerminalEngine {
|
|||
flush_input(terminal)?;
|
||||
let mut bytes = [0_u8; MAX_OUTPUT_BYTES];
|
||||
match terminal.process.read_output(&mut bytes) {
|
||||
Ok(0) => self.close(session_id, generation_id, Some(TerminalLoss::PtyLost), true),
|
||||
Ok(0) => self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
Some(TerminalLoss::PtyLost),
|
||||
true,
|
||||
GuestReleaseAuthority::Validate,
|
||||
),
|
||||
Ok(length) => Ok(vec![SessionTerminalMessage::Output(TerminalOutput {
|
||||
session_id,
|
||||
bytes: bytes[..length].to_vec(),
|
||||
})]),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(Vec::new()),
|
||||
Err(_) => self.close(session_id, generation_id, Some(TerminalLoss::PtyLost), true),
|
||||
Err(_) => self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
Some(TerminalLoss::PtyLost),
|
||||
true,
|
||||
GuestReleaseAuthority::Validate,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -520,7 +601,13 @@ impl SessionTerminalEngine {
|
|||
loss,
|
||||
TerminalLoss::ClientDisconnected | TerminalLoss::HostCoordinatorLost
|
||||
);
|
||||
self.close(session_id, generation_id, Some(loss), connected)
|
||||
self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
Some(loss),
|
||||
connected,
|
||||
GuestReleaseAuthority::Validate,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -533,21 +620,46 @@ impl SessionTerminalEngine {
|
|||
session_id: SessionId,
|
||||
generation_id: GenerationId,
|
||||
) -> Result<TerminalRoute, TerminalError> {
|
||||
let active_generation = self
|
||||
.active
|
||||
.get(&session_id)
|
||||
.ok_or(TerminalError::UnknownSession)?
|
||||
.generation_id;
|
||||
if active_generation != generation_id {
|
||||
let cleanup = self.close(
|
||||
session_id,
|
||||
active_generation,
|
||||
Some(TerminalLoss::ExecClientLost),
|
||||
false,
|
||||
GuestReleaseAuthority::HostOnly,
|
||||
);
|
||||
return Err(with_cleanup(TerminalError::StaleGeneration, cleanup));
|
||||
}
|
||||
let request = {
|
||||
let terminal = self.active_mut(session_id, generation_id)?;
|
||||
terminal.request.clone()
|
||||
};
|
||||
match self.recorder.require_authority(&request) {
|
||||
Ok(TerminalRoute::Detached) | Err(TerminalError::AuthorityMismatch) => {
|
||||
Ok(TerminalRoute::Detached) => {
|
||||
let cleanup = self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
Some(TerminalLoss::ExecClientLost),
|
||||
false,
|
||||
GuestReleaseAuthority::HostOnly,
|
||||
);
|
||||
Err(with_cleanup(TerminalError::AuthorityMismatch, cleanup))
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
Err(error) => {
|
||||
let cleanup = self.close(
|
||||
session_id,
|
||||
generation_id,
|
||||
Some(TerminalLoss::ExecClientLost),
|
||||
false,
|
||||
GuestReleaseAuthority::HostOnly,
|
||||
);
|
||||
Err(with_cleanup(error, cleanup))
|
||||
}
|
||||
Ok(route) => {
|
||||
self.active_mut(session_id, generation_id)?.route = route;
|
||||
Ok(route)
|
||||
|
|
@ -576,15 +688,36 @@ impl SessionTerminalEngine {
|
|||
generation_id: GenerationId,
|
||||
loss: Option<TerminalLoss>,
|
||||
connected: bool,
|
||||
authority: GuestReleaseAuthority,
|
||||
) -> Result<Vec<SessionTerminalMessage>, TerminalError> {
|
||||
let active_generation = self
|
||||
.active
|
||||
.get(&session_id)
|
||||
.ok_or(TerminalError::UnknownSession)?
|
||||
.generation_id;
|
||||
if active_generation != generation_id {
|
||||
let cleanup = self.close(
|
||||
session_id,
|
||||
active_generation,
|
||||
loss,
|
||||
false,
|
||||
GuestReleaseAuthority::HostOnly,
|
||||
);
|
||||
return Err(with_cleanup(TerminalError::StaleGeneration, cleanup));
|
||||
}
|
||||
let mut terminal = self
|
||||
.active
|
||||
.remove(&session_id)
|
||||
.ok_or(TerminalError::UnknownSession)?;
|
||||
if terminal.generation_id != generation_id {
|
||||
self.active.insert(session_id, terminal);
|
||||
return Err(TerminalError::StaleGeneration);
|
||||
}
|
||||
let was_serviceable = matches!(authority, GuestReleaseAuthority::Validate)
|
||||
&& (matches!(
|
||||
self.recorder.require_authority(&terminal.request),
|
||||
Ok(TerminalRoute::Sandbox | TerminalRoute::Prompt | TerminalRoute::HostProcess)
|
||||
) || (terminal.record.state() == SessionTerminalState::ReleaseIntent
|
||||
&& self
|
||||
.recorder
|
||||
.require_guest_signal_authority(&terminal.request, &terminal.record)
|
||||
.is_ok()));
|
||||
let result = (|| {
|
||||
terminal.releasing = true;
|
||||
terminal.route = TerminalRoute::Detached;
|
||||
|
|
@ -594,14 +727,23 @@ impl SessionTerminalEngine {
|
|||
terminal.record = release;
|
||||
self.cancellation.cancel_for_close(session_id)?;
|
||||
|
||||
let (mut output, exit) = release_physical(
|
||||
let mut authorize_guest = || {
|
||||
was_serviceable
|
||||
&& self
|
||||
.recorder
|
||||
.require_guest_signal_authority(&terminal.request, &terminal.record)
|
||||
.is_ok()
|
||||
};
|
||||
let (mut output, exit, guest_absent) = release_physical(
|
||||
terminal.process.as_mut(),
|
||||
session_id,
|
||||
connected,
|
||||
!matches!(loss, Some(TerminalLoss::HostCoordinatorLost)),
|
||||
&mut authorize_guest,
|
||||
)?;
|
||||
let clean = clean_terminal(generation_id, session_id)?;
|
||||
self.recorder.clean(clean)?;
|
||||
if guest_absent {
|
||||
let clean = clean_terminal(generation_id, session_id)?;
|
||||
self.recorder.clean(clean)?;
|
||||
}
|
||||
if connected {
|
||||
output.push(SessionTerminalMessage::Exit(exit_message(
|
||||
session_id, loss, exit,
|
||||
|
|
@ -620,11 +762,16 @@ fn release_physical(
|
|||
process: &mut dyn SessionTerminalProcess,
|
||||
session_id: SessionId,
|
||||
connected: bool,
|
||||
signal_guest: bool,
|
||||
) -> Result<(Vec<SessionTerminalMessage>, ProcessExit), TerminalError> {
|
||||
authorize_guest: &mut dyn FnMut() -> bool,
|
||||
) -> Result<(Vec<SessionTerminalMessage>, ProcessExit, bool), TerminalError> {
|
||||
let mut failure = None;
|
||||
if signal_guest {
|
||||
let mut guest_authorized = authorize_guest();
|
||||
if guest_authorized {
|
||||
for signal in ["HUP", "TERM", "KILL"] {
|
||||
if !authorize_guest() {
|
||||
guest_authorized = false;
|
||||
break;
|
||||
}
|
||||
match process.group_absent() {
|
||||
Ok(true) => break,
|
||||
Ok(false) => {}
|
||||
|
|
@ -633,6 +780,10 @@ fn release_physical(
|
|||
break;
|
||||
}
|
||||
}
|
||||
if !authorize_guest() {
|
||||
guest_authorized = false;
|
||||
break;
|
||||
}
|
||||
if let Err(error) = process.signal_group(signal) {
|
||||
failure.get_or_insert(error);
|
||||
break;
|
||||
|
|
@ -685,14 +836,19 @@ fn release_physical(
|
|||
}
|
||||
}
|
||||
process.close_pty();
|
||||
match process.group_absent() {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
failure.get_or_insert(TerminalError::GuestGroupStillPresent);
|
||||
}
|
||||
Err(error) => {
|
||||
failure.get_or_insert(error);
|
||||
if guest_authorized && authorize_guest() {
|
||||
match process.group_absent() {
|
||||
Ok(true) if authorize_guest() => {}
|
||||
Ok(true) => guest_authorized = false,
|
||||
Ok(false) => {
|
||||
failure.get_or_insert(TerminalError::GuestGroupStillPresent);
|
||||
}
|
||||
Err(error) => {
|
||||
failure.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
guest_authorized = false;
|
||||
}
|
||||
if let Some(error) = failure {
|
||||
return Err(error);
|
||||
|
|
@ -700,6 +856,7 @@ fn release_physical(
|
|||
Ok((
|
||||
output,
|
||||
exit.ok_or_else(|| io::Error::other("exec client exit is unavailable"))?,
|
||||
guest_authorized,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -951,13 +1108,17 @@ fn cleanup_failed_running_commit(
|
|||
PersistedHostProcessOwnership::committed(exec_client),
|
||||
Some(guest),
|
||||
)?;
|
||||
let durable = recorder.put(observed.clone()).and_then(|()| {
|
||||
recorder.put(copy_terminal_state(
|
||||
&observed,
|
||||
SessionTerminalState::ReleaseIntent,
|
||||
)?)
|
||||
});
|
||||
let physical = release_physical(process, request.session_id, false, true).map(|_| ());
|
||||
let release = copy_terminal_state(&observed, SessionTerminalState::ReleaseIntent)?;
|
||||
let durable = recorder
|
||||
.put(observed)
|
||||
.and_then(|()| recorder.put(release.clone()));
|
||||
let mut authorize_guest = || {
|
||||
recorder
|
||||
.require_guest_signal_authority(request, &release)
|
||||
.is_ok()
|
||||
};
|
||||
let physical = release_physical(process, request.session_id, false, &mut authorize_guest)
|
||||
.map(|(_, _, guest_absent)| guest_absent);
|
||||
match (durable, physical) {
|
||||
(Err(durable), Err(physical)) => {
|
||||
return Err(TerminalError::AcquisitionCleanup {
|
||||
|
|
@ -965,8 +1126,9 @@ fn cleanup_failed_running_commit(
|
|||
cleanup: Some(Box::new(physical)),
|
||||
});
|
||||
}
|
||||
(Err(error), Ok(())) | (Ok(()), Err(error)) => return Err(error),
|
||||
(Ok(()), Ok(())) => {}
|
||||
(Err(error), Ok(_)) | (Ok(()), Err(error)) => return Err(error),
|
||||
(Ok(()), Ok(false)) => return Ok(()),
|
||||
(Ok(()), Ok(true)) => {}
|
||||
}
|
||||
recorder.clean(clean_terminal(request.generation_id, request.session_id)?)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue