quick_file_transfer/
ssh.rs1use crate::{
2 config::{compression::Compression, transfer::util::TcpConnectMode, Config},
3 util::verbosity_to_args,
4};
5use anyhow::Result;
6use remote_info::RemoteInfo;
7use remote_session::RemoteSshSession;
8use std::{
9 path::{Path, PathBuf},
10 sync::atomic::{AtomicBool, Ordering},
11 thread::ScopedJoinHandle,
12 time::Duration,
13};
14
15#[cfg(feature = "mdns")]
16mod mdns_util;
17pub mod private_key;
18mod remote_cmd;
19pub mod remote_find_free_port;
20pub mod remote_info;
21pub mod remote_session;
22pub(crate) mod util;
23
24pub const ENV_REMOTE_PASSWORD: &str = "QFT_REMOTE_PASSWORD";
25pub const ENV_SSH_KEY_DIR: &str = "QFT_SSH_KEY_DIR";
26pub const ENV_SSH_PRIVATE_KEY: &str = "QFT_SSH_PRIVATE_KEY";
27
28pub const ENV_REMOTE_USER: &str = "QFT_REMOTE_USER";
29
30#[allow(clippy::too_many_arguments)]
31pub fn run_ssh(
32 cfg: &Config,
33 remote: &RemoteInfo,
34 private_key: Option<&Path>,
35 private_key_dir: Option<&Path>,
36 tcp_port: Option<u16>,
37 use_mmap: bool,
38 input_files: &[PathBuf],
39 prealloc: bool,
40 compression: &Option<Compression>,
41 start_port: u16,
42 end_port: u16,
43 ssh_timeout_ms: u64,
44 tcp_connect_mode: TcpConnectMode,
45) -> Result<()> {
46 log::debug!(
47 "Connecting to {remote_ip} as {user} with a timeout of {ssh_timeout_ms} ms",
48 remote_ip = remote.ip(),
49 user = remote.user(),
50 );
51 let mut session = RemoteSshSession::new(
52 remote.user(),
53 (remote.ip(), remote.ssh_port()),
54 Some(Duration::from_millis(ssh_timeout_ms)),
55 private_key,
56 private_key_dir,
57 )?;
58
59 let tcp_port = match tcp_port {
60 Some(tp) => tp,
61 None => session.find_free_port(start_port, end_port)?,
62 };
63
64 tracing::debug!("Using TCP port: {tcp_port}");
65
66 let remote_cmd = remote_cmd::remote_qft_command_str(tcp_port, verbosity_to_args(cfg));
67
68 tracing::info!("Sending remote qft command '{remote_cmd}'");
69
70 let server_ready_flag = AtomicBool::new(false);
71 let (server_output, client_result) = std::thread::scope(|scope| {
72 let server_h: ScopedJoinHandle<Result<Vec<u8>>> = scope.spawn(|| {
73 session.run_cmd(&remote_cmd)?;
74
75 server_ready_flag.store(true, Ordering::Relaxed);
76 let out = session
77 .get_cmd_output()
78 .expect("No command output for remote sesion");
79
80 session.close();
81 Ok(out)
82 });
83
84 let client_h = scope.spawn(|| {
85 log::debug!(
86 "Starting client thread targetting {remote_ip}:{tcp_port}",
87 remote_ip = remote.ip()
88 );
89 log::trace!(
90 "\
91 use mmap: {use_mmap}\
92 \nfile(s): {input_files:?}\
93 \nprealloc: {prealloc}\
94 \ncompression: {compression:?}"
95 );
96 while !server_ready_flag.load(Ordering::Relaxed) {
97 std::thread::sleep(Duration::from_millis(2));
98 }
99 crate::send::client::run_client(
100 remote.ip(),
101 tcp_port,
102 use_mmap,
103 input_files,
104 prealloc,
105 *compression,
106 tcp_connect_mode,
107 Some(remote.dest()),
108 )
109 });
110 tracing::trace!("Joining client thread");
111 let client_res = client_h.join().expect("Failed joining client thread");
112 tracing::trace!("Joining server thread");
113 let server_output = server_h.join().expect("Failed joining server thread");
114 (server_output, client_res)
115 });
116
117 #[cfg(debug_assertions)]
118 {
119 tracing::trace!(
120 "\n=============================== REMOTE SERVER OUTPUT ===============================\n\n{}\n^^^^^^^^^^^^^^^^^^^^^^^^^^^ END OF REMOTE SERVER OUTPUT ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n:",
121 String::from_utf8_lossy(&server_output?)
122 );
123 tracing::debug!("Client result: {client_result:?}");
124 }
125
126 #[cfg(not(debug_assertions))]
127 log::trace!(
128 "remote server output: {}",
129 String::from_utf8_lossy(&server_output?)
130 );
131
132 client_result
133}