From 0c32963f1cb7ca03d91527b092aaedcb40fe7e38 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Fri, 2 Oct 2020 23:35:18 -0700 Subject: [PATCH] Move server to its own wezterm-mux-server binary --- Cargo.lock | 35 ++- Cargo.toml | 3 +- codec/Cargo.toml | 3 + config/src/frontend.rs | 6 +- config/src/unix.rs | 11 +- src/frontend/mod.rs | 13 +- src/frontend/muxserver/mod.rs | 62 ----- src/main.rs | 7 +- src/server/listener/mod.rs | 34 --- src/server/mod.rs | 5 +- umask/Cargo.toml | 10 + .../listener/umask.rs => umask/src/lib.rs | 0 wezterm-mux-server/Cargo.toml | 35 +++ .../src}/clientsession.rs | 4 +- .../src}/local.rs | 4 +- wezterm-mux-server/src/main.rs | 218 ++++++++++++++++++ .../src}/ossl.rs | 0 .../src}/pki.rs | 0 wezterm-mux-server/src/pollable.rs | 131 +++++++++++ .../src}/sessionhandler.rs | 6 +- 20 files changed, 454 insertions(+), 133 deletions(-) delete mode 100644 src/frontend/muxserver/mod.rs delete mode 100644 src/server/listener/mod.rs create mode 100644 umask/Cargo.toml rename src/server/listener/umask.rs => umask/src/lib.rs (100%) create mode 100644 wezterm-mux-server/Cargo.toml rename {src/server/listener => wezterm-mux-server/src}/clientsession.rs (97%) rename {src/server/listener => wezterm-mux-server/src}/local.rs (97%) create mode 100644 wezterm-mux-server/src/main.rs rename {src/server/listener => wezterm-mux-server/src}/ossl.rs (100%) rename {src/server/listener => wezterm-mux-server/src}/pki.rs (100%) create mode 100644 wezterm-mux-server/src/pollable.rs rename {src/server/listener => wezterm-mux-server/src}/sessionhandler.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index de074fd05..1f8a8b273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,6 +651,7 @@ name = "codec" version = "0.1.0" dependencies = [ "anyhow", + "base91", "config", "leb128", "log", @@ -3711,6 +3712,13 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "umask" +version = "0.1.0" +dependencies = [ + "libc", +] + [[package]] name = "unicase" version = "2.6.0" @@ -4111,7 +4119,6 @@ dependencies = [ "async-task 1.3.1", "async-trait", "base64 0.10.1", - "base91", "bitflags 1.2.1", "bstr 0.2.13", "cc", @@ -4174,6 +4181,7 @@ dependencies = [ "tinyvec", "toml", "uds_windows", + "umask", "unicode-normalization", "unicode-segmentation", "unicode-width", @@ -4185,6 +4193,31 @@ dependencies = [ "winrt-notification", ] +[[package]] +name = "wezterm-mux-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "codec", + "config", + "crossbeam", + "daemonize", + "filedescriptor", + "hostname", + "log", + "mux", + "openssl", + "portable-pty", + "pretty_env_logger", + "promise", + "rangeset", + "rcgen", + "structopt", + "umask", + "url", + "wezterm-term", +] + [[package]] name = "wezterm-term" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 481cbea2d..c635d04f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ async-trait = "0.1" anyhow = "1.0" thiserror = "1.0" base64 = "0.10" -base91 = { path = "base91" } rangeset = { path = "rangeset" } bitflags = "1.0" bstr = "0.2" @@ -72,6 +71,7 @@ termwiz = { path = "termwiz" } textwrap = "0.11" tinyvec = "0.3" toml = "0.4" +umask = { path = "umask" } unicode-normalization = "0.1" unicode-segmentation = "1.6" unicode-width = "0.1" @@ -123,6 +123,7 @@ default = ["vendor_openssl"] vendor_openssl = ["openssl/vendored"] [workspace] +members = ["wezterm-mux-server"] [profile.release] opt-level = 3 diff --git a/codec/Cargo.toml b/codec/Cargo.toml index a6fbbc906..53c240dfa 100644 --- a/codec/Cargo.toml +++ b/codec/Cargo.toml @@ -20,3 +20,6 @@ termwiz = { path = "../termwiz" } varbincode = "0.1" wezterm-term = { path = "../term", features=["use_serde"] } zstd = "0.4" + +[dev-dependencies] +base91 = { path = "../base91" } diff --git a/config/src/frontend.rs b/config/src/frontend.rs index 77725edd2..b13bebf24 100644 --- a/config/src/frontend.rs +++ b/config/src/frontend.rs @@ -5,8 +5,6 @@ pub enum FrontEndSelection { OpenGL, Software, OldSoftware, - MuxServer, - Null, } impl_lua_conversion!(FrontEndSelection); @@ -19,7 +17,7 @@ impl Default for FrontEndSelection { impl FrontEndSelection { // TODO: find or build a proc macro for this pub fn variants() -> Vec<&'static str> { - vec!["OpenGL", "Software", "OldSoftware", "MuxServer", "Null"] + vec!["OpenGL", "Software", "OldSoftware"] } } @@ -27,8 +25,6 @@ impl std::str::FromStr for FrontEndSelection { type Err = Error; fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { - "muxserver" => Ok(FrontEndSelection::MuxServer), - "null" => Ok(FrontEndSelection::Null), "software" => Ok(FrontEndSelection::Software), "oldsoftware" => Ok(FrontEndSelection::OldSoftware), "opengl" => Ok(FrontEndSelection::OpenGL), diff --git a/config/src/unix.rs b/config/src/unix.rs index a47ab8370..fad81a4d4 100644 --- a/config/src/unix.rs +++ b/config/src/unix.rs @@ -24,9 +24,9 @@ pub struct UnixDomain { /// If we decide that we need to start the server, the command to run /// to set that up. The default is to spawn: - /// `wezterm start --daemonize --front-end MuxServer` + /// `wezterm-mux-server --daemonize` /// but it can be useful to set this to eg: - /// `wsl -e wezterm --daemonize --front-end MuxServer` to start up + /// `wsl -e wezterm-mux-server --daemonize` to start up /// a unix domain inside a wsl container. pub serve_command: Option>, @@ -66,11 +66,10 @@ impl UnixDomain { match self.serve_command.as_ref() { Some(cmd) => Ok(cmd.iter().map(Into::into).collect()), None => Ok(vec![ - std::env::current_exe()?.into_os_string(), - OsString::from("start"), + std::env::current_exe()? + .with_file_name("wezterm-mux-server") + .into_os_string(), OsString::from("--daemonize"), - OsString::from("--front-end"), - OsString::from("MuxServer"), ]), } } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 5e14baa63..9d8efb912 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; pub mod gui; -pub mod muxserver; pub use config::FrontEndSelection; @@ -37,18 +36,16 @@ pub fn shutdown() { } pub fn try_new(sel: FrontEndSelection) -> Result, Error> { - let (front_end, is_gui) = match sel { - FrontEndSelection::MuxServer => (muxserver::MuxServerFrontEnd::try_new(), false), - FrontEndSelection::Null => (muxserver::MuxServerFrontEnd::new_null(), false), - FrontEndSelection::Software => (gui::GuiFrontEnd::try_new_swrast(), true), - FrontEndSelection::OldSoftware => (gui::GuiFrontEnd::try_new_no_opengl(), true), - FrontEndSelection::OpenGL => (gui::GuiFrontEnd::try_new(), true), + let front_end = match sel { + FrontEndSelection::Software => gui::GuiFrontEnd::try_new_swrast(), + FrontEndSelection::OldSoftware => gui::GuiFrontEnd::try_new_no_opengl(), + FrontEndSelection::OpenGL => gui::GuiFrontEnd::try_new(), }; let front_end = front_end?; FRONT_END.with(|f| *f.borrow_mut() = Some(Rc::clone(&front_end))); - HAS_GUI_FRONT_END.store(is_gui, Ordering::Release); + HAS_GUI_FRONT_END.store(true, Ordering::Release); Ok(front_end) } diff --git a/src/frontend/muxserver/mod.rs b/src/frontend/muxserver/mod.rs deleted file mode 100644 index 99f376a51..000000000 --- a/src/frontend/muxserver/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Implements the multiplexer server frontend -use crate::frontend::FrontEnd; -use crate::server::listener::spawn_listener; -use anyhow::{bail, Error}; -use crossbeam::channel::{unbounded as channel, Receiver}; -use log::info; -use mux::Mux; -use promise::*; -use std::rc::Rc; - -pub struct MuxServerFrontEnd { - rx: Receiver, -} - -impl MuxServerFrontEnd { - #[cfg_attr(feature = "cargo-clippy", allow(clippy::new_ret_no_self))] - fn new(start_listener: bool) -> Result, Error> { - let (tx, rx) = channel(); - - let tx_main = tx.clone(); - let tx_low = tx.clone(); - let queue_func = move |f: SpawnFunc| { - tx_main.send(f).ok(); - }; - let queue_func_low = move |f: SpawnFunc| { - tx_low.send(f).ok(); - }; - promise::spawn::set_schedulers( - Box::new(move |task| queue_func(Box::new(move || task.run()))), - Box::new(move |task| queue_func_low(Box::new(move || task.run()))), - ); - - if start_listener { - spawn_listener()?; - } - Ok(Rc::new(Self { rx })) - } - - pub fn try_new() -> Result, Error> { - Self::new(true) - } - - pub fn new_null() -> Result, Error> { - Self::new(false) - } -} - -impl FrontEnd for MuxServerFrontEnd { - fn run_forever(&self) -> Result<(), Error> { - loop { - match self.rx.recv() { - Ok(func) => func(), - Err(err) => bail!("while waiting for events: {:?}", err), - } - - if Mux::get().unwrap().is_empty() && mux::activity::Activity::count() == 0 { - info!("No more tabs; all done!"); - return Ok(()); - } - } - } -} diff --git a/src/main.rs b/src/main.rs index cf89f7396..3a1b796b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ // Don't create a new standard console window when launched from the windows GUI. #![windows_subsystem = "windows"] -use crate::server::listener::umask; use anyhow::{anyhow, bail}; use config::{wezterm_version, SshParameters}; use mux::domain::{Domain, LocalDomain}; @@ -574,8 +573,7 @@ fn run_terminal_gui(config: config::ConfigHandle, opts: StartCommand) -> anyhow: let front_end_selection = opts.front_end.unwrap_or(config.front_end); let gui = crate::frontend::try_new(front_end_selection)?; let activity = Activity::new(); - let do_auto_connect = - front_end_selection != FrontEndSelection::MuxServer && !opts.no_auto_connect; + let do_auto_connect = !opts.no_auto_connect; promise::spawn::spawn(async move { if let Err(err) = async_run_terminal_gui(cmd, do_auto_connect).await { @@ -838,8 +836,6 @@ fn run() -> anyhow::Result<()> { SubCommand::ImageCat(cmd) => cmd.run(), SubCommand::Cli(cli) => { // Start a front end so that the futures executor is running - let front_end = crate::frontend::try_new(FrontEndSelection::Null)?; - let initial = true; let mut ui = crate::connui::ConnectionUI::new_headless(); let client = Client::new_default_unix_domain(initial, &mut ui)?; @@ -971,7 +967,6 @@ fn run() -> anyhow::Result<()> { let stdin = std::io::stdin(); consume_stream_then_exit_process(stdin.lock(), stream); }); - front_end.run_forever()?; } CliSubCommand::TlsCreds => { let creds = block_on(client.get_tls_creds())?; diff --git a/src/server/listener/mod.rs b/src/server/listener/mod.rs deleted file mode 100644 index 784a5b90e..000000000 --- a/src/server/listener/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use anyhow::{anyhow, bail, Context, Error}; -use config::{configuration, TlsDomainServer}; -use log::error; -use promise::spawn::spawn_into_main_thread; -use std::net::TcpListener; -use std::path::Path; -use std::sync::Arc; -use std::thread; - -mod clientsession; -mod local; -mod ossl; -mod pki; -mod sessionhandler; -pub mod umask; - -lazy_static::lazy_static! { - static ref PKI: pki::Pki = pki::Pki::init().expect("failed to initialize PKI"); -} - -pub fn spawn_listener() -> anyhow::Result<()> { - let config = configuration(); - for unix_dom in &config.unix_domains { - let mut listener = local::LocalListener::with_domain(unix_dom)?; - thread::spawn(move || { - listener.run(); - }); - } - - for tls_server in &config.tls_servers { - ossl::spawn_tls_listener(tls_server)?; - } - Ok(()) -} diff --git a/src/server/mod.rs b/src/server/mod.rs index aad8e6e54..5091a9fbe 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,10 +1,9 @@ #[cfg(unix)] -use std::os::unix::net::{UnixListener, UnixStream}; +use std::os::unix::net::UnixStream; #[cfg(windows)] -use uds_windows::{UnixListener, UnixStream}; +use uds_windows::UnixStream; pub mod client; pub mod domain; -pub mod listener; pub mod pollable; pub mod tab; diff --git a/umask/Cargo.toml b/umask/Cargo.toml new file mode 100644 index 000000000..11cc5287f --- /dev/null +++ b/umask/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "umask" +version = "0.1.0" +authors = ["Wez Furlong "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2" diff --git a/src/server/listener/umask.rs b/umask/src/lib.rs similarity index 100% rename from src/server/listener/umask.rs rename to umask/src/lib.rs diff --git a/wezterm-mux-server/Cargo.toml b/wezterm-mux-server/Cargo.toml new file mode 100644 index 000000000..490ec360c --- /dev/null +++ b/wezterm-mux-server/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "wezterm-mux-server" +version = "0.1.0" +authors = ["Wez Furlong "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +codec = { path = "../codec" } +config = { path = "../config" } +crossbeam = "0.7" +filedescriptor = { version="0.7", path = "../filedescriptor" } +hostname = "0.3" +log = "0.4" +mux = { path = "../mux" } +openssl = "0.10" +portable-pty = { path = "../pty", features = ["serde_support"]} +pretty_env_logger = "0.4" +promise = { path = "../promise" } +rangeset = { path = "../rangeset" } +rcgen = "0.7" +structopt = "0.3" +umask = { path = "../umask" } +url = "2" +wezterm-term = { path = "../term", features=["use_serde"] } + +[target.'cfg(unix)'.dependencies] +daemonize = { git = "https://github.com/wez/daemonize" } + +[features] +default = ["vendor_openssl"] +# FIXME: find a way to magically disable vendor_openssl only on linux! +vendor_openssl = ["openssl/vendored"] diff --git a/src/server/listener/clientsession.rs b/wezterm-mux-server/src/clientsession.rs similarity index 97% rename from src/server/listener/clientsession.rs rename to wezterm-mux-server/src/clientsession.rs index e963a4466..91015a268 100644 --- a/src/server/listener/clientsession.rs +++ b/wezterm-mux-server/src/clientsession.rs @@ -1,5 +1,5 @@ -use crate::server::listener::sessionhandler::SessionHandler; -use crate::server::pollable::*; +use crate::pollable::*; +use crate::sessionhandler::SessionHandler; use anyhow::{bail, Context, Error}; use codec::*; use crossbeam::channel::TryRecvError; diff --git a/src/server/listener/local.rs b/wezterm-mux-server/src/local.rs similarity index 97% rename from src/server/listener/local.rs rename to wezterm-mux-server/src/local.rs index 785eaf1ec..0f58cf72a 100644 --- a/src/server/listener/local.rs +++ b/wezterm-mux-server/src/local.rs @@ -1,5 +1,5 @@ -use crate::server::listener::clientsession; -use crate::server::UnixListener; +use crate::clientsession; +use crate::UnixListener; use anyhow::{anyhow, Context as _}; use config::{create_user_owned_dirs, UnixDomain}; use promise::spawn::spawn_into_main_thread; diff --git a/wezterm-mux-server/src/main.rs b/wezterm-mux-server/src/main.rs new file mode 100644 index 000000000..5f5c85298 --- /dev/null +++ b/wezterm-mux-server/src/main.rs @@ -0,0 +1,218 @@ +use anyhow::{anyhow, bail, Context, Error}; +use config::{configuration, TlsDomainServer}; +use crossbeam::channel::unbounded as channel; +use log::error; +use mux::activity::Activity; +use mux::domain::{Domain, LocalDomain}; +use mux::Mux; +use portable_pty::cmdbuilder::CommandBuilder; +use promise::spawn::spawn_into_main_thread; +use promise::*; +use std::ffi::OsString; +use std::net::TcpListener; +use std::path::Path; +use std::rc::Rc; +use std::sync::Arc; +use std::thread; +use structopt::*; + +#[cfg(unix)] +use std::os::unix::net::{UnixListener, UnixStream}; +#[cfg(windows)] +use uds_windows::{UnixListener, UnixStream}; + +#[derive(Debug, StructOpt)] +#[structopt( + about = "Wez's Terminal Emulator\nhttp://github.com/wez/wezterm", + global_setting = structopt::clap::AppSettings::ColoredHelp, + version = config::wezterm_version() +)] +struct Opt { + /// Skip loading wezterm.lua + #[structopt(short = "n")] + skip_config: bool, + + /// Detach from the foreground and become a background process + #[structopt(long = "daemonize")] + daemonize: bool, + + /// Specify the current working directory for the initially + /// spawned program + #[structopt(long = "cwd", parse(from_os_str))] + cwd: Option, + + /// Instead of executing your shell, run PROG. + /// For example: `wezterm start -- bash -l` will spawn bash + /// as if it were a login shell. + #[structopt(parse(from_os_str))] + prog: Vec, +} + +fn main() -> anyhow::Result<()> { + pretty_env_logger::init_timed(); + //stats::Stats::init()?; + let _saver = umask::UmaskSaver::new(); + + let opts = Opt::from_args(); + if !opts.skip_config { + config::reload(); + } + let config = config::configuration(); + + #[cfg(unix)] + { + if opts.daemonize { + let stdout = config.daemon_options.open_stdout()?; + let stderr = config.daemon_options.open_stderr()?; + let mut daemonize = daemonize::Daemonize::new() + .stdout(stdout) + .stderr(stderr) + .working_directory(config::HOME_DIR.clone()); + + if !config::running_under_wsl() { + // pid file locking is only partly function when running under + // WSL 1; it is possible for the pid file to exist after a reboot + // and for attempts to open and lock it to fail when there are no + // other processes that might possibly hold a lock on it. + // So, we only use a pid file when not under WSL. + daemonize = daemonize.pid_file(config.daemon_options.pid_file()); + } + if let Err(err) = daemonize.start() { + use daemonize::DaemonizeError; + match err { + DaemonizeError::OpenPidfile + | DaemonizeError::LockPidfile(_) + | DaemonizeError::ChownPidfile(_) + | DaemonizeError::WritePid => { + bail!("{} {}", err, config.daemon_options.pid_file().display()); + } + DaemonizeError::ChangeDirectory => { + bail!("{} {}", err, config::HOME_DIR.display()); + } + _ => return Err(err.into()), + } + } + + // Remove some environment variables that aren't super helpful or + // that are potentially misleading when we're starting up the + // server. + // We may potentially want to look into starting/registering + // a session of some kind here as well in the future. + for name in &[ + "OLDPWD", + "PWD", + "SHLVL", + "SSH_AUTH_SOCK", + "SSH_CLIENT", + "SSH_CONNECTION", + "_", + ] { + std::env::remove_var(name); + } + } + } + + let need_builder = !opts.prog.is_empty() || opts.cwd.is_some(); + + let cmd = if need_builder { + let mut builder = if opts.prog.is_empty() { + CommandBuilder::new_default_prog() + } else { + CommandBuilder::from_argv(opts.prog) + }; + if let Some(cwd) = opts.cwd { + builder.cwd(cwd); + } + Some(builder) + } else { + None + }; + + let domain: Arc = Arc::new(LocalDomain::new("local")?); + let mux = Rc::new(mux::Mux::new(Some(domain.clone()))); + Mux::set_mux(&mux); + + let (tx, rx) = channel(); + + let tx_main = tx.clone(); + let tx_low = tx.clone(); + let queue_func = move |f: SpawnFunc| { + tx_main.send(f).ok(); + }; + let queue_func_low = move |f: SpawnFunc| { + tx_low.send(f).ok(); + }; + promise::spawn::set_schedulers( + Box::new(move |task| queue_func(Box::new(move || task.run()))), + Box::new(move |task| queue_func_low(Box::new(move || task.run()))), + ); + + spawn_listener()?; + + let activity = Activity::new(); + + promise::spawn::spawn(async move { + if let Err(err) = async_run(cmd).await { + terminate_with_error(err); + } + drop(activity); + }); + + loop { + match rx.recv() { + Ok(func) => func(), + Err(err) => bail!("while waiting for events: {:?}", err), + } + + if Mux::get().unwrap().is_empty() && mux::activity::Activity::count() == 0 { + log::info!("No more tabs; all done!"); + return Ok(()); + } + } +} + +async fn async_run(cmd: Option) -> anyhow::Result<()> { + let mux = Mux::get().unwrap(); + + let domain = mux.default_domain(); + domain.attach().await?; + + let config = config::configuration(); + let window_id = mux.new_empty_window(); + let _tab = mux + .default_domain() + .spawn(config.initial_size(), cmd, None, *window_id) + .await?; + Ok(()) +} + +fn terminate_with_error(err: anyhow::Error) -> ! { + log::error!("{:#}; terminating", err); + std::process::exit(1); +} + +mod clientsession; +mod local; +mod ossl; +mod pki; +mod pollable; +mod sessionhandler; + +lazy_static::lazy_static! { + static ref PKI: pki::Pki = pki::Pki::init().expect("failed to initialize PKI"); +} + +pub fn spawn_listener() -> anyhow::Result<()> { + let config = configuration(); + for unix_dom in &config.unix_domains { + let mut listener = local::LocalListener::with_domain(unix_dom)?; + thread::spawn(move || { + listener.run(); + }); + } + + for tls_server in &config.tls_servers { + ossl::spawn_tls_listener(tls_server)?; + } + Ok(()) +} diff --git a/src/server/listener/ossl.rs b/wezterm-mux-server/src/ossl.rs similarity index 100% rename from src/server/listener/ossl.rs rename to wezterm-mux-server/src/ossl.rs diff --git a/src/server/listener/pki.rs b/wezterm-mux-server/src/pki.rs similarity index 100% rename from src/server/listener/pki.rs rename to wezterm-mux-server/src/pki.rs diff --git a/wezterm-mux-server/src/pollable.rs b/wezterm-mux-server/src/pollable.rs new file mode 100644 index 000000000..be5cac37e --- /dev/null +++ b/wezterm-mux-server/src/pollable.rs @@ -0,0 +1,131 @@ +use crate::UnixStream; +use anyhow::Error; +use crossbeam::channel::{unbounded as channel, Receiver, Sender, TryRecvError}; +use filedescriptor::*; +use std::cell::RefCell; +use std::io::{Read, Write}; +use std::net::TcpStream; +use std::sync::{Arc, Mutex}; + +pub trait ReadAndWrite: std::io::Read + std::io::Write + Send + AsPollFd { + fn set_non_blocking(&self, non_blocking: bool) -> anyhow::Result<()>; + fn has_read_buffered(&self) -> bool; +} +impl ReadAndWrite for UnixStream { + fn set_non_blocking(&self, non_blocking: bool) -> anyhow::Result<()> { + self.set_nonblocking(non_blocking)?; + Ok(()) + } + fn has_read_buffered(&self) -> bool { + false + } +} + +impl ReadAndWrite for openssl::ssl::SslStream { + fn set_non_blocking(&self, non_blocking: bool) -> anyhow::Result<()> { + self.get_ref().set_nonblocking(non_blocking)?; + Ok(()) + } + fn has_read_buffered(&self) -> bool { + self.ssl().pending() != 0 + } +} + +pub struct PollableSender { + sender: Sender, + write: Arc>, +} + +impl PollableSender { + pub fn send(&self, item: T) -> anyhow::Result<()> { + // Attempt to write to the pipe; if it fails due to + // being full, that's fine: it means that the other end + // is going to be signalled already so they won't miss + // anything if our write doesn't happen. + self.write.lock().unwrap().write_all(b"x").ok(); + self.sender.send(item).map_err(Error::msg)?; + Ok(()) + } +} + +impl Clone for PollableSender { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + write: self.write.clone(), + } + } +} + +pub struct PollableReceiver { + receiver: Receiver, + read: RefCell, +} + +impl PollableReceiver { + pub fn try_recv(&self) -> Result { + // try to drain the pipe. + // We do this regardless of whether we popped an item + // so that we avoid being in a perpetually signalled state. + let mut byte = [0u8; 64]; + self.read.borrow_mut().read(&mut byte).ok(); + + Ok(self.receiver.try_recv()?) + } +} + +impl AsPollFd for PollableReceiver { + fn as_poll_fd(&self) -> pollfd { + self.read.borrow().as_socket_descriptor().as_poll_fd() + } +} + +/// A channel that can be polled together with a socket. +/// This uses the self-pipe trick but with a unix domain +/// socketpair. +/// In theory this should also work on windows, but will require +/// windows 10 w/unix domain socket support. +pub fn pollable_channel() -> anyhow::Result<(PollableSender, PollableReceiver)> { + let (sender, receiver) = channel(); + let (mut write, mut read) = socketpair()?; + + write.set_non_blocking(true)?; + read.set_non_blocking(true)?; + + Ok(( + PollableSender { + sender, + write: Arc::new(Mutex::new(FileDescriptor::new(write))), + }, + PollableReceiver { + receiver, + read: RefCell::new(FileDescriptor::new(read)), + }, + )) +} + +pub trait AsPollFd { + fn as_poll_fd(&self) -> pollfd; +} + +impl AsPollFd for SocketDescriptor { + fn as_poll_fd(&self) -> pollfd { + pollfd { + fd: *self, + events: POLLIN, + revents: 0, + } + } +} + +impl AsPollFd for openssl::ssl::SslStream { + fn as_poll_fd(&self) -> pollfd { + self.get_ref().as_socket_descriptor().as_poll_fd() + } +} + +impl AsPollFd for UnixStream { + fn as_poll_fd(&self) -> pollfd { + self.as_socket_descriptor().as_poll_fd() + } +} diff --git a/src/server/listener/sessionhandler.rs b/wezterm-mux-server/src/sessionhandler.rs similarity index 99% rename from src/server/listener/sessionhandler.rs rename to wezterm-mux-server/src/sessionhandler.rs index feeb707d2..9d27060d4 100644 --- a/src/server/listener/sessionhandler.rs +++ b/wezterm-mux-server/src/sessionhandler.rs @@ -1,5 +1,5 @@ -use crate::server::listener::PKI; -use crate::server::pollable::*; +use crate::pollable::*; +use crate::PKI; use anyhow::anyhow; use codec::*; use config::keyassignment::SpawnTabDomain; @@ -447,7 +447,7 @@ impl SessionHandler { Pdu::GetCodecVersion(_) => { send_response(Ok(Pdu::GetCodecVersionResponse(GetCodecVersionResponse { codec_vers: CODEC_VERSION, - version_string: crate::wezterm_version().to_owned(), + version_string: config::wezterm_version().to_owned(), }))) }