Skip to content

Commit

Permalink
ssh: rewrite exit_behavior=Close -> CloseOnCleanExit during connection
Browse files Browse the repository at this point in the history
ssh connection or host authentication errors would not be displayed
when the config was in its default exit_behavior=Close state.

This commit introduces some plumbing to allow a pane to override
the effective exit_behavior value, so that ssh sessions can now
rewrite it to CloseOnCleanExit while they are in the process
of connecting.

refs: #3941
  • Loading branch information
wez committed Jul 6, 2023
1 parent 69bb69b commit 6be3f0e
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 19 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ As features stabilize some brief notes about them will accumulate here.
* Pasting large amounts of text in helix caused issues. #3683
* Wayland: Copying to clipboard was not always successful when triggered by the
keyboard. Thanks to @osandov! #3929
* `wezterm ssh` connection errors were not shown; the window would close
immediately with the default `exit_behavior` setting. Now ssh sessions change
`exit_behavior="Close"` to `exit_behavior="CloseOnCleanExit"` so that error
information can be displayed. #3941

#### Updated
* Bundled harfbuzz to 7.3.0
Expand Down
6 changes: 3 additions & 3 deletions mux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ fn read_from_pane_pty(
// or in the main mux thread. If `true`, this thread will terminate.
let dead = Arc::new(AtomicBool::new(false));

let pane_id = match pane.upgrade() {
Some(pane) => pane.pane_id(),
let (pane_id, exit_behavior) = match pane.upgrade() {
Some(pane) => (pane.pane_id(), pane.exit_behavior()),
None => return,
};

Expand Down Expand Up @@ -333,7 +333,7 @@ fn read_from_pane_pty(
}
}

match configuration().exit_behavior {
match exit_behavior.unwrap_or_else(|| configuration().exit_behavior) {
ExitBehavior::Hold | ExitBehavior::CloseOnCleanExit => {
// We don't know if we can unilaterally close
// this pane right now, so don't!
Expand Down
43 changes: 36 additions & 7 deletions mux/src/localpane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ enum ProcessState {
Running {
child_waiter: Receiver<IoResult<ExitStatus>>,
pid: Option<u32>,
signaller: Box<dyn ChildKiller + Send + Sync>,
signaller: Box<dyn ChildKiller + Sync>,
// Whether we've explicitly killed the child
killed: bool,
},
Expand Down Expand Up @@ -115,11 +115,17 @@ impl CachedLeaderInfo {
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LocalPaneConnectionState {
Connecting,
Connected,
}

pub struct LocalPane {
pane_id: PaneId,
terminal: Mutex<Terminal>,
process: Mutex<ProcessState>,
pty: Mutex<Box<dyn MasterPty + Send>>,
pty: Mutex<Box<dyn MasterPty>>,
writer: Mutex<Box<dyn Write + Send>>,
domain_id: DomainId,
tmux_domain: Mutex<Option<Arc<TmuxDomainState>>>,
Expand Down Expand Up @@ -213,6 +219,24 @@ impl Pane for LocalPane {
self.terminal.lock().user_vars().clone()
}

fn exit_behavior(&self) -> Option<ExitBehavior> {
// If we are ssh, and we've not yet fully connected,
// then override exit_behavior so that we can show
// connection issues
let is_ssh_connecting = self
.pty
.lock()
.downcast_mut::<crate::ssh::WrappedSshPty>()
.map(|s| s.is_connecting())
.unwrap_or(false);

if is_ssh_connecting {
Some(ExitBehavior::CloseOnCleanExit)
} else {
None
}
}

fn kill(&self) {
let mut proc = self.process.lock();
log::debug!(
Expand Down Expand Up @@ -263,9 +287,14 @@ impl Pane for LocalPane {
.contains(&status.exit_code()),
};

match (configuration().exit_behavior, success, killed) {
match (
self.exit_behavior()
.unwrap_or_else(|| configuration().exit_behavior),
success,
killed,
) {
(ExitBehavior::Close, _, _) => *proc = ProcessState::Dead,
(ExitBehavior::CloseOnCleanExit, false, false) => {
(ExitBehavior::CloseOnCleanExit, false, _) => {
notify = Some(format!(
"\r\n⚠️ Process {} didn't exit cleanly\r\n{}.\r\n{}=\"CloseOnCleanExit\"\r\n",
self.command_description,
Expand Down Expand Up @@ -901,10 +930,10 @@ impl AlertHandler for LocalPaneNotifHandler {
/// Without this, typing `exit` in `cmd.exe` would keep the pane around
/// until something else triggered the mux to prune dead processes.
fn split_child(
mut process: Box<dyn Child + Send>,
mut process: Box<dyn Child>,
) -> (
Receiver<IoResult<ExitStatus>>,
Box<dyn ChildKiller + Send + Sync>,
Box<dyn ChildKiller + Sync>,
Option<u32>,
) {
let pid = process.process_id();
Expand All @@ -930,7 +959,7 @@ impl LocalPane {
pane_id: PaneId,
mut terminal: Terminal,
process: Box<dyn Child + Send>,
pty: Box<dyn MasterPty + Send>,
pty: Box<dyn MasterPty>,
writer: Box<dyn Write + Send>,
domain_id: DomainId,
command_description: String,
Expand Down
5 changes: 5 additions & 0 deletions mux/src/pane.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::domain::DomainId;
use crate::renderable::*;
use crate::ExitBehavior;
use async_trait::async_trait;
use config::keyassignment::{KeyAssignment, ScrollbackEraseMode};
use downcast_rs::{impl_downcast, Downcast};
Expand Down Expand Up @@ -308,6 +309,10 @@ pub trait Pane: Downcast + Send + Sync {
fn tty_name(&self) -> Option<String> {
None
}

fn exit_behavior(&self) -> Option<ExitBehavior> {
None
}
}
impl_downcast!(Pane);

Expand Down
24 changes: 19 additions & 5 deletions mux/src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ struct WrappedSshChildKiller {
}

#[derive(Debug)]
struct WrappedSshChild {
pub(crate) struct WrappedSshChild {
status: Option<AsyncReceiver<ExitStatus>>,
rx: Receiver<SshChildProcess>,
exited: Option<ExitStatus>,
Expand All @@ -791,7 +791,7 @@ impl WrappedSshChild {
}
Err(TryRecvError::Empty) => {}
Err(err) => {
log::error!("WrappedSshChild err: {:#?}", err);
log::debug!("WrappedSshChild::check_connected err: {:#?}", err);
self.exited.replace(ExitStatus::with_exit_code(1));
}
}
Expand Down Expand Up @@ -836,7 +836,7 @@ impl portable_pty::Child for WrappedSshChild {
}
Err(smol::channel::TryRecvError::Empty) => Ok(None),
Err(err) => {
log::error!("WrappedSshChild err: {:#?}", err);
log::debug!("WrappedSshChild::try_wait err: {:#?}", err);
let status = ExitStatus::with_exit_code(1);
self.exited.replace(status.clone());
Ok(Some(status))
Expand All @@ -858,7 +858,7 @@ impl portable_pty::Child for WrappedSshChild {
self.got_child(c);
}
Err(err) => {
log::error!("WrappedSshChild err: {:#?}", err);
log::debug!("WrappedSshChild err: {:#?}", err);
let status = ExitStatus::with_exit_code(1);
self.exited.replace(status.clone());
return Ok(status);
Expand Down Expand Up @@ -926,10 +926,16 @@ impl ChildKiller for WrappedSshChildKiller {
type BoxedReader = Box<(dyn Read + Send + 'static)>;
type BoxedWriter = Box<(dyn Write + Send + 'static)>;

struct WrappedSshPty {
pub(crate) struct WrappedSshPty {
inner: RefCell<WrappedSshPtyInner>,
}

impl WrappedSshPty {
pub fn is_connecting(&mut self) -> bool {
self.inner.borrow_mut().is_connecting()
}
}

enum WrappedSshPtyInner {
Connecting {
reader: Option<PtyReader>,
Expand Down Expand Up @@ -975,6 +981,14 @@ impl WrappedSshPtyInner {
_ => Ok(()),
}
}

fn is_connecting(&mut self) -> bool {
self.check_connected().ok();
match self {
Self::Connecting { .. } => true,
Self::Connected { .. } => false,
}
}
}

impl portable_pty::MasterPty for WrappedSshPty {
Expand Down
2 changes: 1 addition & 1 deletion pty/src/cmdbuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ pub struct CommandBuilder {
}

impl CommandBuilder {
/// Create a new builder instance with argv[0] set to the specified
/// Create a new builder instance with argv\[0\] set to the specified
/// program.
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
Self {
Expand Down
9 changes: 6 additions & 3 deletions pty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Default for PtySize {
}

/// Represents the master/control end of the pty
pub trait MasterPty {
pub trait MasterPty: Downcast + Send {
/// Inform the kernel and thus the child process that the window resized.
/// It will update the winsize information maintained by the kernel,
/// and generate a signal for the child to notice and update its state.
Expand Down Expand Up @@ -123,10 +123,11 @@ pub trait MasterPty {
None
}
}
impl_downcast!(MasterPty);

/// Represents a child process spawned into the pty.
/// This handle can be used to wait for or terminate that child process.
pub trait Child: std::fmt::Debug + ChildKiller {
pub trait Child: std::fmt::Debug + ChildKiller + Downcast + Send {
/// Poll the child to see if it has completed.
/// Does not block.
/// Returns None if the child has not yet terminated,
Expand All @@ -143,9 +144,10 @@ pub trait Child: std::fmt::Debug + ChildKiller {
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
}
impl_downcast!(Child);

/// Represents the ability to signal a Child to terminate
pub trait ChildKiller: std::fmt::Debug {
pub trait ChildKiller: std::fmt::Debug + Downcast + Send {
/// Terminate the child process
fn kill(&mut self) -> IoResult<()>;

Expand All @@ -154,6 +156,7 @@ pub trait ChildKiller: std::fmt::Debug {
/// blocked in `.wait`.
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
}
impl_downcast!(ChildKiller);

/// Represents the slave side of a pty.
/// Can be used to spawn processes into the pty.
Expand Down

0 comments on commit 6be3f0e

Please sign in to comment.