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<
115 S: AsRef<std::ffi::OsStr> + fmt::Debug,
116 P: AsRef<Path>,
117>(
118 exe: S,
119 path: P,
120 args: &[&str],
121) -> Result<String> {
122 log_info!("execute_and_capture_output_with_path. executable: {exe:?}, arguments: {args:?}",);
123 let output = Command::new(exe)
124 .args(args)
125 .current_dir(path)
126 .stdin(Stdio::null())
127 .stdout(Stdio::piped())
128 .stderr(Stdio::piped())
129 .output()?;
130 if output.status.success() {
131 Ok(String::from_utf8(output.stdout)?)
132 } else {
133 let err = String::from_utf8(output.stderr)?;
134 log_info!("{err}");
135 Err(anyhow!(
136 "execute_and_capture_output: command didn't finish properly: {err}",
137 ))
138 }
139}
140
141pub fn execute_and_capture_output_without_check<S>(exe: S, args: &[&str]) -> Result<String>
145where
146 S: AsRef<std::ffi::OsStr> + fmt::Debug,
147{
148 log_info!("execute_and_capture_output_without_check. executable: {exe:?}, arguments: {args:?}",);
149 let child = Command::new(exe)
150 .args(args)
151 .stdin(Stdio::null())
152 .stdout(Stdio::piped())
153 .stderr(Stdio::null())
154 .spawn()?;
155 let output = child.wait_with_output()?;
156 Ok(String::from_utf8(output.stdout)?)
157}
158
159pub fn execute_and_output<S, I>(exe: S, args: I) -> Result<std::process::Output>
161where
162 S: AsRef<std::ffi::OsStr> + fmt::Debug,
163 I: IntoIterator<Item = S> + fmt::Debug,
164{
165 log_info!("execute_and_output. executable: {exe:?}, arguments: {args:?}",);
166 Ok(Command::new(exe)
167 .args(args)
168 .stdin(Stdio::null())
169 .stderr(Stdio::null())
170 .output()?)
171}
172
173pub fn execute_and_output_no_log<S, I>(exe: S, args: I) -> Result<std::process::Output>
175where
176 S: AsRef<std::ffi::OsStr> + fmt::Debug,
177 I: IntoIterator<Item = S> + fmt::Debug,
178{
179 Ok(Command::new(exe).args(args).stdin(Stdio::null()).output()?)
180}
181
182pub fn execute_with_ansi_colors(args: &[String]) -> Result<std::process::Output> {
186 log_info!("execute. {args:?}");
187 log_line!("Executed {args:?}");
188 Ok(Command::new(&args[0])
189 .args(&args[1..])
190 .env("CLICOLOR_FORCE", "1")
191 .env("COLORTERM", "ansi")
192 .stdin(Stdio::null())
193 .stdout(Stdio::piped())
194 .stderr(Stdio::null())
195 .output()?)
196}
197
198fn new_sudo_command_awaiting_password<S, P>(args: &[S], path: P) -> Result<std::process::Child>
205where
206 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
207 P: AsRef<std::path::Path> + std::fmt::Debug,
208{
209 Ok(Command::new("sudo")
210 .arg("-S")
211 .args(args)
212 .stdin(Stdio::piped())
213 .stdout(Stdio::piped())
214 .stderr(Stdio::piped())
215 .current_dir(path)
216 .spawn()?)
217}
218
219fn inject_password(password: &str, child: &mut std::process::Child) -> Result<()> {
221 let child_stdin = child
222 .stdin
223 .as_mut()
224 .context("inject_password: couldn't open child stdin")?;
225 child_stdin.write_all(password.as_bytes())?;
226 child_stdin.write_all(b"\n")?;
227 Ok(())
228}
229
230pub fn execute_sudo_command_with_password<S, P>(
234 args: &[S],
235 password: &str,
236 path: P,
237) -> Result<(bool, String, String)>
238where
239 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
240 P: AsRef<std::path::Path> + std::fmt::Debug,
241{
242 execute_sudo_command_inner(args, Some(password), Some(path))
243}
244
245pub fn execute_sudo_command_passwordless<S>(args: &[S]) -> Result<(bool, String, String)>
248where
249 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
250{
251 execute_sudo_command_inner::<S, &str>(args, None, None)
252}
253
254fn execute_sudo_command_inner<S, P>(
255 args: &[S],
256 password: Option<&str>,
257 path: Option<P>,
258) -> Result<(bool, String, String)>
259where
260 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
261 P: AsRef<std::path::Path> + std::fmt::Debug,
262{
263 log_info!("running sudo {args:?}");
264 log_line!("running sudo command. {args:?}");
265 let child = match (password, path) {
266 (None, None) => new_sudo_command_passwordless(args)?,
267 (Some(password), Some(path)) => {
268 log_info!("CWD {path:?}");
269 let mut child = new_sudo_command_awaiting_password(args, path)?;
270 inject_password(password, &mut child)?;
271 log_info!("injected sudo password");
272 child
273 }
274 _ => bail!("Password and Path should be set together"),
275 };
276 run_and_output(child)
277}
278
279fn run_and_output(child: std::process::Child) -> Result<(bool, String, String)> {
280 let output = child.wait_with_output()?;
281 Ok((
282 output.status.success(),
283 String::from_utf8(output.stdout)?,
284 String::from_utf8(output.stderr)?,
285 ))
286}
287fn new_sudo_command_passwordless<S>(args: &[S]) -> Result<std::process::Child>
290where
291 S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
292{
293 Ok(Command::new("sudo")
294 .args(args)
295 .stdin(Stdio::null())
296 .stdout(Stdio::piped())
297 .stderr(Stdio::piped())
298 .spawn()?)
299}
300
301pub fn drop_sudo_privileges() -> Result<()> {
303 Command::new("sudo")
304 .arg("-k")
305 .stdin(Stdio::null())
306 .stdout(Stdio::null())
307 .stderr(Stdio::null())
308 .spawn()?;
309 Ok(())
310}
311
312pub fn reset_sudo_faillock() -> Result<()> {
315 Command::new("faillock")
316 .arg("--user")
317 .arg(current_username()?)
318 .arg("--reset")
319 .stdin(Stdio::null())
320 .stdout(Stdio::null())
321 .stderr(Stdio::null())
322 .spawn()?;
323 Ok(())
324}
325
326pub fn set_sudo_session(password: &mut PasswordHolder) -> Result<bool> {
330 let root_path = std::path::Path::new("/");
331 let (success, _, _) = execute_sudo_command_with_password(
333 &["ls", "/root"],
334 password
335 .sudo()
336 .as_ref()
337 .context("sudo password isn't set")?,
338 root_path,
339 )?;
340 Ok(success)
341}
342
343#[tokio::main]
345pub async fn inject_command(mut command: TokioCommand, injector: Injector<String>) {
346 let Ok(mut cmd) = command
347 .stdout(Stdio::piped()) .spawn()
349 else {
350 log_info!("Cannot spawn command");
351 return;
352 };
353 let Some(stdout) = cmd.stdout.take() else {
354 log_info!("no stdout");
355 return;
356 };
357 let mut lines = TokioBufReader::new(stdout).lines();
358 while let Ok(opt_line) = lines.next_line().await {
359 let Some(line) = opt_line else {
360 break;
361 };
362 injector.push(line.clone(), |line, cols| {
363 cols[0] = line.as_str().into();
364 });
365 }
366}
367
368pub fn build_tokio_greper() -> Option<TokioCommand> {
370 let shell_command = if is_in_path(RG_EXECUTABLE) {
371 RG_EXECUTABLE
372 } else if is_in_path(GREP_EXECUTABLE) {
373 GREP_EXECUTABLE
374 } else {
375 return None;
376 };
377 let mut args: Vec<_> = shell_command.split_whitespace().collect();
378 if args.is_empty() {
379 return None;
380 }
381 let grep = args.remove(0);
382 let mut tokio_greper = TokioCommand::new(grep);
383 tokio_greper.args(&args);
384 Some(tokio_greper)
385}
386
387pub fn execute_in_shell(args: &[&str]) -> Result<bool> {
389 let shell = env::var("SHELL").unwrap_or_else(|_| "bash".to_string());
390 let mut command = Command::new(&shell);
391 if !args.is_empty() {
392 command.arg("-c").args(args);
393 }
394 log_info!("execute_in_shell: shell: {shell}, args: {args:?}");
395 let success = command.status()?.success();
396 if !success {
397 log_info!("Shell exited with non-zero status:");
398 }
399 Ok(success)
400}