1use crate::{Result, config, highlighter, log_debug, log_error, log_info, log_ssh};
16use std::{
17 io::{self, BufReader, Read, Write},
18 process::{Command, ExitCode, Stdio},
19 sync::mpsc::{self, Receiver, Sender},
20 thread,
21};
22
23pub fn process_handler(process_args: Vec<String>, is_non_interactive: bool) -> Result<ExitCode> {
36 log_info!("Starting SSH process with args: {:?}", process_args);
37 log_debug!("Non-interactive mode: {}", is_non_interactive);
38
39 if is_non_interactive {
41 log_info!("Using passthrough mode for non-interactive command");
42 return spawn_ssh_passthrough(&process_args);
43 }
44
45 let mut child = spawn_ssh(&process_args).map_err(|err| {
47 log_error!("Failed to spawn SSH process: {}", err);
48 err
49 })?;
50
51 log_debug!("SSH process spawned successfully (PID: {:?})", child.id());
52
53 let stdout = child.stdout.take().ok_or_else(|| {
54 log_error!("Failed to capture stdout from SSH process");
55 io::Error::other("Failed to capture stdout")
56 })?;
57
58 let mut reader = BufReader::new(stdout);
59
60 let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
62
63 let reset_color = "\x1b[0m"; let processing_thread = thread::Builder::new()
68 .name("output-processor".to_string())
69 .spawn(move || {
70 log_debug!("Output processing thread started");
71 let mut chunk_id = 0;
72 let mut cached_rules = config::get_config().read().unwrap().metadata.compiled_rules.clone();
74 let mut cached_version = config::get_config().read().unwrap().metadata.version;
75
76 while let Ok(chunk) = rx.recv() {
77 let current_version = config::get_config().read().unwrap().metadata.version;
79 if current_version != cached_version {
80 cached_rules = config::get_config().read().unwrap().metadata.compiled_rules.clone();
81 cached_version = current_version;
82 log_debug!("Rules updated due to config reload (version {})", cached_version);
83 }
84
85 let processed = highlighter::process_chunk(chunk, chunk_id, &cached_rules, reset_color);
86 chunk_id += 1;
87 print!("{}", processed); if let Err(e) = io::stdout().flush() {
89 log_error!("Failed to flush stdout: {}", e);
90 }
91 }
92 log_debug!("Output processing thread finished (processed {} chunks)", chunk_id);
93 })
94 .map_err(|err| {
95 log_error!("Failed to spawn output processing thread: {}", err);
96 io::Error::other( "Failed to spawn processing thread")
97 })?;
98
99 let mut buffer = [0; 4096];
101 let mut total_bytes = 0;
102
103 log_debug!("Starting to read SSH output...");
104
105 loop {
106 match reader.read(&mut buffer) {
107 Ok(0) => {
108 log_debug!("EOF reached (total bytes read: {})", total_bytes);
109 break; }
111 Ok(n) => {
112 total_bytes += n;
113 let chunk = String::from_utf8_lossy(&buffer[..n]).to_string();
115 log_ssh!("{}", chunk);
116
117 if let Err(err) = tx.send(chunk) {
118 log_error!("Failed to send data to processing thread: {}", err);
119 break;
120 }
121 }
122 Err(err) => {
123 log_error!("Error reading from SSH process: {}", err);
124 return Err(err.into());
125 }
126 }
127 }
128
129 drop(tx);
131
132 if let Err(err) = processing_thread.join() {
134 log_error!("Processing thread panicked: {:?}", err);
135 }
136
137 let status = child.wait().map_err(|err| {
139 log_error!("Failed to wait for SSH process (PID: {:?}): {}", child.id(), err);
140 err
141 })?;
142
143 let exit_code = status.code().unwrap_or(1);
144 log_info!("SSH process exited with code: {}", exit_code);
145
146 if status.success() {
147 Ok(ExitCode::SUCCESS)
148 } else {
149 let clamped_code = u8::try_from(exit_code).unwrap_or(255);
151 Ok(ExitCode::from(clamped_code))
152 }
153}
154
155pub fn spawn_ssh(args: &[String]) -> std::io::Result<std::process::Child> {
168 log_debug!("Spawning SSH with args: {:?}", args);
169
170 let child = Command::new("ssh")
171 .args(args)
172 .stdin(Stdio::inherit()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn()
176 .map_err(|err| {
177 log_error!("Failed to spawn SSH command: {}", err);
178 err
179 })?;
180
181 log_debug!("SSH process spawned (PID: {:?})", child.id());
182 Ok(child)
183}
184
185fn spawn_ssh_passthrough(args: &[String]) -> Result<ExitCode> {
196 log_debug!("Spawning SSH in passthrough mode with args: {:?}", args);
197
198 let status = Command::new("ssh")
199 .args(args)
200 .stdin(Stdio::inherit())
201 .stdout(Stdio::inherit()) .stderr(Stdio::inherit())
203 .status()
204 .map_err(|err| {
205 log_error!("Failed to execute SSH command in passthrough mode: {}", err);
206 err
207 })?;
208
209 let exit_code = status.code().unwrap_or(1);
210 log_info!("SSH passthrough process exited with code: {}", exit_code);
211
212 if status.success() {
213 Ok(ExitCode::SUCCESS)
214 } else {
215 let clamped_code = u8::try_from(exit_code).unwrap_or(255);
217 Ok(ExitCode::from(clamped_code))
218 }
219}