1use std::collections::HashMap;
40use std::env;
41use std::ffi::OsStr;
42use std::process::{Command, ExitStatus, Output, Stdio};
43
44use std::io::Write;
46use std::iter::IntoIterator;
47
48pub struct OutputString {
54 pub status: ExitStatus,
56 pub stdout: String,
58 pub stderr: String,
60}
61
62#[derive(Debug)]
64pub enum ExecError {
65 Io(std::io::Error),
67 SpawnFailed(String, std::io::Error),
69}
70
71impl std::fmt::Display for ExecError {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 ExecError::Io(err) => write!(f, "IO error: {}", err),
75 ExecError::SpawnFailed(cmd, err) => write!(f, "failed to spawn {}: {}", cmd, err),
76 }
77 }
78}
79
80impl std::error::Error for ExecError {}
81
82impl From<std::io::Error> for ExecError {
83 fn from(err: std::io::Error) -> Self {
84 ExecError::Io(err)
85 }
86}
87
88pub type Result<T> = std::result::Result<T, ExecError>;
89
90fn setup_envs<I, K, V>(cmd: &mut Command, vars: I) -> &mut Command
91where
92 I: IntoIterator<Item = (K, V)>,
93 K: AsRef<OsStr>,
94 V: AsRef<OsStr>,
95{
96 let filtered_env: HashMap<String, String> = env::vars()
97 .filter(|(k, _)| k == "TERM" || k == "TZ" || k == "PATH" || k == "LD_LIBRARY_PATH")
98 .collect();
99 cmd.env_clear()
100 .envs(filtered_env)
101 .envs(vars)
102 .env("LANG", "C")
103}
104
105fn exec_internal<I, S, IKV, K, V>(
106 target_exe: &str,
107 args: I,
108 env: IKV,
109 in_bytes: Option<&[u8]>,
110) -> Result<OutputString>
111where
112 I: IntoIterator<Item = S>,
113 S: AsRef<OsStr>,
114 IKV: IntoIterator<Item = (K, V)>,
115 K: AsRef<OsStr>,
116 V: AsRef<OsStr>,
117{
118 let mut cmd: Command = Command::new(target_exe);
119 setup_envs(&mut cmd, env).args(args);
120
121 if in_bytes.is_some() {
122 cmd.stdin(Stdio::piped());
123 }
124 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
125
126 let mut child = cmd
127 .spawn()
128 .map_err(|e| ExecError::SpawnFailed(target_exe.to_string(), e))?;
129
130 if let Some(bytes) = in_bytes {
131 let stdin = child
132 .stdin
133 .as_mut()
134 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "failed to get stdin"))?;
135 let r = stdin.write_all(bytes);
136 match r {
137 Err(ioe) if ioe.kind() == std::io::ErrorKind::BrokenPipe => {
138 }
140 _ => {
141 r?;
142 }
143 }
144 }
145
146 let output: Output = child.wait_with_output()?;
147 Ok(OutputString {
149 status: output.status,
150 stdout: String::from(String::from_utf8_lossy(&output.stdout)),
151 stderr: String::from(String::from_utf8_lossy(&output.stderr)),
152 })
153}
154
155pub fn exec_target<I, S>(target_exe: &str, args: I) -> Result<OutputString>
156where
157 I: IntoIterator<Item = S>,
158 S: AsRef<OsStr>,
159{
160 exec_internal(target_exe, args, Vec::<(&str, &str)>::new(), None)
161}
162
163pub fn exec_target_with_env<I, S, IKV, K, V>(
164 target_exe: &str,
165 args: I,
166 env: IKV,
167) -> Result<OutputString>
168where
169 I: IntoIterator<Item = S>,
170 S: AsRef<OsStr>,
171 IKV: IntoIterator<Item = (K, V)>,
172 K: AsRef<OsStr>,
173 V: AsRef<OsStr>,
174{
175 exec_internal(target_exe, args, env, None)
176}
177
178pub fn exec_target_with_in<I, S>(target_exe: &str, args: I, in_bytes: &[u8]) -> Result<OutputString>
179where
180 I: IntoIterator<Item = S>,
181 S: AsRef<OsStr>,
182{
183 exec_internal(target_exe, args, Vec::<(&str, &str)>::new(), Some(in_bytes))
184}
185
186pub fn exec_target_with_env_in<I, S, IKV, K, V>(
208 target_exe: &str,
209 args: I,
210 env: IKV,
211 in_bytes: &[u8],
212) -> Result<OutputString>
213where
214 I: IntoIterator<Item = S>,
215 S: AsRef<OsStr>,
216 IKV: IntoIterator<Item = (K, V)>,
217 K: AsRef<OsStr>,
218 V: AsRef<OsStr>,
219{
220 exec_internal(target_exe, args, env, Some(in_bytes))
221}
222
223pub fn args_from(s: &str) -> Vec<String> {
235 let mut v: Vec<String> = Vec::new();
236 let mut ss = String::new();
237 let mut enter_q: bool = false;
238 let mut enter_qq: bool = false;
239 let mut back_slash: bool = false;
240 for c in s.chars() {
242 if back_slash {
243 ss.push(c);
244 back_slash = false;
245 continue;
246 }
247 if c == '\\' {
248 back_slash = true;
249 continue;
250 }
251 if enter_q {
252 if c == '\'' {
253 v.push(ss.clone());
254 ss.clear();
255 enter_q = false;
256 } else {
257 ss.push(c);
258 }
259 continue;
260 }
261 if enter_qq {
262 if c == '\"' {
263 v.push(ss.clone());
264 ss.clear();
265 enter_qq = false;
266 } else {
267 ss.push(c);
268 }
269 continue;
270 }
271 match c {
272 '\'' => {
273 enter_q = true;
274 continue;
275 }
276 '\"' => {
277 enter_qq = true;
278 continue;
279 }
280 ' ' => {
281 if !ss.is_empty() {
282 v.push(ss.clone());
283 ss.clear();
284 }
285 }
286 _ => {
287 ss.push(c);
288 }
289 }
290 }
291 if !ss.is_empty() {
292 v.push(ss.clone());
293 ss.clear();
294 }
295 v
297}