Skip to content

Commit 4273ef0

Browse files
committed
feat: proper interactive sudo support
1 parent d8ccc71 commit 4273ef0

3 files changed

Lines changed: 60 additions & 20 deletions

File tree

src/local_logger.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ lazy_static! {
2727
pub fn suspend_progress_bar<F: FnOnce() -> R, R>(f: F) -> R {
2828
// If the output is a TTY, and there is a spinner, suspend it
2929
if *IS_TTY {
30-
if let Ok(mut spinner) = SPINNER.lock() {
30+
// Use try_lock to avoid deadlock on reentrant calls
31+
if let Ok(mut spinner) = SPINNER.try_lock() {
3132
if let Some(spinner) = spinner.as_mut() {
3233
return spinner.suspend(f);
3334
}

src/run/runner/helpers/run_with_sudo.rs

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,68 @@
1-
use crate::prelude::*;
2-
use std::process::{Command, Stdio};
1+
use crate::{local_logger::suspend_progress_bar, prelude::*};
2+
use std::{
3+
io::IsTerminal,
4+
process::{Command, Stdio},
5+
};
36

4-
/// Run a command with sudo if available
5-
pub fn run_with_sudo(command_args: &[&str]) -> Result<()> {
6-
let use_sudo = Command::new("sudo")
7-
// `sudo true` will fail if sudo does not exist or the current user does not have sudo privileges
8-
.arg("true")
9-
.stdout(Stdio::null())
10-
.status()
11-
.is_ok_and(|status| status.success());
12-
let mut command_args: Vec<&str> = command_args.into();
13-
if use_sudo {
14-
command_args.insert(0, "sudo");
7+
/// Validate sudo access, prompting the user for their password if necessary
8+
fn validate_sudo_access() -> Result<()> {
9+
let needs_password = IsTerminal::is_terminal(&std::io::stdout())
10+
&& Command::new("sudo")
11+
.arg("--non-interactive") // Fail if password is required
12+
.arg("true")
13+
.stdout(Stdio::null())
14+
.stderr(Stdio::null())
15+
.status()
16+
.map(|status| !status.success())
17+
.unwrap_or(true);
18+
19+
if needs_password {
20+
suspend_progress_bar(|| {
21+
info!(
22+
"Sudo privileges are required to continue. Please enter your password if prompted."
23+
);
24+
25+
// Validate and cache sudo credentials
26+
let auth_status = Command::new("sudo")
27+
.arg("--validate") // Validate and extend the timeout
28+
.stdin(Stdio::inherit())
29+
.stdout(Stdio::inherit())
30+
.stderr(Stdio::inherit())
31+
.status()
32+
.map_err(|_| anyhow!("Failed to authenticate with sudo"))?;
33+
34+
if !auth_status.success() {
35+
bail!("Failed to authenticate with sudo");
36+
}
37+
Ok(())
38+
})?;
1539
}
40+
Ok(())
41+
}
42+
43+
/// Creates the base sudo command after validating sudo access
44+
pub fn sudo_command() -> Result<Command> {
45+
validate_sudo_access()?;
46+
let mut cmd = Command::new("sudo");
47+
// Password prompt should not appear here since it has already been validated
48+
cmd.arg("--non-interactive");
49+
Ok(cmd)
50+
}
1651

17-
debug!("Running command: {}", command_args.join(" "));
18-
let output = Command::new(command_args[0])
19-
.args(&command_args[1..])
52+
/// Run a command with sudo after validating sudo access
53+
pub fn run_with_sudo(command_args: &[&str]) -> Result<()> {
54+
let command_str = command_args.join(" ");
55+
debug!("Running command with sudo: {command_str}");
56+
let output = sudo_command()?
57+
.args(command_args)
2058
.stdout(Stdio::piped())
2159
.output()
22-
.map_err(|_| anyhow!("Failed to execute command: {}", command_args.join(" ")))?;
60+
.map_err(|_| anyhow!("Failed to execute command with sudo: {command_str}"))?;
2361

2462
if !output.status.success() {
2563
info!("stdout: {}", String::from_utf8_lossy(&output.stdout));
2664
error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
27-
bail!("Failed to execute command: {}", command_args.join(" "));
65+
bail!("Failed to execute command with sudo: {command_str}");
2866
}
2967

3068
Ok(())

src/run/runner/wall_time/executor.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::run::runner::helpers::get_bench_command::get_bench_command;
88
use crate::run::runner::helpers::introspected_golang;
99
use crate::run::runner::helpers::introspected_nodejs;
1010
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe;
11+
use crate::run::runner::helpers::run_with_sudo::sudo_command;
1112
use crate::run::runner::{ExecutorName, RunData};
1213
use crate::run::{check_system::SystemInfo, config::Config};
1314
use async_trait::async_trait;
@@ -178,7 +179,7 @@ impl Executor for WallTimeExecutor {
178179
run_data: &RunData,
179180
_mongo_tracer: &Option<MongoTracer>,
180181
) -> Result<()> {
181-
let mut cmd = Command::new("sudo");
182+
let mut cmd = sudo_command()?;
182183

183184
if let Some(cwd) = &config.working_directory {
184185
let abs_cwd = canonicalize(cwd)?;

0 commit comments

Comments
 (0)