105 lines
3.7 KiB
Rust
105 lines
3.7 KiB
Rust
use std::process::Stdio;
|
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
|
use tokio::process::Command;
|
|
|
|
use crate::core::types::AppError;
|
|
|
|
pub struct PrivilegedCommandRunner;
|
|
|
|
impl PrivilegedCommandRunner {
|
|
/// Проверка, запущен ли процесс уже с root-правами
|
|
pub fn is_root() -> bool {
|
|
// Проверка через EUID (в Linux root = 0)
|
|
unsafe { libc_geteuid() == 0 }
|
|
}
|
|
|
|
/// Асинхронный запуск команды с повышением привилегий через pkexec (Polkit).
|
|
/// Вывод процесса построчно передается в callback `on_output_line`.
|
|
/// UI-поток Flutter не блокируется, так как выполнение происходит в Tokio runtime.
|
|
pub async fn run_privileged<F>(
|
|
program: &str,
|
|
args: &[&str],
|
|
mut on_output_line: F,
|
|
) -> Result<(), AppError>
|
|
where
|
|
F: FnMut(String) + Send + 'static,
|
|
{
|
|
let mut cmd = if Self::is_root() {
|
|
let mut c = Command::new(program);
|
|
c.args(args);
|
|
c
|
|
} else {
|
|
// Запуск через PolKit (pkexec) вызывает нативный GUI-диалог авторизации Linux
|
|
let mut c = Command::new("pkexec");
|
|
c.arg(program).args(args);
|
|
c
|
|
};
|
|
|
|
cmd.stdout(Stdio::piped());
|
|
cmd.stderr(Stdio::piped());
|
|
|
|
let mut child = cmd.spawn().map_err(|e| {
|
|
AppError::ExecutionFailed(format!("Failed to spawn privileged command {}: {}", program, e))
|
|
})?;
|
|
|
|
// Читаем stdout асинхронно построчно
|
|
let stdout = child.stdout.take();
|
|
let stderr = child.stderr.take();
|
|
|
|
let stdout_handle = tokio::spawn(async move {
|
|
let mut lines_vec = Vec::new();
|
|
if let Some(stdout) = stdout {
|
|
let mut reader = BufReader::new(stdout).lines();
|
|
while let Ok(Some(line)) = reader.next_line().await {
|
|
lines_vec.push(line);
|
|
}
|
|
}
|
|
lines_vec
|
|
});
|
|
|
|
let stderr_handle = tokio::spawn(async move {
|
|
let mut err_vec = Vec::new();
|
|
if let Some(stderr) = stderr {
|
|
let mut reader = BufReader::new(stderr).lines();
|
|
while let Ok(Some(line)) = reader.next_line().await {
|
|
err_vec.push(line);
|
|
}
|
|
}
|
|
err_vec
|
|
});
|
|
|
|
// Ожидаем завершения процесса
|
|
let status = child.wait().await.map_err(|e| {
|
|
AppError::ExecutionFailed(format!("Failed waiting for child process: {}", e))
|
|
})?;
|
|
|
|
if let Ok(lines) = stdout_handle.await {
|
|
for line in lines {
|
|
on_output_line(line);
|
|
}
|
|
}
|
|
|
|
if let Ok(err_lines) = stderr_handle.await {
|
|
for line in err_lines {
|
|
on_output_line(format!("[STDERR] {}", line));
|
|
}
|
|
}
|
|
|
|
match status.code() {
|
|
Some(0) => Ok(()),
|
|
Some(126) => Err(AppError::PrivilegeDenied("Polkit authorization was cancelled or dismissed by user".into())),
|
|
Some(127) => Err(AppError::PrivilegeDenied("Polkit authorization failed or pkexec not found".into())),
|
|
Some(code) => Err(AppError::ExecutionFailed(format!("Process exited with error code: {}", code))),
|
|
None => Err(AppError::ExecutionFailed("Process terminated by signal".into())),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback функция получения euid без лишней зависимости от nix
|
|
unsafe fn libc_geteuid() -> u32 {
|
|
extern "C" {
|
|
fn geteuid() -> u32;
|
|
}
|
|
geteuid()
|
|
}
|