Skip to content

Commit d749a03

Browse files
committed
Use Task generally over Command
Especially in places where we call `.output()`, what we generally always want is to get stdout, but keep stderr going to the parent process stderr. The `Command` API doesn't make this obvious to do, and worse forces every caller to manually check the exit status. The `Task` API fixes both of these. (Now, the Task API also defaults to printing a description, which we need suppress in many cases with `.quiet()`) Signed-off-by: Colin Walters <[email protected]>
1 parent 5463470 commit d749a03

File tree

7 files changed

+38
-58
lines changed

7 files changed

+38
-58
lines changed

lib/src/blockdev.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,22 +113,6 @@ pub(crate) fn reread_partition_table(file: &mut File, retry: bool) -> Result<()>
113113
Ok(())
114114
}
115115

116-
/// Runs the provided Command object, captures its stdout, and swallows its stderr except on
117-
/// failure. Returns a Result<String> describing whether the command failed, and if not, its
118-
/// standard output. Output is assumed to be UTF-8. Errors are adequately prefixed with the full
119-
/// command.
120-
pub(crate) fn cmd_output(cmd: &mut Command) -> Result<String> {
121-
let result = cmd
122-
.output()
123-
.with_context(|| format!("running {:#?}", cmd))?;
124-
if !result.status.success() {
125-
eprint!("{}", String::from_utf8_lossy(&result.stderr));
126-
anyhow::bail!("{:#?} failed with {}", cmd, result.status);
127-
}
128-
String::from_utf8(result.stdout)
129-
.with_context(|| format!("decoding as UTF-8 output of `{:#?}`", cmd))
130-
}
131-
132116
/// Parse key-value pairs from lsblk --pairs.
133117
/// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
134118
fn split_lsblk_line(line: &str) -> HashMap<String, String> {
@@ -144,15 +128,15 @@ fn split_lsblk_line(line: &str) -> HashMap<String, String> {
144128
/// hierarchy of `device` capable of containing other partitions. So e.g. parent devices of type
145129
/// "part" doesn't match, but "disk" and "mpath" does.
146130
pub(crate) fn find_parent_devices(device: &str) -> Result<Vec<String>> {
147-
let mut cmd = Command::new("lsblk");
148-
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
149-
cmd.arg("--pairs")
131+
let output = Task::new_quiet("lsblk")
132+
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
133+
.arg("--pairs")
150134
.arg("--paths")
151135
.arg("--inverse")
152136
.arg("--output")
153137
.arg("NAME,TYPE")
154-
.arg(device);
155-
let output = cmd_output(&mut cmd)?;
138+
.arg(device)
139+
.read()?;
156140
let mut parents = Vec::new();
157141
// skip first line, which is the device itself
158142
for line in output.lines().skip(1) {

lib/src/install.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -691,14 +691,11 @@ pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> {
691691

692692
#[context("Querying skopeo version")]
693693
fn skopeo_supports_containers_storage() -> Result<bool> {
694-
let o = run_in_host_mountns("skopeo").arg("--version").output()?;
695-
let st = o.status;
696-
if !st.success() {
697-
let _ = std::io::copy(&mut std::io::Cursor::new(o.stderr), &mut std::io::stderr()); // Ignore errors copying stderr
698-
anyhow::bail!("Failed to run skopeo --version: {st:?}");
699-
}
700-
let stdout = String::from_utf8(o.stdout).context("Parsing skopeo version")?;
701-
let mut v = stdout
694+
let out = Task::new_cmd("skopeo --version", run_in_host_mountns("skopeo"))
695+
.args(["--version"])
696+
.quiet()
697+
.read()?;
698+
let mut v = out
702699
.strip_prefix("skopeo version ")
703700
.map(|v| v.split('.'))
704701
.ok_or_else(|| anyhow::anyhow!("Unexpected output from skopeo version"))?;

lib/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod lsm;
1919
mod reboot;
2020
mod reexec;
2121
mod status;
22+
mod task;
2223
mod utils;
2324

2425
#[cfg(feature = "internal-testing-api")]
@@ -38,8 +39,6 @@ pub(crate) mod mount;
3839
#[cfg(feature = "install")]
3940
mod podman;
4041
pub mod spec;
41-
#[cfg(feature = "install")]
42-
mod task;
4342

4443
#[cfg(feature = "docgen")]
4544
mod docgen;

lib/src/lsm.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use gvariant::{aligned_bytes::TryAsAligned, Marker, Structure};
1212
#[cfg(feature = "install")]
1313
use ostree_ext::ostree;
1414

15-
#[cfg(feature = "install")]
1615
use crate::task::Task;
1716

1817
/// The mount path for selinux
@@ -139,30 +138,23 @@ pub(crate) fn selinux_set_permissive(permissive: bool) -> Result<()> {
139138

140139
fn selinux_label_for_path(target: &str) -> Result<String> {
141140
// TODO: detect case where SELinux isn't enabled
142-
let o = Command::new("matchpathcon").args(["-n", target]).output()?;
143-
let st = o.status;
144-
if !st.success() {
145-
anyhow::bail!("matchpathcon failed: {st:?}");
146-
}
147-
let label = String::from_utf8(o.stdout)?;
148-
let label = label.trim();
149-
Ok(label.to_string())
141+
let label = Task::new_quiet("matchpathcon")
142+
.args(["-n", target])
143+
.read()?;
144+
// TODO: trim in place instead of reallocating
145+
Ok(label.trim().to_string())
150146
}
151147

152148
// Write filesystem labels (currently just for SELinux)
153149
#[context("Labeling {as_path}")]
154150
pub(crate) fn lsm_label(target: &Utf8Path, as_path: &Utf8Path, recurse: bool) -> Result<()> {
155151
let label = selinux_label_for_path(as_path.as_str())?;
156152
tracing::debug!("Label for {target} is {label}");
157-
let st = Command::new("chcon")
153+
Task::new_quiet("chcon")
158154
.arg("-h")
159155
.args(recurse.then_some("-R"))
160156
.args(["-h", label.as_str(), target.as_str()])
161-
.status()?;
162-
if !st.success() {
163-
anyhow::bail!("Failed to invoke chcon: {st:?}");
164-
}
165-
Ok(())
157+
.run()
166158
}
167159

168160
#[cfg(feature = "install")]

lib/src/podman.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
22
use serde::Deserialize;
33

44
use crate::install::run_in_host_mountns;
5+
use crate::task::Task;
56

67
#[derive(Deserialize)]
78
#[serde(rename_all = "PascalCase")]
@@ -11,14 +12,11 @@ pub(crate) struct Inspect {
1112

1213
/// Given an image ID, return its manifest digest
1314
pub(crate) fn imageid_to_digest(imgid: &str) -> Result<String> {
14-
let o = run_in_host_mountns("podman")
15+
let out = Task::new_cmd("podman inspect", run_in_host_mountns("podman"))
1516
.args(["inspect", imgid])
16-
.output()?;
17-
let st = o.status;
18-
if !st.success() {
19-
anyhow::bail!("Failed to execute podman inspect: {st:?}");
20-
}
21-
let o: Vec<Inspect> = serde_json::from_slice(&o.stdout)?;
17+
.quiet()
18+
.read()?;
19+
let o: Vec<Inspect> = serde_json::from_str(&out)?;
2220
let i = o
2321
.into_iter()
2422
.next()

lib/src/reboot.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
//! Handling of system restarts/reboot
22
33
use std::io::Write;
4-
use std::process::Command;
54

65
use fn_error_context::context;
76

7+
use crate::task::Task;
8+
89
/// Initiate a system reboot.
910
/// This function will only return in case of error.
1011
#[context("Initiating reboot")]
1112
pub(crate) fn reboot() -> anyhow::Result<()> {
1213
// Flush output streams
1314
let _ = std::io::stdout().flush();
1415
let _ = std::io::stderr().flush();
15-
let st = Command::new("reboot").status()?;
16-
if !st.success() {
17-
anyhow::bail!("Failed to reboot: {st:?}");
18-
}
16+
Task::new("Rebooting system", "reboot").run()?;
1917
tracing::debug!("Initiated reboot, sleeping forever...");
2018
loop {
2119
std::thread::park();

lib/src/task.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ impl Task {
2121
Self::new_cmd(description, Command::new(exe.as_ref()))
2222
}
2323

24+
/// This API can be used in place of Command::new() generally and just adds error
25+
/// checking on top.
26+
pub(crate) fn new_quiet(exe: impl AsRef<str>) -> Self {
27+
let exe = exe.as_ref();
28+
Self::new(exe, exe).quiet()
29+
}
30+
2431
/// Set the working directory for this task.
2532
pub(crate) fn cwd(mut self, dir: &Dir) -> Result<Self> {
2633
self.cmd.cwd_dir(dir.try_clone()?);
@@ -55,6 +62,11 @@ impl Task {
5562
self
5663
}
5764

65+
pub(crate) fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
66+
self.cmd.args([arg]);
67+
self
68+
}
69+
5870
/// Run the command, returning an error if the command does not exit successfully.
5971
pub(crate) fn run(self) -> Result<()> {
6072
self.run_with_stdin_buf(None)

0 commit comments

Comments
 (0)