2022-11-25 17:19:32 -05:00
|
|
|
use std::ffi::OsStr;
|
2025-06-25 15:59:02 +02:00
|
|
|
use std::path::Path;
|
2022-11-24 14:15:43 -05:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
use color_eyre::eyre::Context;
|
|
|
|
|
use color_eyre::eyre::Result;
|
2023-03-13 19:23:37 +00:00
|
|
|
use serde::Deserialize;
|
2025-06-25 19:46:49 +02:00
|
|
|
use strum::Display;
|
2022-11-24 14:15:43 -05:00
|
|
|
|
|
|
|
|
use crate::command::CommandExt;
|
2025-06-25 19:46:49 +02:00
|
|
|
use crate::error::UnsupportedSudo;
|
2022-11-24 14:15:43 -05:00
|
|
|
use crate::execution_context::ExecutionContext;
|
2022-11-25 17:19:32 -05:00
|
|
|
use crate::executor::Executor;
|
2022-11-24 14:15:43 -05:00
|
|
|
use crate::terminal::print_separator;
|
|
|
|
|
use crate::utils::which;
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct Sudo {
|
|
|
|
|
/// The path to the `sudo` binary.
|
2025-09-26 14:58:35 +02:00
|
|
|
path: Option<PathBuf>,
|
2022-11-25 17:19:32 -05:00
|
|
|
/// The type of program being used as `sudo`.
|
|
|
|
|
kind: SudoKind,
|
2022-11-24 14:15:43 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-25 19:46:49 +02:00
|
|
|
#[derive(Clone, Debug, Default)]
|
2025-07-16 00:11:20 +02:00
|
|
|
pub enum SudoPreserveEnv<'a> {
|
|
|
|
|
/// Preserve all environment variables.
|
|
|
|
|
All,
|
|
|
|
|
/// Preserve only the specified environment variables.
|
|
|
|
|
Some(&'a [&'a str]),
|
|
|
|
|
/// Preserve no environment variables.
|
|
|
|
|
#[default]
|
|
|
|
|
None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generic sudo options, translated into flags to pass to `sudo`.
|
|
|
|
|
/// NOTE: Depending on the sudo kind, OS and system config, some options might be specified by
|
|
|
|
|
/// default or unsupported.
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
2025-06-25 19:46:49 +02:00
|
|
|
pub struct SudoExecuteOpts<'a> {
|
2025-09-28 14:53:42 +02:00
|
|
|
/// Run the command inside a login shell.
|
|
|
|
|
pub login_shell: bool,
|
2025-07-16 00:11:20 +02:00
|
|
|
/// Preserve environment variables across the sudo call.
|
|
|
|
|
pub preserve_env: SudoPreserveEnv<'a>,
|
2025-06-25 19:46:49 +02:00
|
|
|
/// Set the HOME environment variable to the target user's home directory.
|
|
|
|
|
pub set_home: bool,
|
|
|
|
|
/// Run the command as a user other than the root user.
|
|
|
|
|
pub user: Option<&'a str>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 00:11:20 +02:00
|
|
|
impl<'a> SudoExecuteOpts<'a> {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self::default()
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 14:53:42 +02:00
|
|
|
/// Run the command inside a login shell.
|
2025-07-16 00:11:20 +02:00
|
|
|
#[allow(unused)]
|
2025-09-28 14:53:42 +02:00
|
|
|
pub fn login_shell(mut self) -> Self {
|
|
|
|
|
self.login_shell = true;
|
2025-07-16 00:11:20 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Preserve all environment variables across the sudo call.
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
pub fn preserve_env(mut self) -> Self {
|
|
|
|
|
self.preserve_env = SudoPreserveEnv::All;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Preserve only the specified environment variables across the sudo call.
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
pub fn preserve_env_list(mut self, vars: &'a [&'a str]) -> Self {
|
|
|
|
|
self.preserve_env = SudoPreserveEnv::Some(vars);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the HOME environment variable to the target user's home directory.
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
pub fn set_home(mut self) -> Self {
|
|
|
|
|
self.set_home = true;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run the command as a user other than the root user.
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
pub fn user(mut self, user: &'a str) -> Self {
|
|
|
|
|
self.user = Some(user);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 22:35:46 +02:00
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
|
|
|
const DETECT_ORDER: [SudoKind; 5] = [
|
|
|
|
|
SudoKind::Doas,
|
|
|
|
|
SudoKind::Sudo,
|
|
|
|
|
SudoKind::Pkexec,
|
|
|
|
|
SudoKind::Run0,
|
|
|
|
|
SudoKind::Please,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
2025-07-16 00:41:24 +02:00
|
|
|
const DETECT_ORDER: [SudoKind; 2] = [SudoKind::Gsudo, SudoKind::WinSudo];
|
2025-03-06 01:51:40 +01:00
|
|
|
|
2025-06-26 22:35:46 +02:00
|
|
|
impl Sudo {
|
2022-11-25 17:19:32 -05:00
|
|
|
/// Get the `sudo` binary for this platform.
|
|
|
|
|
pub fn detect() -> Option<Self> {
|
2025-06-26 22:35:46 +02:00
|
|
|
for kind in DETECT_ORDER {
|
2025-09-26 14:58:35 +02:00
|
|
|
if let Some(sudo) = Self::new(kind) {
|
|
|
|
|
return Some(sudo);
|
2025-06-26 22:35:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
|
2023-03-13 19:23:37 +00:00
|
|
|
/// Create Sudo from SudoKind, if found in the system
|
|
|
|
|
pub fn new(kind: SudoKind) -> Option<Self> {
|
2025-09-26 14:58:35 +02:00
|
|
|
match kind {
|
|
|
|
|
SudoKind::Null => Some(Self {
|
|
|
|
|
path: None, // no actual binary for null sudo
|
|
|
|
|
kind,
|
|
|
|
|
}),
|
|
|
|
|
_ => kind.which().map(|path| Self { path: Some(path), kind }),
|
|
|
|
|
}
|
2023-03-13 19:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-25 15:59:02 +02:00
|
|
|
/// Gets the path to the `sudo` binary. Do not use this to execute `sudo` directly - either use
|
|
|
|
|
/// [`Sudo::elevate`], or if you need to specify arguments to `sudo`, use [`Sudo::elevate_opts`].
|
|
|
|
|
/// This way, sudo options can be specified generically and the actual arguments customized
|
|
|
|
|
/// depending on the sudo kind.
|
|
|
|
|
#[allow(unused)]
|
2025-09-26 14:58:35 +02:00
|
|
|
pub fn path(&self) -> Option<&Path> {
|
|
|
|
|
self.path.as_deref()
|
2025-06-25 15:59:02 +02:00
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
/// Elevate permissions with `sudo`.
|
|
|
|
|
///
|
|
|
|
|
/// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a
|
|
|
|
|
/// step.
|
|
|
|
|
///
|
|
|
|
|
/// See: https://github.com/topgrade-rs/topgrade/issues/205
|
|
|
|
|
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
|
2025-09-26 14:58:35 +02:00
|
|
|
// skip if using null sudo
|
|
|
|
|
if let SudoKind::Null = self.kind {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-24 14:15:43 -05:00
|
|
|
print_separator("Sudo");
|
2025-09-26 14:58:35 +02:00
|
|
|
|
|
|
|
|
// self.path is only None for null sudo, which we've handled above
|
|
|
|
|
let mut cmd = ctx.execute(self.path.as_deref().unwrap());
|
2022-11-25 17:19:32 -05:00
|
|
|
match self.kind {
|
|
|
|
|
SudoKind::Doas => {
|
|
|
|
|
// `doas` doesn't have anything like `sudo -v` to cache credentials,
|
|
|
|
|
// so we just execute a dummy `echo` command so we have something
|
|
|
|
|
// unobtrusive to run.
|
|
|
|
|
// See: https://man.openbsd.org/doas
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Sudo => {
|
2022-11-25 17:19:32 -05:00
|
|
|
// From `man sudo` on macOS:
|
|
|
|
|
// -v, --validate
|
|
|
|
|
// Update the user's cached credentials, authenticating the user
|
|
|
|
|
// if necessary. For the sudoers plugin, this extends the sudo
|
|
|
|
|
// timeout for another 5 minutes by default, but does not run a
|
|
|
|
|
// command. Not all security policies support cached credentials.
|
|
|
|
|
cmd.arg("-v");
|
|
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::WinSudo => {
|
2025-06-25 19:46:49 +02:00
|
|
|
// Windows `sudo` doesn't cache credentials, so we just execute a
|
|
|
|
|
// dummy command - the easiest on Windows is `rem` in cmd.
|
|
|
|
|
// See: https://learn.microsoft.com/en-us/windows/advanced-settings/sudo/
|
|
|
|
|
cmd.args(["cmd.exe", "/c", "rem"]);
|
|
|
|
|
}
|
2022-11-25 17:19:32 -05:00
|
|
|
SudoKind::Gsudo => {
|
2025-03-06 01:51:40 +01:00
|
|
|
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
2025-06-25 19:46:49 +02:00
|
|
|
// so we just execute a dummy command - the easiest on Windows is
|
|
|
|
|
// `rem` in cmd. `-d` tells it to run the command directly, without
|
|
|
|
|
// going through a shell (which could be powershell) first.
|
2022-11-25 17:19:32 -05:00
|
|
|
// See: https://gerardog.github.io/gsudo/docs/usage
|
2025-06-25 19:46:49 +02:00
|
|
|
cmd.args(["-d", "cmd.exe", "/c", "rem"]);
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
SudoKind::Pkexec => {
|
|
|
|
|
// I don't think this does anything; `pkexec` usually asks for
|
|
|
|
|
// authentication every time, although it can be configured
|
|
|
|
|
// differently.
|
|
|
|
|
//
|
|
|
|
|
// See the note for `doas` above.
|
|
|
|
|
//
|
|
|
|
|
// See: https://linux.die.net/man/1/pkexec
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
2025-03-12 02:07:37 +01:00
|
|
|
SudoKind::Run0 => {
|
|
|
|
|
// `run0` uses polkit for authentication
|
|
|
|
|
// and thus has the same issues as `pkexec`.
|
|
|
|
|
//
|
|
|
|
|
// See: https://www.freedesktop.org/software/systemd/man/devel/run0.html
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
2023-08-22 09:14:29 +08:00
|
|
|
SudoKind::Please => {
|
|
|
|
|
// From `man please`
|
|
|
|
|
// -w, --warm
|
|
|
|
|
// Warm the access token and exit.
|
|
|
|
|
cmd.arg("-w");
|
|
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Execute a command with `sudo`.
|
2025-06-25 19:46:49 +02:00
|
|
|
pub fn execute<S: AsRef<OsStr>>(&self, ctx: &ExecutionContext, command: S) -> Result<Executor> {
|
2025-07-16 00:11:20 +02:00
|
|
|
self.execute_opts(ctx, command, SudoExecuteOpts::new())
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Execute a command with `sudo`, with custom options.
|
|
|
|
|
pub fn execute_opts<S: AsRef<OsStr>>(
|
|
|
|
|
&self,
|
|
|
|
|
ctx: &ExecutionContext,
|
|
|
|
|
command: S,
|
|
|
|
|
opts: SudoExecuteOpts,
|
|
|
|
|
) -> Result<Executor> {
|
2025-09-26 14:58:35 +02:00
|
|
|
// null sudo is very different, do separately
|
|
|
|
|
if let SudoKind::Null = self.kind {
|
2025-09-28 14:53:42 +02:00
|
|
|
if opts.login_shell {
|
2025-09-26 14:58:35 +02:00
|
|
|
// TODO: emulate running in a login shell with su/runuser
|
|
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
2025-09-28 14:53:42 +02:00
|
|
|
option: "login_shell",
|
2025-09-26 14:58:35 +02:00
|
|
|
}
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
if opts.user.is_some() {
|
|
|
|
|
// TODO: emulate running as a different user with su/runuser
|
|
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
|
|
|
|
option: "user",
|
|
|
|
|
}
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: we ignore preserve_env and set_home, using
|
|
|
|
|
// no sudo effectively preserves these by default
|
|
|
|
|
|
|
|
|
|
// run command directly
|
|
|
|
|
return Ok(ctx.execute(command));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// self.path is only None for null sudo, which we've handled above
|
|
|
|
|
let mut cmd = ctx.execute(self.path.as_ref().unwrap());
|
2022-11-25 17:19:32 -05:00
|
|
|
|
2025-09-28 14:53:42 +02:00
|
|
|
if opts.login_shell {
|
2025-06-25 19:46:49 +02:00
|
|
|
match self.kind {
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Sudo => {
|
2025-06-25 19:46:49 +02:00
|
|
|
cmd.arg("-i");
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Gsudo => {
|
2025-09-28 14:53:42 +02:00
|
|
|
// By default, gsudo runs all commands inside a shell. If login_shell
|
|
|
|
|
// is *not* specified, we add `-d` to run outside of a shell - see below.
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
2025-06-25 19:46:49 +02:00
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
2025-09-28 14:53:42 +02:00
|
|
|
option: "login_shell",
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
|
|
|
|
.into());
|
|
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
|
|
|
|
} else if let SudoKind::Gsudo = self.kind {
|
|
|
|
|
// The `-d` (direct) flag disables shell detection, running the command directly
|
2025-09-28 14:53:42 +02:00
|
|
|
// rather than through the current shell.
|
2025-06-25 19:46:49 +02:00
|
|
|
// Additionally, if the current shell is pwsh >= 7.3.0, then not including this
|
|
|
|
|
// gives errors if the command to run has spaces in it: see
|
|
|
|
|
// https://github.com/gerardog/gsudo/issues/297
|
|
|
|
|
cmd.arg("-d");
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 00:11:20 +02:00
|
|
|
match opts.preserve_env {
|
|
|
|
|
SudoPreserveEnv::All => match self.kind {
|
|
|
|
|
SudoKind::Sudo => {
|
|
|
|
|
cmd.arg("-E");
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-07-16 00:11:20 +02:00
|
|
|
SudoKind::Gsudo => {
|
|
|
|
|
cmd.arg("--copyEV");
|
|
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
2025-07-16 00:11:20 +02:00
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
|
|
|
|
option: "preserve_env",
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-07-16 00:11:20 +02:00
|
|
|
.into());
|
|
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2025-07-16 00:11:20 +02:00
|
|
|
},
|
|
|
|
|
SudoPreserveEnv::Some(vars) => match self.kind {
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Sudo => {
|
2025-08-20 16:33:35 +01:00
|
|
|
cmd.arg(format!("--preserve-env={}", vars.join(",")));
|
2025-07-16 00:11:20 +02:00
|
|
|
}
|
|
|
|
|
SudoKind::Run0 => {
|
|
|
|
|
for env in vars {
|
|
|
|
|
cmd.arg(format!("--setenv={}", env));
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-07-16 00:11:20 +02:00
|
|
|
}
|
|
|
|
|
SudoKind::Please => {
|
|
|
|
|
cmd.arg("-a");
|
|
|
|
|
cmd.arg(vars.join(","));
|
|
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Gsudo | SudoKind::Pkexec => {
|
2025-07-16 00:11:20 +02:00
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
|
|
|
|
option: "preserve_env_list",
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-07-16 00:11:20 +02:00
|
|
|
.into());
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2025-07-16 00:11:20 +02:00
|
|
|
},
|
|
|
|
|
SudoPreserveEnv::None => {}
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.set_home {
|
|
|
|
|
match self.kind {
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Sudo => {
|
2025-06-25 19:46:49 +02:00
|
|
|
cmd.arg("-H");
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Doas
|
2025-07-16 00:41:24 +02:00
|
|
|
| SudoKind::WinSudo
|
2025-06-25 19:46:49 +02:00
|
|
|
| SudoKind::Gsudo
|
|
|
|
|
| SudoKind::Pkexec
|
|
|
|
|
| SudoKind::Run0
|
|
|
|
|
| SudoKind::Please => {
|
|
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
|
|
|
|
option: "set_home",
|
|
|
|
|
}
|
|
|
|
|
.into());
|
|
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(user) = opts.user {
|
|
|
|
|
match self.kind {
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::Sudo => {
|
2025-06-25 19:46:49 +02:00
|
|
|
cmd.args(["-u", user]);
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Doas | SudoKind::Gsudo | SudoKind::Run0 | SudoKind::Please => {
|
|
|
|
|
cmd.args(["-u", user]);
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Pkexec => {
|
|
|
|
|
cmd.args(["--user", user]);
|
|
|
|
|
}
|
2025-07-16 00:41:24 +02:00
|
|
|
SudoKind::WinSudo => {
|
2025-06-25 19:46:49 +02:00
|
|
|
// Windows sudo is the only one that doesn't have a `-u` flag
|
|
|
|
|
return Err(UnsupportedSudo {
|
|
|
|
|
sudo_kind: self.kind,
|
|
|
|
|
option: "user",
|
|
|
|
|
}
|
|
|
|
|
.into());
|
|
|
|
|
}
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Null => unreachable!(),
|
2025-06-25 19:46:49 +02:00
|
|
|
}
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd.arg(command);
|
|
|
|
|
|
2025-06-25 19:46:49 +02:00
|
|
|
Ok(cmd)
|
2022-11-24 14:15:43 -05:00
|
|
|
}
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
2022-11-24 14:15:43 -05:00
|
|
|
|
2025-09-26 14:58:35 +02:00
|
|
|
// On unix we use `SudoKind::Sudo`, and on windows `SudoKind::WinSudo`.
|
|
|
|
|
// We always define both though, so that we don't have to put
|
|
|
|
|
// #[cfg(...)] everywhere.
|
2025-07-16 00:41:24 +02:00
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Display, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
#[strum(serialize_all = "lowercase")]
|
|
|
|
|
pub enum SudoKind {
|
2025-09-26 14:58:35 +02:00
|
|
|
// On unix, "sudo" in the config file means Sudo
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
|
Sudo,
|
|
|
|
|
// and WinSudo is skipped, making it unused.
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
|
#[expect(unused, reason = "WinSudo is windows-only")]
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
WinSudo,
|
|
|
|
|
|
|
|
|
|
// On unix, Sudo is skipped and unused
|
|
|
|
|
#[cfg(windows)]
|
2025-07-16 00:41:24 +02:00
|
|
|
#[expect(unused, reason = "Sudo is unix-only")]
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
Sudo,
|
2025-09-26 14:58:35 +02:00
|
|
|
// and "sudo" in the config file means WinSudo.
|
|
|
|
|
#[cfg(windows)]
|
2025-07-16 00:41:24 +02:00
|
|
|
#[serde(rename = "sudo")]
|
|
|
|
|
WinSudo,
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
Doas,
|
|
|
|
|
Gsudo,
|
|
|
|
|
Pkexec,
|
2025-03-12 02:07:37 +01:00
|
|
|
Run0,
|
2023-08-22 09:14:29 +08:00
|
|
|
Please,
|
2025-09-26 14:58:35 +02:00
|
|
|
/// A "no-op" sudo, used when topgrade itself is running as root
|
|
|
|
|
Null,
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
2025-06-26 22:35:46 +02:00
|
|
|
|
|
|
|
|
impl SudoKind {
|
2025-09-26 14:58:35 +02:00
|
|
|
/// Get the name of the "sudo" binary.
|
|
|
|
|
///
|
|
|
|
|
/// For `SudoKind::WinSudo`, returns the full hardcoded path
|
|
|
|
|
/// instead to ensure we find Windows Sudo rather than gsudo
|
|
|
|
|
/// masquerading as sudo.
|
|
|
|
|
///
|
|
|
|
|
/// Only returns `None` for `SudoKind::Null`.
|
|
|
|
|
fn binary_name(self) -> Option<&'static str> {
|
2025-06-26 22:35:46 +02:00
|
|
|
match self {
|
2025-09-26 14:58:35 +02:00
|
|
|
SudoKind::Doas => Some("doas"),
|
|
|
|
|
SudoKind::Sudo => Some("sudo"),
|
|
|
|
|
SudoKind::WinSudo => Some(r"C:\Windows\System32\sudo.exe"),
|
|
|
|
|
SudoKind::Gsudo => Some("gsudo"),
|
|
|
|
|
SudoKind::Pkexec => Some("pkexec"),
|
|
|
|
|
SudoKind::Run0 => Some("run0"),
|
|
|
|
|
SudoKind::Please => Some("please"),
|
|
|
|
|
SudoKind::Null => None,
|
2025-06-26 22:35:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 14:58:35 +02:00
|
|
|
/// Find the full path to the "sudo" binary, if it exists on the system.
|
2025-06-26 22:35:46 +02:00
|
|
|
fn which(self) -> Option<PathBuf> {
|
2025-09-26 14:58:35 +02:00
|
|
|
match self.binary_name() {
|
|
|
|
|
Some(name) => which(name),
|
|
|
|
|
None => None,
|
|
|
|
|
}
|
2025-06-26 22:35:46 +02:00
|
|
|
}
|
|
|
|
|
}
|