Skip to content

Commit 868f917

Browse files
committed
fix(sudo): handle root user case
1 parent e89b00b commit 868f917

3 files changed

Lines changed: 79 additions & 41 deletions

File tree

src/run/runner/helpers/run_with_sudo.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ use std::{
44
process::{Command, Stdio},
55
};
66

7+
fn is_sudo_available() -> bool {
8+
Command::new("sudo")
9+
.arg("--version")
10+
.stdout(Stdio::null())
11+
.stderr(Stdio::null())
12+
.status()
13+
.map(|status| status.success())
14+
.unwrap_or(false)
15+
}
16+
717
/// Validate sudo access, prompting the user for their password if necessary
818
fn validate_sudo_access() -> Result<()> {
919
let needs_password = IsTerminal::is_terminal(&std::io::stdout())
@@ -37,32 +47,48 @@ fn validate_sudo_access() -> Result<()> {
3747
Ok(())
3848
})?;
3949
}
50+
debug!("Sudo access validated");
4051
Ok(())
4152
}
4253

4354
/// Creates the base sudo command after validating sudo access
44-
pub fn validated_sudo_command() -> Result<Command> {
55+
fn validated_sudo_command() -> Result<Command> {
4556
validate_sudo_access()?;
4657
let mut cmd = Command::new("sudo");
4758
// Password prompt should not appear here since it has already been validated
4859
cmd.arg("--non-interactive");
4960
Ok(cmd)
5061
}
5162

52-
/// Run a command with sudo after validating sudo access
63+
/// Build a command wrapped with sudo if possible
64+
pub fn build_command_with_sudo(command_args: &[&str]) -> Result<Command> {
65+
let command_str = command_args.join(" ");
66+
if is_sudo_available() {
67+
debug!("Sudo is required for command: {command_str}");
68+
let mut c = validated_sudo_command()?;
69+
c.args(command_args);
70+
Ok(c)
71+
} else {
72+
debug!("Running command without sudo: {command_str}");
73+
let mut c = Command::new(command_args[0]);
74+
c.args(&command_args[1..]);
75+
Ok(c)
76+
}
77+
}
78+
79+
/// Run a command with sudo after validating sudo access (if possible)
5380
pub fn run_with_sudo(command_args: &[&str]) -> Result<()> {
81+
let mut cmd = build_command_with_sudo(command_args)?;
5482
let command_str = command_args.join(" ");
55-
debug!("Running command with sudo: {command_str}");
56-
let output = validated_sudo_command()?
57-
.args(command_args)
83+
let output = cmd
5884
.stdout(Stdio::piped())
5985
.output()
60-
.map_err(|_| anyhow!("Failed to execute command with sudo: {command_str}"))?;
86+
.map_err(|_| anyhow!("Failed to execute: {command_str}"))?;
6187

6288
if !output.status.success() {
6389
info!("stdout: {}", String::from_utf8_lossy(&output.stdout));
6490
error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
65-
bail!("Failed to execute command with sudo: {command_str}");
91+
bail!("Failed to execute command: {command_str}");
6692
}
6793

6894
Ok(())

src/run/runner/wall_time/executor.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +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::validated_sudo_command;
11+
use crate::run::runner::helpers::run_with_sudo::build_command_with_sudo;
1212
use crate::run::runner::{ExecutorName, RunData};
1313
use crate::run::{check_system::SystemInfo, config::Config};
1414
use async_trait::async_trait;
@@ -100,7 +100,7 @@ impl WallTimeExecutor {
100100
fn walltime_bench_cmd(
101101
config: &Config,
102102
run_data: &RunData,
103-
) -> Result<(NamedTempFile, NamedTempFile, String)> {
103+
) -> Result<(NamedTempFile, NamedTempFile, Vec<String>)> {
104104
let bench_cmd = get_bench_command(config)?;
105105

106106
let system_env = get_exported_system_env()?;
@@ -150,11 +150,24 @@ impl WallTimeExecutor {
150150
// - We have to pass the environment variables because `--scope` only inherits the system and not the user environment variables.
151151
let uid = nix::unistd::Uid::current().as_raw();
152152
let gid = nix::unistd::Gid::current().as_raw();
153-
let cmd = format!(
154-
"systemd-run {quiet_flag} --scope --slice=codspeed.slice --same-dir --uid={uid} --gid={gid} -- bash {}",
155-
script_file.path().display()
156-
);
157-
Ok((env_file, script_file, cmd))
153+
let script_path = script_file.path().to_string_lossy().to_string();
154+
let cmd = [
155+
"systemd-run",
156+
quiet_flag,
157+
"--scope",
158+
"--slice=codspeed.slice",
159+
"--same-dir",
160+
&format!("--uid={uid}"),
161+
&format!("--gid={gid}"),
162+
"--",
163+
"bash",
164+
&script_path,
165+
];
166+
Ok((
167+
env_file,
168+
script_file,
169+
cmd.into_iter().map(|s| s.to_string()).collect(),
170+
))
158171
}
159172
}
160173

@@ -179,25 +192,24 @@ impl Executor for WallTimeExecutor {
179192
run_data: &RunData,
180193
_mongo_tracer: &Option<MongoTracer>,
181194
) -> Result<()> {
182-
let mut cmd = validated_sudo_command()?;
183-
184-
if let Some(cwd) = &config.working_directory {
185-
let abs_cwd = canonicalize(cwd)?;
186-
cmd.current_dir(abs_cwd);
187-
}
188-
189195
let status = {
190196
let _guard = HookScriptsGuard::setup();
191197

198+
// Keep the temporary files alive for the duration of the command execution
192199
let (_env_file, _script_file, bench_cmd) = Self::walltime_bench_cmd(config, run_data)?;
193200
if let Some(perf) = &self.perf
194201
&& config.enable_perf
195202
{
196-
perf.run(cmd, &bench_cmd, config).await
203+
perf.run(bench_cmd, config).await
197204
} else {
198-
cmd.args(["sh", "-c", &bench_cmd]);
205+
let mut cmd = build_command_with_sudo(
206+
&bench_cmd.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
207+
)?;
199208
debug!("cmd: {cmd:?}");
200-
209+
if let Some(cwd) = &config.working_directory {
210+
let abs_cwd = canonicalize(cwd)?;
211+
cmd.current_dir(abs_cwd);
212+
}
201213
run_command_with_log_pipe(cmd).await
202214
}
203215
};

src/run/runner/wall_time/perf/mod.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::run::UnwindingMode;
55
use crate::run::config::Config;
66
use crate::run::runner::helpers::env::is_codspeed_debug_enabled;
77
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback;
8+
use crate::run::runner::helpers::run_with_sudo::build_command_with_sudo;
89
use crate::run::runner::helpers::run_with_sudo::run_with_sudo;
910
use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore;
1011
use crate::run::runner::valgrind::helpers::perf_maps::harvest_perf_maps_for_pids;
@@ -24,8 +25,8 @@ use runner_shared::fifo::MarkerType;
2425
use runner_shared::metadata::PerfMetadata;
2526
use runner_shared::unwind_data::UnwindData;
2627
use std::collections::HashSet;
28+
use std::fs::canonicalize;
2729
use std::path::{Path, PathBuf};
28-
use std::process::Command;
2930
use std::time::Duration;
3031
use std::{cell::OnceCell, collections::HashMap, process::ExitStatus};
3132

@@ -84,12 +85,7 @@ impl PerfRunner {
8485
}
8586
}
8687

87-
pub async fn run(
88-
&self,
89-
mut cmd: Command,
90-
bench_cmd: &str,
91-
config: &Config,
92-
) -> anyhow::Result<ExitStatus> {
88+
pub async fn run(&self, bench_cmd: Vec<String>, config: &Config) -> anyhow::Result<ExitStatus> {
9389
let perf_fifo = PerfFifo::new()?;
9490
let runner_fifo = RunnerFifo::new()?;
9591

@@ -111,11 +107,11 @@ impl PerfRunner {
111107
(UnwindingMode::Dwarf, None)
112108
};
113109

114-
let cg_mode = match cg_mode {
115-
UnwindingMode::FramePointer => "fp",
116-
UnwindingMode::Dwarf => &format!("dwarf,{}", stack_size.unwrap_or(8192)),
110+
let cg_mode_str = match cg_mode {
111+
UnwindingMode::FramePointer => "fp".to_string(),
112+
UnwindingMode::Dwarf => format!("dwarf,{}", stack_size.unwrap_or(8192)),
117113
};
118-
debug!("Using call graph mode: {cg_mode:?}");
114+
debug!("Using call graph mode: {cg_mode_str:?}");
119115

120116
let quiet_flag = if !is_codspeed_debug_enabled() {
121117
"--quiet"
@@ -126,6 +122,7 @@ impl PerfRunner {
126122
let working_perf_executable =
127123
get_working_perf_executable().context("Failed to find a working perf executable")?;
128124

125+
let bench_str = bench_cmd.join(" ");
129126
let perf_args = [
130127
working_perf_executable.as_str(),
131128
"record",
@@ -138,20 +135,23 @@ impl PerfRunner {
138135
"--delay=-1",
139136
"-g",
140137
"--user-callchains",
141-
&format!("--call-graph={cg_mode}"),
138+
&format!("--call-graph={cg_mode_str}"),
142139
&format!(
143140
"--control=fifo:{},{}",
144141
perf_fifo.ctl_fifo_path.to_string_lossy(),
145142
perf_fifo.ack_fifo_path.to_string_lossy()
146143
),
147144
&format!("--output={PERF_DATA_PATH}"),
148145
"--",
149-
bench_cmd,
146+
&bench_str,
150147
];
151-
152-
cmd.args(["sh", "-c", &perf_args.join(" ")]);
153-
debug!("cmd: {cmd:?}");
154-
148+
let perf_cmd = perf_args.join(" ");
149+
debug!("raw perf cmd: {perf_cmd:?}");
150+
let mut cmd = build_command_with_sudo(&["sh", "-c", &perf_cmd])?;
151+
if let Some(cwd) = &config.working_directory {
152+
let abs_cwd = canonicalize(cwd)?;
153+
cmd.current_dir(abs_cwd);
154+
}
155155
let on_process_started = async |_| -> anyhow::Result<()> {
156156
let data = Self::handle_fifo(runner_fifo, perf_fifo).await?;
157157
let _ = self.benchmark_data.set(data);

0 commit comments

Comments
 (0)