use command_timeout::{run_command_with_timeout, CommandError, CommandOutput};
use std::os::unix::process::ExitStatusExt;
use std::path::PathBuf; use std::process::{Command, ExitStatus};
use std::time::Duration;
use tempfile::Builder;
use tracing::{error, info, warn, Level};
use tracing_subscriber::FmtSubscriber;
const KERNEL_REPO_URL: &str = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git";
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO) .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Setting default tracing subscriber failed");
info!("Starting Linux kernel git clone example...");
let min_timeout = Duration::from_secs(10);
let max_timeout = Duration::from_secs(60 * 60 * 24 * 7); let activity_timeout = Duration::from_secs(60 * 5);
let temp_dir_builder = Builder::new().prefix("kernel_clone_persistent").tempdir()?; let clone_target_path_buf: PathBuf = temp_dir_builder.into_path();
let clone_target_path_str = clone_target_path_buf.to_str().unwrap_or(".");
info!(
"Preparing to clone '{}' into directory '{}'", KERNEL_REPO_URL,
clone_target_path_buf.display() );
info!(
"Timeouts: min={:?}, max={:?} (effectively disabled), activity={:?}",
min_timeout, max_timeout, activity_timeout
);
info!("Run with RUST_LOG=debug for detailed library tracing.");
info!("NOTE: The clone directory will NOT be deleted automatically.");
let mut cmd = Command::new("git");
cmd.arg("clone")
.arg("--progress") .arg(KERNEL_REPO_URL)
.arg(clone_target_path_str);
let command_result =
run_command_with_timeout(cmd, min_timeout, max_timeout, activity_timeout).await;
match command_result {
Ok(output) => {
handle_command_output(output);
info!(
"Clone operation finished. Directory location: {}",
clone_target_path_buf.display()
);
}
Err(e) => {
match &e {
CommandError::Spawn(io_err) => error!("Failed to spawn git: {}", io_err),
CommandError::Io(io_err) => error!("IO error reading output: {}", io_err),
CommandError::Kill(io_err) => error!("Error sending kill signal: {}", io_err),
CommandError::Wait(io_err) => error!("Error waiting for command exit: {}", io_err),
CommandError::InvalidTimeout(ref msg) => error!("Invalid timeout config: {}", msg),
CommandError::StdoutPipe => error!("Failed to get stdout pipe from command"),
CommandError::StderrPipe => error!("Failed to get stderr pipe from command"),
}
error!("Command execution failed.");
warn!(
"Clone operation failed. Directory may be incomplete or empty: {}",
clone_target_path_buf.display()
);
return Err(e.into());
}
}
Ok(())
}
fn handle_command_output(output: CommandOutput) {
info!("Command finished.");
info!("Total Duration: {:?}", output.duration);
info!("Timed Out: {}", output.timed_out);
let status_opt: Option<ExitStatus> = output.exit_status;
if let Some(status) = status_opt {
if status.success() {
info!("Exit Status: {} (Success)", status);
} else {
warn!("Exit Status: {} (Failure)", status);
if let Some(code) = status.code() {
warn!("Exit Code: {}", code);
}
if let Some(signal) = status.signal() {
warn!("Terminated by Signal: {}", signal);
}
}
} else {
warn!("Exit Status: None (Killed by timeout, status unavailable?)");
}
info!("Stdout Length: {} bytes", output.stdout.len());
if !output.stdout.is_empty() {
info!(
"Stdout (first 1KB):\n---\n{}...\n---",
String::from_utf8_lossy(&output.stdout.iter().take(1024).cloned().collect::<Vec<_>>())
);
}
info!("Stderr Length: {} bytes", output.stderr.len());
if !output.stderr.is_empty() {
warn!(
"Stderr (first 1KB):\n---\n{}...\n---",
String::from_utf8_lossy(&output.stderr.iter().take(1024).cloned().collect::<Vec<_>>())
);
}
if output.exit_status.map_or(false, |s| s.success()) && !output.timed_out {
info!("---> Clone completed successfully! <---");
} else if output.timed_out {
error!("---> Clone FAILED due to timeout. <---");
} else {
error!("---> Clone FAILED with non-zero exit status. <---");
}
}