quick_file_transfer/
ssh.rs

1use 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}