use std::fs::File;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::Command;
use std::process::Stdio;
use error_stack::Report;
use error_stack::ResultExt;
use netstat2::AddressFamilyFlags;
use netstat2::ProtocolFlags;
use netstat2::ProtocolSocketInfo;
use sysinfo::Process;
use super::constants::APP_EXTENSION_SUFFIX;
use super::constants::BIN_EXTENSION_SUFFIX;
use super::constants::EXE_EXTENSION_SUFFIX;
use crate::brp_tools::Port;
use crate::error::Error;
use crate::error::Result;
pub(super) fn launch_detached_process(
command: &Command,
working_dir: &Path,
log_file: File,
process_name: &str,
) -> Result<u32> {
let log_file_for_stderr = log_file
.try_clone()
.change_context(Error::ProcessManagement(
"Failed to clone log file handle".to_string(),
))
.attach(format!("Process: {process_name}, Operation: launch"))?;
let mut new_command = std::process::Command::new(command.get_program());
for arg in command.get_args() {
new_command.arg(arg);
}
new_command
.current_dir(working_dir)
.env("CARGO_MANIFEST_DIR", working_dir);
for (key, value) in command.get_envs() {
if let Some(value) = value {
new_command.env(key, value);
}
}
new_command
.stdin(Stdio::null())
.stdout(Stdio::from(log_file))
.stderr(Stdio::from(log_file_for_stderr));
#[cfg(unix)]
new_command.process_group(0);
tracing::debug!("Preparing to spawn process: {process_name}");
tracing::debug!("Command: {:?}", new_command);
tracing::debug!("Working directory: {}", working_dir.display());
match new_command.spawn() {
Ok(mut child) => {
let process_id = child.id();
tracing::debug!("Process spawned successfully: {process_name} (PID: {process_id})");
std::thread::spawn(move || match child.wait() {
Ok(status) => {
tracing::debug!("Child process {process_id} exited with status: {status:?}");
},
Err(e) => {
tracing::warn!("Failed to wait for child process {process_id}: {e}");
},
});
Ok(process_id)
},
Err(e) => {
tracing::error!("Failed to spawn process {process_name}: {e}");
Err(Report::new(e)
.change_context(Error::ProcessManagement(
"Failed to spawn process".to_string(),
))
.attach(format!("Process: {process_name}"))
.attach(format!("Working directory: {}", working_dir.display())))?
},
}
}
pub fn normalize_process_name(name: &str) -> String {
let name = name.to_lowercase();
let base_name = name.split(['/', '\\']).next_back().unwrap_or(&name);
base_name
.strip_suffix(EXE_EXTENSION_SUFFIX)
.or_else(|| base_name.strip_suffix(APP_EXTENSION_SUFFIX))
.or_else(|| base_name.strip_suffix(BIN_EXTENSION_SUFFIX))
.unwrap_or(base_name)
.to_string()
}
pub fn process_matches_name_exact(process: &Process, target: &str) -> bool {
let normalized_target = normalize_process_name(target);
if let Some(command_path) = process.cmd().first()
&& normalize_process_name(&command_path.to_string_lossy()) == normalized_target
{
return true;
}
let process_name = process.name().to_string_lossy();
normalize_process_name(&process_name) == normalized_target
}
pub fn get_pid_for_port(port: Port) -> Option<u32> {
let address_family_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6;
let protocol_flags = ProtocolFlags::TCP;
netstat2::get_sockets_info(address_family_flags, protocol_flags)
.ok()?
.into_iter()
.find_map(|socket_info| {
if let ProtocolSocketInfo::Tcp(tcp_socket_info) = socket_info.protocol_socket_info
&& tcp_socket_info.local_port == *port
{
return socket_info.associated_pids.first().copied();
}
None
})
}