aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/populate
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-13 20:36:10 +0000
committertv <tv@krebsco.de>2026-11-13 22:01:46 +0100
commitce2cd2553a937d84f1a78386ab28502e89f247d6 (patch)
treefd598a289771894a6eb82157d192a5fa4f784b3f /pkgs/populate
parent7768c71fd8083ea21572de2859fed39c17524baf (diff)
populate: exit with the status of the command that failed
The shell implementation runs under set -e, so it exits with the status of the command that failed, e.g. 255 if ssh can't reach the target, which wrappers retrying on connection failures rely on, or 128 if git fetch fails. The program does the same, instead of exiting with 1, and with 128 plus the signal for a command a signal ended. A phase's expect.status is the exit status it expects populate to fail with; git-status checks git fetch's.
Diffstat (limited to 'pkgs/populate')
-rw-r--r--pkgs/populate/docs/design.md6
-rw-r--r--pkgs/populate/src/main.rs2
-rw-r--r--pkgs/populate/src/run.rs26
3 files changed, 26 insertions, 8 deletions
diff --git a/pkgs/populate/docs/design.md b/pkgs/populate/docs/design.md
index 6ad5eea..cc78a65 100644
--- a/pkgs/populate/docs/design.md
+++ b/pkgs/populate/docs/design.md
@@ -37,7 +37,11 @@ populate SPEC
input. Exit status:
- 0: populate delivered all sources.
-- 1: a check of the spec failed, or delivering did.
+- 1: a check of the spec failed, or delivering did, with a message of its
+ own.
+- The exit status of the command that failed, if delivering failed with
+ that, like the shell implementation does: e.g. 255 if ssh can't reach
+ the target, or 128 if git fetch fails.
- 2: the arguments are wrong, or the spec can't be read, isn't JSON, is of
another version, or doesn't have the spec's shape.
- 129, 130, 143: populate got SIGHUP, SIGINT, or SIGTERM, and stopped.
diff --git a/pkgs/populate/src/main.rs b/pkgs/populate/src/main.rs
index 17d9678..2884fd0 100644
--- a/pkgs/populate/src/main.rs
+++ b/pkgs/populate/src/main.rs
@@ -78,7 +78,7 @@ fn exit(failure: run::Failure) -> ExitCode {
eprintln!("{message}");
ExitCode::FAILURE
}
- run::Failure::Command => ExitCode::FAILURE,
+ run::Failure::Command(status) => ExitCode::from(status),
run::Failure::Signal(signal) => ExitCode::from(128 + signal as u8),
}
}
diff --git a/pkgs/populate/src/run.rs b/pkgs/populate/src/run.rs
index 528977c..4ed1d76 100644
--- a/pkgs/populate/src/run.rs
+++ b/pkgs/populate/src/run.rs
@@ -15,8 +15,10 @@ use std::sync::atomic::{AtomicI32, Ordering};
pub enum Failure {
/// A message to print.
Message(String),
- /// A command failed, after printing why.
- Command,
+ /// A command failed, after printing why, with its exit status, which
+ /// populate exits with: the shell implementation, running under set -e,
+ /// does, so e.g. 255 from ssh still tells a connection failure.
+ Command(u8),
/// A signal arrived: SIGHUP, SIGINT, or SIGTERM.
Signal(i32),
}
@@ -25,7 +27,7 @@ impl fmt::Display for Failure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Failure::Message(message) => f.write_str(message),
- Failure::Command => f.write_str("a command failed"),
+ Failure::Command(status) => write!(f, "a command failed with status {status}"),
Failure::Signal(signal) => write!(f, "got signal {signal}"),
}
}
@@ -117,7 +119,7 @@ impl Context {
.output()
.map_err(|e| Failure::Message(format!("error: can't run mktemp: {e}")))?;
if !output.status.success() {
- return Err(Failure::Command);
+ return Err(failed(output.status));
}
let tmpdir = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim_end());
// Shared with the commands populate runs.
@@ -319,13 +321,25 @@ fn spawn_error(command: &Command, e: std::io::Error) -> Failure {
))
}
+/// The failure of a command that exited with status, or, like in a shell,
+/// with 128 plus the signal that ended it.
+fn failed(status: ExitStatus) -> Failure {
+ use std::os::unix::process::ExitStatusExt;
+ let code = match (status.code(), status.signal()) {
+ (Some(code), _) => code,
+ (None, Some(signal)) => 128 + signal,
+ (None, None) => 1,
+ };
+ Failure::Command(u8::try_from(code).ok().filter(|c| *c != 0).unwrap_or(1))
+}
+
/// Runs a command, failing if it fails, or if a signal arrived meanwhile.
pub fn status(command: &mut Command) -> Result<()> {
let result: std::io::Result<ExitStatus> = command.status();
check_signal()?;
match result {
Ok(status) if status.success() => Ok(()),
- Ok(_) => Err(Failure::Command),
+ Ok(status) => Err(failed(status)),
Err(e) => Err(spawn_error(command, e)),
}
}
@@ -339,7 +353,7 @@ pub fn output(command: &mut Command) -> Result<String> {
Ok(output) if output.status.success() => {
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
- Ok(_) => Err(Failure::Command),
+ Ok(output) => Err(failed(output.status)),
Err(e) => Err(spawn_error(command, e)),
}
}