1use std::env;
2use std::fmt;
3use std::io::Write;
4use std::path::Path;
5use std::process::{Command, Stdio};
6
7use anyhow::{anyhow, bail, Context, Result};
8use nucleo::Injector;
9use tokio::{
10 io::AsyncBufReadExt, io::BufReader as TokioBufReader, process::Command as TokioCommand,
11};
12
13use crate::common::{current_username, is_in_path, GREP_EXECUTABLE, RG_EXECUTABLE, SETSID};
14use crate::modes::PasswordHolder;
15use crate::{log_info, log_line};
16
17pub fn execute<S, P>(exe: S, args: &[P]) -> Result<std::process::Child>
26where
27 S: AsRef<std::ffi::OsStr> + fmt::Debug,
28 P: AsRef<std::ffi::OsStr> + fmt::Debug,
29{
30 log_info!("execute. executable: {exe:?}, arguments: {args:?}");
31 log_line!("Execute: {exe:?}, arguments: {args:?}");
32 if is_in_path(SETSID) {
33 Ok(Command::new(SETSID)
34 .arg(exe)
35 .args(args)
36 .stdin(Stdio::null())
37 .stdout(Stdio::null())
38 .stderr(Stdio::null())
39 .spawn()?)
40 } else {
41 Ok(Command::new(exe)
42 .args(args)
43 .stdin(Stdio::null())
44 .stdout(Stdio::null())
45 .stderr(Stdio::null())
46 .spawn()?)
47 }
48}
49
50pub fn execute_without_output<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
54 exe: S,
55 args: &[&str],
56) -> Result<std::process::Child> {
57 log_info!("execute_in_child_without_output. executable: {exe:?}, arguments: {args:?}",);
58 Ok(Command::new(exe)
59 .args(args)
60 .stdin(Stdio::null())
61 .stdout(Stdio::null())
62 .stderr(Stdio::null())
63 .spawn()?)
64}
65
66pub fn execute_and_capture_output<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
72 exe: S,
73 args: &[&str],
74) -> Result<String> {
75 log_info!("execute_and_capture_output. executable: {exe:?}, arguments: {args:?}",);
76 let output = Command::new(exe)
77 .args(args)
78 .stdin(Stdio::null())
79 .stdout(Stdio::piped())
80 .stderr(Stdio::null())
81 .output()?;
82 if output.status.success() {
83 Ok(String::from_utf8(output.stdout)?)
84 } else {
85 Err(anyhow!(
86 "execute_and_capture_output: command didn't finish properly",
87 ))
88 }
89}
90
91pub fn command_with_path<S: AsRef<std::ffi::OsStr> + fmt::Debug, P: AsRef<Path>>(
95 exe: S,
96 path: P,
97 args: &[&str],
98) -> Command {
99 let mut command = Command::new(exe);
100 command
101 .args(args)
102 .current_dir(path)
103 .stdin(Stdio::null())
104 .stdout(Stdio::null())
105 .stderr(Stdio::null());
106 command
107}
108
109pub fn execute_and_capture_output_with_path<
116 S: AsRef<std::ffi::OsStr> + fmt::Debug,
117 P: AsRef<Path>,
118>(
119 exe: S,
120 path: P,
121 args: &[&str],
122) -> Result<String> {
123 log_info!("execute_and_capture_output_with_path. executable: {exe:?}, arguments: {args:?}",);
124 let output = Command::new(exe)
125 .args(args)
126 .current_dir(path)
127 .stdin(Stdio::null())
128 .stdout(Stdio::piped())
129 .stderr(Stdio::piped())
130 .output()?;
131 if output.status.success() {
132 Ok(String::from_utf8(output.stdout)?)
133 } else {
134 let err = String::from_utf8(output.stderr)?;
135 log_info!("{err}");
136 Err(anyhow!(
137 "execute_and_capture_output: command didn't finish properly: {err}",
138 ))
139 }
140}
141
142pub fn execute_and_capture_output_without_check<S>(exe: S, args: &[&str]) -> Result<String>
146where
147 S: AsRef<std::ffi::OsStr> + fmt::Debug,
148{
149 log_info!("execute_and_capture_output_without_check. executable: {exe:?}, arguments: {args:?}",);
150 let child = Command::new(exe)
151 .args(args)
152 .stdin(Stdio::null())
153 .stdout(Stdio::piped())
154 .stderr(Stdio::null())
155 .spawn()?;
156 let output = child.wait_with_output()?;
157 Ok(String::from_utf8(output.stdout)?)
158}
159
160pub fn execute_and_output<S, I>(exe: S, args: I) -> Result<std::process::Output>
162where
163 S: AsRef<std::ffi::OsStr> + fmt::Debug,
164 I: IntoIterator<Item = S> + fmt::Debug,
165{
166 log_info!("execute_and_output. executable: {exe:?}, arguments: {args:?}",);
167 Ok(Command::new(exe)
168 .args(args)
169 .stdin(Stdio::null())
170 .stderr(Stdio::null())
171 .output()?)
172}
173
174pub fn execute_and_output_no_log<S, I>(exe: S, args: I) -> Result<std::process::Output>
176where
177 S: AsRef<std::ffi::OsStr> + fmt::Debug,
178 I: IntoIterator<Item = S> + fmt::Debug,
179{
180 Ok(Command::new(exe).args(args).stdin(Stdio::null()).output()?)
181}
182
183pub fn execute_with_ansi_colors(args: &[String]) -> Result<std::process::Output> {
187 log_info!("execute. {args:?}");
188 log_line!("Executed {args:?}");
189 Ok(Command::new(&args[0])
190 .args(&args[1..])
191 .env("CLICOLOR_FORCE", "1")
192 .env("COLORTERM", "ansi")
193 .stdin(Stdio::null())
194 .stdout(Stdio::piped())
195 .stderr(Stdio::null())
196 .output()?)
197}
198
199fn new_sudo_command_awaiting_password<S, P>(args: &[S], path: P) -> Result<std::process::Child>
206where
207 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
208 P: AsRef<std::path::Path> + std::fmt::Debug,
209{
210 Ok(Command::new("sudo")
211 .arg("-S")
212 .args(args)
213 .stdin(Stdio::piped())
214 .stdout(Stdio::piped())
215 .stderr(Stdio::piped())
216 .current_dir(path)
217 .spawn()?)
218}
219
220fn inject_password(password: &str, child: &mut std::process::Child) -> Result<()> {
222 let child_stdin = child
223 .stdin
224 .as_mut()
225 .context("inject_password: couldn't open child stdin")?;
226 child_stdin.write_all(password.as_bytes())?;
227 child_stdin.write_all(b"\n")?;
228 Ok(())
229}
230
231pub fn execute_sudo_command_with_password<S, P>(
235 args: &[S],
236 password: &str,
237 path: P,
238) -> Result<(bool, String, String)>
239where
240 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
241 P: AsRef<std::path::Path> + std::fmt::Debug,
242{
243 execute_sudo_command_inner(args, Some(password), Some(path))
244}
245
246pub fn execute_sudo_command_passwordless<S>(args: &[S]) -> Result<(bool, String, String)>
249where
250 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
251{
252 execute_sudo_command_inner::<S, &str>(args, None, None)
253}
254
255fn execute_sudo_command_inner<S, P>(
256 args: &[S],
257 password: Option<&str>,
258 path: Option<P>,
259) -> Result<(bool, String, String)>
260where
261 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
262 P: AsRef<std::path::Path> + std::fmt::Debug,
263{
264 log_info!("running sudo {args:?}");
265 log_line!("running sudo command. {args:?}");
266 let child = match (password, path) {
267 (None, None) => new_sudo_command_passwordless(args)?,
268 (Some(password), Some(path)) => {
269 log_info!("CWD {path:?}");
270 let mut child = new_sudo_command_awaiting_password(args, path)?;
271 inject_password(password, &mut child)?;
272 log_info!("injected sudo password");
273 child
274 }
275 _ => bail!("Password and Path should be set together"),
276 };
277 run_and_output(child)
278}
279
280fn run_and_output(child: std::process::Child) -> Result<(bool, String, String)> {
281 let output = child.wait_with_output()?;
282 Ok((
283 output.status.success(),
284 String::from_utf8(output.stdout)?,
285 String::from_utf8(output.stderr)?,
286 ))
287}
288fn new_sudo_command_passwordless<S>(args: &[S]) -> Result<std::process::Child>
291where
292 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
293{
294 Ok(Command::new("sudo")
295 .args(args)
296 .stdin(Stdio::null())
297 .stdout(Stdio::piped())
298 .stderr(Stdio::piped())
299 .spawn()?)
300}
301
302pub fn drop_sudo_privileges() -> Result<()> {
304 Command::new("sudo")
305 .arg("-k")
306 .stdin(Stdio::null())
307 .stdout(Stdio::null())
308 .stderr(Stdio::null())
309 .spawn()?;
310 Ok(())
311}
312
313pub fn reset_sudo_faillock() -> Result<()> {
316 Command::new("faillock")
317 .arg("--user")
318 .arg(current_username()?)
319 .arg("--reset")
320 .stdin(Stdio::null())
321 .stdout(Stdio::null())
322 .stderr(Stdio::null())
323 .spawn()?;
324 Ok(())
325}
326
327pub fn set_sudo_session(password: &mut PasswordHolder) -> Result<bool> {
331 let root_path = std::path::Path::new("/");
332 let (success, _, _) = execute_sudo_command_with_password(
334 &["ls", "/root"],
335 password
336 .sudo()
337 .as_ref()
338 .context("sudo password isn't set")?,
339 root_path,
340 )?;
341 Ok(success)
342}
343
344#[tokio::main]
346pub async fn inject_command(mut command: TokioCommand, injector: Injector<String>) {
347 let Ok(mut cmd) = command
348 .stdout(Stdio::piped()) .spawn()
350 else {
351 log_info!("Cannot spawn command");
352 return;
353 };
354 let Some(stdout) = cmd.stdout.take() else {
355 log_info!("no stdout");
356 return;
357 };
358 let mut lines = TokioBufReader::new(stdout).lines();
359 while let Ok(opt_line) = lines.next_line().await {
360 let Some(line) = opt_line else {
361 break;
362 };
363 injector.push(line.clone(), |line, cols| {
364 cols[0] = line.as_str().into();
365 });
366 }
367}
368
369pub fn build_tokio_greper() -> Option<TokioCommand> {
371 let shell_command = if is_in_path(RG_EXECUTABLE) {
372 RG_EXECUTABLE
373 } else if is_in_path(GREP_EXECUTABLE) {
374 GREP_EXECUTABLE
375 } else {
376 return None;
377 };
378 let mut args: Vec<_> = shell_command.split_whitespace().collect();
379 if args.is_empty() {
380 return None;
381 }
382 let grep = args.remove(0);
383 let mut tokio_greper = TokioCommand::new(grep);
384 tokio_greper.args(&args);
385 Some(tokio_greper)
386}
387
388pub fn execute_in_shell<P>(args: &[&str], path: P) -> Result<bool>
390where
391 P: AsRef<Path>,
392{
393 let shell = env::var("SHELL").unwrap_or_else(|_| "bash".to_string());
394 let mut command = Command::new(&shell);
395 command.current_dir(path);
396 if !args.is_empty() {
397 command.arg("-c").args(args);
398 }
399 log_info!("execute_in_shell: shell: {shell}, args: {args:?}");
400 let success = command.status()?.success();
401 if !success {
402 log_info!("Shell exited with non-zero status:");
403 }
404 Ok(success)
405}