Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support on windows #61

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
65a99e8
Rm `cfg(unix)` requirement for the crate
NobodyXu Mar 9, 2022
7e16a54
Only enable feature `native-mux` on unix
NobodyXu Mar 9, 2022
cefcd0f
Enable the current mod `stdio` only on unix
NobodyXu Mar 9, 2022
97b1951
Enable unix-only crates only on unix
NobodyXu Mar 9, 2022
699cef2
Fix compilation of feature `native-mux` on windows
NobodyXu Mar 9, 2022
1d8ad8b
Fix integration tests on windows
NobodyXu Mar 9, 2022
1f63d1a
Fix feature `native-mux` for all non-unix platforms
NobodyXu Mar 9, 2022
b2c2e30
Impl `stdio/windows.rs`
NobodyXu Mar 10, 2022
ca33611
Rename `process_impl/session.rs` to `session-unix.rs`
NobodyXu Mar 10, 2022
049a6a2
Fix unix-only function/field in `SessionBuilder`
NobodyXu Mar 11, 2022
5926a00
Impl `process_impl/session-windows.rs`
NobodyXu Mar 11, 2022
d6cfd02
Simplify `SessionBuilder::apply_options`
NobodyXu Mar 11, 2022
9a7a999
Run workflow `test` on windows
NobodyXu Mar 11, 2022
706c830
Update crate doc for windows support
NobodyXu Mar 11, 2022
200208f
Revert changes made to workflow `test`
NobodyXu Mar 11, 2022
949c8a9
Enable workflow `os-check` to run on `windows-latest`
NobodyXu Mar 11, 2022
04df280
Fix `impl_from_impl_child_io!` in `stdio/windows.rs`
NobodyXu Mar 11, 2022
8408e35
Fix undeclared crates in windows CI build
NobodyXu Mar 11, 2022
469dfac
Fix bug in `impl_child_stdio!` in `stdio/windows.rs`
NobodyXu Mar 11, 2022
c7c1b20
Fix unused import in `lib.rs` in windows CI
NobodyXu Mar 11, 2022
5effcc2
Fix unused imports in mod `builder` in windows CI
NobodyXu Mar 11, 2022
2428948
Fix unused imports in `process_impl/session-windows.rs`
NobodyXu Mar 11, 2022
1c7526e
Fix unused imports in `port_forwarding` in win CI
NobodyXu Mar 11, 2022
111418e
Enable `Socket::UnixSocket` for windows
NobodyXu Mar 11, 2022
4c9029c
Fix `SessionBuilder::connect` for win
NobodyXu Mar 11, 2022
c2d29bd
Fix win impl `Session::parse_stderr`
NobodyXu Mar 11, 2022
f6d0c7e
Refactor `session-windows.rs`: Rm fn `parse_stderr`
NobodyXu Mar 11, 2022
5ba0379
Rm unnecessary `mut` in win impl of `Session::close`
NobodyXu Mar 11, 2022
8276988
Fix `SessionBuilder::apply_options`: Make it `pub(crate)`
NobodyXu Mar 11, 2022
494ab04
Fix err parsing in win impl of `Session::request_port_forward`
NobodyXu Mar 11, 2022
46a31e9
FIx `impl_try_from_child_io_for_stdio` in `stdio/windows.rs`
NobodyXu Mar 11, 2022
4889340
Fix code style in `process_impl/session-windows.rs`
NobodyXu Mar 11, 2022
69252fe
Fix `SessionBuilder::connect`: Fix win impl
NobodyXu Mar 11, 2022
6b6dc0d
Fix `FromRawHandle` for `Stdio` in Windows impl
NobodyXu Mar 11, 2022
94943ca
Fix unused `Error::interpret_ssh_error` in Win CI
NobodyXu Mar 11, 2022
4afaf42
Rm unused func `Session::check` in Win impl
NobodyXu Mar 11, 2022
efc14c4
Add dep `tempfile` to `dev-dependencies`
NobodyXu Mar 11, 2022
bb1c9b0
Fix `examples/native-mux_tsp.rs` in Win CI
NobodyXu Mar 11, 2022
d996c3e
Fix `tests/openssh.rs` on Win CI
NobodyXu Mar 11, 2022
3dd6688
Fix unused imports in `tests/openssh.rs` in Win CI
NobodyXu Mar 11, 2022
c94729b
Fix `examples/native-mux_tsp.rs` in Win CI
NobodyXu Mar 11, 2022
272299f
Fix `error::tests::parse_error`: Only execute on Unix
NobodyXu Mar 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/os-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest]
os: [macos-latest, windows-latest]
steps:
- uses: actions-rs/toolchain@v1
with:
Expand Down
24 changes: 20 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,39 @@ process-mux = []
native-mux = ["openssh-mux-client"]

[dependencies]
tempfile = "3.1.0"
shell-escape = "0.1.5"
tokio = { version = "1", features = [ "process", "io-util", "macros" ] }
tokio-pipe = "0.2.8"

[target.'cfg(unix)'.dependencies]
# tempfile is used on unix to create tempdir for control socket
tempfile = "3.1.0"

# dirs is used on unix to decide the default prefix for tempdir
dirs = "4.0.0"

# libc is used on unix for function `dup`
libc = "0.2.112"
io-lifetimes = "0.5"

# once_cell is used on unix to cache the default prefix for tempdir
# and used in feature `native-mux` to cache `/dev/null` fd.
once_cell = "1.8.0"
dirs = "4.0.0"

# io-lifetime is used on unix to store fd in `Stdio`
io-lifetimes = "0.5"

# tokio-pipe is used on unix to provide `ChildStdin`, `ChildStdout` and `ChildStderr`.
tokio-pipe = "0.2.8"

openssh-mux-client = { version = "0.14.4", optional = true }

[target.'cfg(not(unix))'.dependencies]
openssh-mux-client = { package = "blank", version = "0.1.0", optional = true }

[dev-dependencies]
lazy_static = "1.4.0"
regex = "1"
tokio = { version = "1", features = [ "full" ] }
tempfile = "3.1.0"

[[example]]
name = "native-mux_tsp"
Expand Down
6 changes: 6 additions & 0 deletions examples/native-mux_tsp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#[cfg(unix)]
use openssh::*;

#[tokio::main]
#[cfg(windows)]
async fn main() {}

#[tokio::main]
#[cfg(unix)]
async fn main() {
let session = Session::connect_mux("ssh://[email protected]:222", KnownHosts::Strict)
.await
Expand Down
132 changes: 79 additions & 53 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ use super::{Error, Session};
#[cfg(feature = "process-mux")]
use super::process_impl;

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
use super::native_mux_impl;

use std::borrow::Cow;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::str;

use dirs::state_dir;
use once_cell::sync::OnceCell;
use tempfile::{Builder, TempDir};
use tokio::process;

#[cfg(unix)]
use std::fs;

#[cfg(unix)]
use std::process::Stdio;

#[cfg(unix)]
use tempfile::{Builder, TempDir};

/// The returned `&'static Path` can be coreced to any lifetime.
#[cfg(unix)]
fn get_default_control_dir<'a>() -> Result<&'a Path, Error> {
use dirs::state_dir;
use once_cell::sync::OnceCell;

static DEFAULT_CONTROL_DIR: OnceCell<Option<Box<Path>>> = OnceCell::new();

DEFAULT_CONTROL_DIR
Expand All @@ -42,13 +50,15 @@ fn get_default_control_dir<'a>() -> Result<&'a Path, Error> {
/// Build a [`Session`] with options.
#[derive(Debug, Clone)]
pub struct SessionBuilder {
#[cfg(unix)]
control_dir: Option<PathBuf>,

user: Option<String>,
port: Option<String>,
keyfile: Option<PathBuf>,
connect_timeout: Option<String>,
server_alive_interval: Option<u64>,
known_hosts_check: KnownHosts,
control_dir: Option<PathBuf>,
config_file: Option<PathBuf>,
compression: Option<bool>,
user_known_hosts_file: Option<Box<Path>>,
Expand All @@ -57,13 +67,15 @@ pub struct SessionBuilder {
impl Default for SessionBuilder {
fn default() -> Self {
Self {
#[cfg(unix)]
control_dir: None,

user: None,
port: None,
keyfile: None,
connect_timeout: None,
server_alive_interval: None,
known_hosts_check: KnownHosts::Add,
control_dir: None,
config_file: None,
compression: None,
user_known_hosts_file: None,
Expand Down Expand Up @@ -187,8 +199,17 @@ impl SessionBuilder {
pub async fn connect<S: AsRef<str>>(&self, destination: S) -> Result<Session, Error> {
let destination = destination.as_ref();
let (builder, destination) = self.resolve(destination);
let tempdir = builder.launch_master(destination).await?;
Ok(process_impl::Session::new(tempdir, destination).into())

#[cfg(unix)]
{
let tempdir = builder.launch_master(destination).await?;
Ok(process_impl::Session::new(tempdir, destination).into())
}

#[cfg(windows)]
{
Ok(process_impl::Session::new(builder.into_owned(), destination).into())
}
}

/// Connect to the host at the given `host` over SSH using native mux, which will
Expand All @@ -204,8 +225,8 @@ impl SessionBuilder {
/// If connecting requires interactive authentication based on `STDIN` (such as reading a
/// password), the connection will fail. Consider setting up keypair-based authentication
/// instead.
#[cfg(feature = "native-mux")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))]
#[cfg(all(feature = "native-mux", unix))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "native-mux", unix))))]
pub async fn connect_mux<S: AsRef<str>>(&self, destination: S) -> Result<Session, Error> {
let destination = destination.as_ref();
let (builder, destination) = self.resolve(destination);
Expand Down Expand Up @@ -251,77 +272,82 @@ impl SessionBuilder {
(Cow::Owned(with_overrides), destination)
}

async fn launch_master(&self, destination: &str) -> Result<TempDir, Error> {
let socketdir = if let Some(socketdir) = self.control_dir.as_ref() {
socketdir
} else {
get_default_control_dir()?
};

let dir = Builder::new()
.prefix(".ssh-connection")
.tempdir_in(socketdir)
.map_err(Error::Master)?;

let log = dir.path().join("log");

let mut init = process::Command::new("ssh");

init.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("-E")
.arg(&log)
.arg("-S")
.arg(dir.path().join("master"))
.arg("-M")
.arg("-f")
.arg("-N")
.arg("-o")
.arg("ControlPersist=yes")
.arg("-o")
.arg("BatchMode=yes")
.arg("-o")
.arg(self.known_hosts_check.as_option());
pub(crate) fn apply_options(&self, cmd: &mut process::Command) {
cmd.arg("-o").arg(self.known_hosts_check.as_option());

if let Some(ref timeout) = self.connect_timeout {
init.arg("-o").arg(format!("ConnectTimeout={}", timeout));
cmd.arg("-o").arg(format!("ConnectTimeout={}", timeout));
}

if let Some(ref interval) = self.server_alive_interval {
init.arg("-o")
cmd.arg("-o")
.arg(format!("ServerAliveInterval={}", interval));
}

if let Some(ref port) = self.port {
init.arg("-p").arg(port);
cmd.arg("-p").arg(port);
}

if let Some(ref user) = self.user {
init.arg("-l").arg(user);
cmd.arg("-l").arg(user);
}

if let Some(ref k) = self.keyfile {
// if the user gives a keyfile, _only_ use that keyfile
init.arg("-o").arg("IdentitiesOnly=yes");
init.arg("-i").arg(k);
cmd.arg("-o").arg("IdentitiesOnly=yes");
cmd.arg("-i").arg(k);
}

if let Some(ref config_file) = self.config_file {
init.arg("-F").arg(config_file);
cmd.arg("-F").arg(config_file);
}

if let Some(compression) = self.compression {
let arg = if compression { "yes" } else { "no" };

init.arg("-o").arg(format!("Compression={}", arg));
cmd.arg("-o").arg(format!("Compression={}", arg));
}

if let Some(user_known_hosts_file) = &self.user_known_hosts_file {
let mut option: OsString = "UserKnownHostsFile=".into();
option.push(&**user_known_hosts_file);
init.arg("-o").arg(option);
cmd.arg("-o").arg(option);
}
}

#[cfg(not(windows))]
async fn launch_master(&self, destination: &str) -> Result<TempDir, Error> {
let socketdir = if let Some(socketdir) = self.control_dir.as_ref() {
socketdir
} else {
get_default_control_dir()?
};

let dir = Builder::new()
.prefix(".ssh-connection")
.tempdir_in(socketdir)
.map_err(Error::Master)?;

let log = dir.path().join("log");

let mut init = process::Command::new("ssh");

init.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("-E")
.arg(&log)
.arg("-S")
.arg(dir.path().join("master"))
.arg("-M")
.arg("-f")
.arg("-N")
.arg("-o")
.arg("ControlPersist=yes")
.arg("-o")
.arg("BatchMode=yes");

self.apply_options(&mut init);

init.arg(destination);

Expand Down
6 changes: 3 additions & 3 deletions src/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) enum RemoteChildImp {
#[cfg(feature = "process-mux")]
ProcessImpl(super::process_impl::RemoteChild),

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
NativeMuxImpl(super::native_mux_impl::RemoteChild),
}
#[cfg(feature = "process-mux")]
Expand All @@ -21,7 +21,7 @@ impl From<super::process_impl::RemoteChild> for RemoteChildImp {
}
}

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
impl From<super::native_mux_impl::RemoteChild> for RemoteChildImp {
fn from(imp: super::native_mux_impl::RemoteChild) -> Self {
RemoteChildImp::NativeMuxImpl(imp)
Expand All @@ -34,7 +34,7 @@ macro_rules! delegate {
#[cfg(feature = "process-mux")]
RemoteChildImp::ProcessImpl($var) => $then,

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
RemoteChildImp::NativeMuxImpl($var) => $then,
}
}};
Expand Down
8 changes: 4 additions & 4 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) enum CommandImp {
#[cfg(feature = "process-mux")]
ProcessImpl(super::process_impl::Command),

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
NativeMuxImpl(super::native_mux_impl::Command),
}
#[cfg(feature = "process-mux")]
Expand All @@ -22,7 +22,7 @@ impl From<super::process_impl::Command> for CommandImp {
}
}

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
impl<'s> From<super::native_mux_impl::Command> for CommandImp {
fn from(imp: super::native_mux_impl::Command) -> Self {
CommandImp::NativeMuxImpl(imp)
Expand All @@ -36,13 +36,13 @@ macro_rules! delegate {
#[cfg(feature = "process-mux")]
CommandImp::ProcessImpl($var) => $then,

#[cfg(feature = "native-mux")]
#[cfg(all(feature = "native-mux", unix))]
CommandImp::NativeMuxImpl($var) => $then,
}
}};
}

#[cfg(not(any(feature = "process-mux", feature = "native-mux")))]
#[cfg(not(any(feature = "process-mux", all(feature = "native-mux", unix))))]
macro_rules! delegate {
($impl:expr, $var:ident, $then:block) => {{
unreachable!("Neither feature process-mux nor native-mux is enabled")
Expand Down
Loading