1use std::{
2 ffi::OsStr,
3 io::{Seek, Write},
4 process::{Command, Stdio},
5};
6
7use anyhow::{Context, Result};
8use cap_std::fs::Dir;
9use cap_std_ext::cap_std;
10use cap_std_ext::prelude::CapStdExtCommandExt;
11
12#[derive(Debug, PartialEq, Eq, Default)]
14enum CmdVerbosity {
15 Quiet,
17 #[default]
19 Description,
20 Verbose,
22}
23
24pub(crate) struct Task {
26 description: String,
27 verbosity: CmdVerbosity,
28 quiet_output: bool,
29 pub(crate) cmd: Command,
30}
31
32#[allow(dead_code)]
33impl Task {
34 pub(crate) fn new(description: impl AsRef<str>, exe: impl AsRef<str>) -> Self {
35 Self::new_cmd(description, Command::new(exe.as_ref()))
36 }
37
38 pub(crate) fn new_quiet(exe: impl AsRef<str>) -> Self {
41 let exe = exe.as_ref();
42 Self::new(exe, exe).quiet()
43 }
44
45 pub(crate) fn cwd(mut self, dir: &Dir) -> Result<Self> {
47 self.cmd.cwd_dir(dir.try_clone()?);
48 Ok(self)
49 }
50
51 pub(crate) fn new_cmd(description: impl AsRef<str>, mut cmd: Command) -> Self {
52 let description = description.as_ref().to_string();
53 cmd.stdin(Stdio::null());
55 Self {
56 description,
57 verbosity: Default::default(),
58 quiet_output: false,
59 cmd,
60 }
61 }
62
63 pub(crate) fn quiet(mut self) -> Self {
65 self.verbosity = CmdVerbosity::Quiet;
66 self
67 }
68
69 pub(crate) fn verbose(mut self) -> Self {
71 self.verbosity = CmdVerbosity::Verbose;
72 self
73 }
74
75 pub(crate) fn quiet_output(mut self) -> Self {
77 self.quiet_output = true;
78 self
79 }
80
81 pub(crate) fn args<S: AsRef<OsStr>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
82 self.cmd.args(args);
83 self
84 }
85
86 pub(crate) fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
87 self.cmd.args([arg]);
88 self
89 }
90
91 pub(crate) fn run(self) -> Result<()> {
93 self.run_with_stdin_buf(None)
94 }
95
96 fn pre_run_output(&self) {
97 match self.verbosity {
98 CmdVerbosity::Quiet => {}
99 CmdVerbosity::Description => {
100 println!("{}", self.description);
101 }
102 CmdVerbosity::Verbose => {
103 println!("{}", self.description);
105
106 let mut stdout = std::io::stdout().lock();
108 let cmd_args = std::iter::once(self.cmd.get_program())
109 .chain(self.cmd.get_args())
110 .map(|arg| arg.to_string_lossy());
111 stdout.write_all(b">").unwrap();
114 for s in cmd_args {
115 stdout.write_all(b" ").unwrap();
116 stdout.write_all(s.as_bytes()).unwrap();
117 }
118 stdout.write_all(b"\n").unwrap();
119 }
120 }
121 }
122
123 pub(crate) fn run_with_stdin_buf(self, stdin: Option<&[u8]>) -> Result<()> {
125 self.pre_run_output();
126 let description = self.description;
127 let mut cmd = self.cmd;
128 let mut output = None;
129 if self.quiet_output {
130 let tmpf = tempfile::tempfile()?;
131 cmd.stdout(Stdio::from(tmpf.try_clone()?));
132 cmd.stderr(Stdio::from(tmpf.try_clone()?));
133 output = Some(tmpf);
134 }
135 tracing::debug!("exec: {cmd:?}");
136 let st = if let Some(stdin_value) = stdin {
137 cmd.stdin(Stdio::piped());
138 let mut child = cmd.spawn()?;
139 let mut stdin = child.stdin.take().unwrap();
141 std::thread::scope(|s| {
143 s.spawn(move || stdin.write_all(stdin_value))
144 .join()
145 .map_err(|e| anyhow::anyhow!("Failed to spawn thread: {e:?}"))?
146 .context("Failed to write to cryptsetup stdin")
147 })?;
148 child.wait()?
149 } else {
150 cmd.status()?
151 };
152 tracing::trace!("{st:?}");
153 if !st.success() {
154 if let Some(mut output) = output {
155 output.seek(std::io::SeekFrom::Start(0))?;
156 let mut stderr = std::io::stderr().lock();
157 std::io::copy(&mut output, &mut stderr)?;
158 }
159 anyhow::bail!("Task {description} failed: {st:?}");
160 }
161 Ok(())
162 }
163
164 pub(crate) fn read(self) -> Result<String> {
166 self.pre_run_output();
167 let description = self.description;
168 let mut cmd = self.cmd;
169 tracing::debug!("exec: {cmd:?}");
170 cmd.stdout(Stdio::piped());
171 let child = cmd
172 .spawn()
173 .with_context(|| format!("Spawning {description} failed"))?;
174 let o = child
175 .wait_with_output()
176 .with_context(|| format!("Executing {description} failed"))?;
177 let st = o.status;
178 if !st.success() {
179 anyhow::bail!("Task {description} failed: {st:?}");
180 }
181 Ok(String::from_utf8(o.stdout)?)
182 }
183
184 pub(crate) fn new_and_run<'a>(
185 description: impl AsRef<str>,
186 exe: impl AsRef<str>,
187 args: impl IntoIterator<Item = &'a str>,
188 ) -> Result<()> {
189 let mut t = Self::new(description.as_ref(), exe);
190 t.cmd.args(args);
191 t.run()
192 }
193}