From 9ef27716c60a5d1a7a5855671fea68a828a07adc Mon Sep 17 00:00:00 2001 From: James McDonald Date: Wed, 29 Apr 2026 17:21:03 +0200 Subject: [PATCH] Tidy command execution --- src/command.rs | 20 +++++++++++++++----- src/job.rs | 16 ++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/command.rs b/src/command.rs index 0ed0364..44985e1 100644 --- a/src/command.rs +++ b/src/command.rs @@ -6,7 +6,12 @@ pub trait Filter { fn filter(&self, reader: Box) -> Box; } -pub fn exec_command(command: &Vec<&str>) -> Result { +// TODO: Make this support quoting for args with spaces (and escape nested quotes) +fn display_command(command: &Vec<&str>) -> String { + command.join(" ") +} + +pub fn exec(command: &Vec<&str>) -> Result { if command.is_empty() { return Err("Command is empty".to_string()); } @@ -14,11 +19,13 @@ pub fn exec_command(command: &Vec<&str>) -> Result { if command.len() > 1 { cmd.args(&command[1..]); } - let output = cmd.output().map_err(|e| e.to_string())?; + let output = cmd + .output() + .map_err(|e| format!("Failed to run command {}: {}", command[0], e))?; if !output.status.success() { return Err(format!( - "Command {:?} failed with status {}: {}", - command, + "Command '{}' failed with status {}: {}", + display_command(command), output.status, String::from_utf8_lossy(&output.stderr) )); @@ -27,7 +34,10 @@ pub fn exec_command(command: &Vec<&str>) -> Result { Ok(output_str) } -pub fn exec_piped_commands( +/// Executes a pipeline of commands, where the output of the source command is passed through a +/// series of filters before being piped into the destination command. The filters are applied in +/// the order they are provided. +pub fn exec_pipe( source: &Vec<&str>, dest: &Vec<&str>, filters: Vec>, diff --git a/src/job.rs b/src/job.rs index fc95126..b173de0 100644 --- a/src/job.rs +++ b/src/job.rs @@ -57,7 +57,7 @@ impl Job { let name = snapshot.to_string(); let mut cmd = self.get_side_command(side); cmd.extend(["snapshot", &name]); - command::exec_command(&cmd)?; + command::exec(&cmd)?; self.send_event(BackupEvent::SnapshotCreated(name.clone())); Ok(snapshot) } @@ -70,7 +70,7 @@ impl Job { } cmd.extend(["-n", "-P"]); cmd.push(source); - let output = command::exec_command(&cmd)?; + let output = command::exec(&cmd)?; let size = output .lines() .last() @@ -107,7 +107,7 @@ impl Job { ))], None => vec![], }; - command::exec_piped_commands(&send_cmd, &receive_cmd, filters)?; + command::exec_pipe(&send_cmd, &receive_cmd, filters)?; self.send_event(BackupEvent::DatasetCompleted(source.to_string())); Ok(()) } @@ -115,7 +115,7 @@ impl Job { fn list_snapshot_ids(&self, source: &str, side: JobSide) -> Result, String> { let mut cmd = self.get_side_command(side); cmd.extend(["list", "-H", "-o", "name", "-t", "snapshot", source]); - let output = command::exec_command(&cmd)?; + let output = command::exec(&cmd)?; let snapshots: Vec<&str> = output .split_whitespace() .map(|s| { @@ -195,7 +195,7 @@ impl Job { let mut cmd = self.get_side_command(snapshot.side); let name = snapshot.to_string(); cmd.extend(["destroy", &name]); - command::exec_command(&cmd)?; + command::exec(&cmd)?; self.send_event(BackupEvent::SnapshotDeleted(name.clone())); Ok(()) } @@ -205,7 +205,7 @@ impl Job { // Check the source exists let mut cmd = self.get_side_command(JobSide::Source); cmd.extend(["list", "-H", "-o", "name", source]); - let _ = command::exec_command(&cmd)?; + let _ = command::exec(&cmd)?; // Check whether the destination exists // TODO: This will assume the destination doesn't exist if the @@ -213,7 +213,7 @@ impl Job { let dest = format!("{}/{}", self.target, source); cmd = self.get_side_command(JobSide::Target); cmd.extend(["list", "-H", "-o", "name", &dest]); - let dest_exists = command::exec_command(&cmd).is_ok(); + let dest_exists = command::exec(&cmd).is_ok(); // Run backup if dest_exists { @@ -375,7 +375,7 @@ impl JobBuilder { let mut cmd: Vec<&str> = self.source_zfs_command.iter().map(|s| s.as_str()).collect(); cmd.extend(args); - let output = command::exec_command(&cmd)?; + let output = command::exec(&cmd)?; datasets.extend(output.lines().map(str::to_string)); } if datasets.is_empty() {