use std::env;
use std::fmt;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
use anyhow::{anyhow, bail, Context, Result};
use nucleo::Injector;
use tokio::{
io::AsyncBufReadExt, io::BufReader as TokioBufReader, process::Command as TokioCommand,
};
use crate::common::{current_username, is_in_path, GREP_EXECUTABLE, RG_EXECUTABLE, SETSID};
use crate::modes::PasswordHolder;
use crate::{log_info, log_line};
pub fn execute<S, P>(exe: S, args: &[P]) -> Result<std::process::Child>
where
S: AsRef<std::ffi::OsStr> + fmt::Debug,
P: AsRef<std::ffi::OsStr> + fmt::Debug,
{
log_info!("execute. executable: {exe:?}, arguments: {args:?}");
log_line!("Execute: {exe:?}, arguments: {args:?}");
if is_in_path(SETSID) {
Ok(Command::new(SETSID)
.arg(exe)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?)
} else {
Ok(Command::new(exe)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?)
}
}
pub fn execute_without_output<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
exe: S,
args: &[&str],
) -> Result<std::process::Child> {
log_info!("execute_in_child_without_output. executable: {exe:?}, arguments: {args:?}",);
Ok(Command::new(exe)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?)
}
pub fn execute_and_capture_output<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
exe: S,
args: &[&str],
) -> Result<String> {
log_info!("execute_and_capture_output. executable: {exe:?}, arguments: {args:?}",);
let output = Command::new(exe)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?)
} else {
Err(anyhow!(
"execute_and_capture_output: command didn't finish properly",
))
}
}
pub fn command_with_path<S: AsRef<std::ffi::OsStr> + fmt::Debug, P: AsRef<Path>>(
exe: S,
path: P,
args: &[&str],
) -> Command {
let mut command = Command::new(exe);
command
.args(args)
.current_dir(path)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
command
}
pub fn execute_and_capture_output_with_path<
S: AsRef<std::ffi::OsStr> + fmt::Debug,
P: AsRef<Path>,
>(
exe: S,
path: P,
args: &[&str],
) -> Result<String> {
log_info!("execute_and_capture_output_with_path. executable: {exe:?}, arguments: {args:?}",);
let output = Command::new(exe)
.args(args)
.current_dir(path)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?)
} else {
let err = String::from_utf8(output.stderr)?;
log_info!("{err}");
Err(anyhow!(
"execute_and_capture_output: command didn't finish properly: {err}",
))
}
}
pub fn execute_and_capture_output_without_check<S>(exe: S, args: &[&str]) -> Result<String>
where
S: AsRef<std::ffi::OsStr> + fmt::Debug,
{
log_info!("execute_and_capture_output_without_check. executable: {exe:?}, arguments: {args:?}",);
let child = Command::new(exe)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
let output = child.wait_with_output()?;
Ok(String::from_utf8(output.stdout)?)
}
pub fn execute_and_output<S, I>(exe: S, args: I) -> Result<std::process::Output>
where
S: AsRef<std::ffi::OsStr> + fmt::Debug,
I: IntoIterator<Item = S> + fmt::Debug,
{
log_info!("execute_and_output. executable: {exe:?}, arguments: {args:?}",);
Ok(Command::new(exe)
.args(args)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()?)
}
pub fn execute_and_output_no_log<S, I>(exe: S, args: I) -> Result<std::process::Output>
where
S: AsRef<std::ffi::OsStr> + fmt::Debug,
I: IntoIterator<Item = S> + fmt::Debug,
{
Ok(Command::new(exe).args(args).stdin(Stdio::null()).output()?)
}
pub fn execute_with_ansi_colors(args: &[String]) -> Result<std::process::Output> {
log_info!("execute. {args:?}");
log_line!("Executed {args:?}");
Ok(Command::new(&args[0])
.args(&args[1..])
.env("CLICOLOR_FORCE", "1")
.env("COLORTERM", "ansi")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()?)
}
fn new_sudo_command_awaiting_password<S, P>(args: &[S], path: P) -> Result<std::process::Child>
where
S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
P: AsRef<std::path::Path> + std::fmt::Debug,
{
Ok(Command::new("sudo")
.arg("-S")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(path)
.spawn()?)
}
fn inject_password(password: &str, child: &mut std::process::Child) -> Result<()> {
let child_stdin = child
.stdin
.as_mut()
.context("inject_password: couldn't open child stdin")?;
child_stdin.write_all(password.as_bytes())?;
child_stdin.write_all(b"\n")?;
Ok(())
}
pub fn execute_sudo_command_with_password<S, P>(
args: &[S],
password: &str,
path: P,
) -> Result<(bool, String, String)>
where
S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
P: AsRef<std::path::Path> + std::fmt::Debug,
{
execute_sudo_command_inner(args, Some(password), Some(path))
}
pub fn execute_sudo_command_passwordless<S>(args: &[S]) -> Result<(bool, String, String)>
where
S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
{
execute_sudo_command_inner::<S, &str>(args, None, None)
}
fn execute_sudo_command_inner<S, P>(
args: &[S],
password: Option<&str>,
path: Option<P>,
) -> Result<(bool, String, String)>
where
S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
P: AsRef<std::path::Path> + std::fmt::Debug,
{
log_info!("running sudo {args:?}");
log_line!("running sudo command. {args:?}");
let child = match (password, path) {
(None, None) => new_sudo_command_passwordless(args)?,
(Some(password), Some(path)) => {
log_info!("CWD {path:?}");
let mut child = new_sudo_command_awaiting_password(args, path)?;
inject_password(password, &mut child)?;
log_info!("injected sudo password");
child
}
_ => bail!("Password and Path should be set together"),
};
run_and_output(child)
}
fn run_and_output(child: std::process::Child) -> Result<(bool, String, String)> {
let output = child.wait_with_output()?;
Ok((
output.status.success(),
String::from_utf8(output.stdout)?,
String::from_utf8(output.stderr)?,
))
}
fn new_sudo_command_passwordless<S>(args: &[S]) -> Result<std::process::Child>
where
S: AsRef<std::ffi::OsStr> + std::fmt::Debug,
{
Ok(Command::new("sudo")
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?)
}
pub fn drop_sudo_privileges() -> Result<()> {
Command::new("sudo")
.arg("-k")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
Ok(())
}
pub fn reset_sudo_faillock() -> Result<()> {
Command::new("faillock")
.arg("--user")
.arg(current_username()?)
.arg("--reset")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
Ok(())
}
pub fn set_sudo_session(password: &mut PasswordHolder) -> Result<bool> {
let root_path = std::path::Path::new("/");
let (success, _, _) = execute_sudo_command_with_password(
&["ls", "/root"],
password
.sudo()
.as_ref()
.context("sudo password isn't set")?,
root_path,
)?;
Ok(success)
}
#[tokio::main]
pub async fn inject_command(mut command: TokioCommand, injector: Injector<String>) {
let Ok(mut cmd) = command
.stdout(Stdio::piped()) .spawn()
else {
log_info!("Cannot spawn command");
return;
};
let Some(stdout) = cmd.stdout.take() else {
log_info!("no stdout");
return;
};
let mut lines = TokioBufReader::new(stdout).lines();
while let Ok(opt_line) = lines.next_line().await {
let Some(line) = opt_line else {
break;
};
injector.push(line.clone(), |line, cols| {
cols[0] = line.as_str().into();
});
}
}
pub fn build_tokio_greper() -> Option<TokioCommand> {
let shell_command = if is_in_path(RG_EXECUTABLE) {
RG_EXECUTABLE
} else if is_in_path(GREP_EXECUTABLE) {
GREP_EXECUTABLE
} else {
return None;
};
let mut args: Vec<_> = shell_command.split_whitespace().collect();
if args.is_empty() {
return None;
}
let grep = args.remove(0);
let mut tokio_greper = TokioCommand::new(grep);
tokio_greper.args(&args);
Some(tokio_greper)
}
pub fn execute_in_shell(args: &[&str]) -> Result<bool> {
let shell = env::var("SHELL").unwrap_or_else(|_| "bash".to_string());
let mut command = Command::new(&shell);
if !args.is_empty() {
command.arg("-c").args(args);
}
log_info!("execute_in_shell: shell: {shell}, args: {args:?}");
let success = command.status()?.success();
if !success {
log_info!("Shell exited with non-zero status:");
}
Ok(success)
}