#[cfg(doc)]
use std::path::Path;
use std::{
fmt::Display,
path::PathBuf,
process::{Command, Output},
};
use log::debug;
use crate::{Error, RootlessBackend, RootlessOptions, utils::get_command};
const ARG_LIBRARY: &str = "--lib";
const ARG_FAKED: &str = "--faked";
const ARG_SAVE_FILE: &str = "-s";
const ARG_LOAD_FILE: &str = "-i";
const ARG_UNKNOWN_IS_REAL: &str = "--unknown-is-real";
const ARG_FD: &str = "-b";
const ARG_SEPARATOR: &str = "--";
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FakerootOptions {
pub library: Option<PathBuf>,
pub faked: Option<PathBuf>,
pub save_file: Option<PathBuf>,
pub load_file: Option<PathBuf>,
pub unknown_is_real: bool,
pub fd: Option<usize>,
}
impl Display for FakerootOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_vec().join(" "))
}
}
impl RootlessOptions for FakerootOptions {
fn to_vec(&self) -> Vec<String> {
let mut options = Vec::new();
if let Some(option) = self.library.as_ref() {
options.push(ARG_LIBRARY.to_string());
options.push(option.to_string_lossy().to_string());
}
if let Some(option) = self.faked.as_ref() {
options.push(ARG_FAKED.to_string());
options.push(option.to_string_lossy().to_string());
}
if let Some(option) = self.save_file.as_ref() {
options.push(ARG_SAVE_FILE.to_string());
options.push(option.to_string_lossy().to_string());
}
if let Some(option) = self.load_file.as_ref() {
options.push(ARG_LOAD_FILE.to_string());
options.push(option.to_string_lossy().to_string());
}
if self.unknown_is_real {
options.push(ARG_UNKNOWN_IS_REAL.to_string());
}
if let Some(option) = self.fd {
options.push(ARG_FD.to_string());
options.push(option.to_string());
}
options.push(ARG_SEPARATOR.to_string());
options
}
}
#[derive(Debug)]
pub struct FakerootBackend(FakerootOptions);
impl RootlessBackend<FakerootOptions> for FakerootBackend {
type Err = Error;
fn new(options: FakerootOptions) -> Self {
debug!("Creating a new fakeroot backend with options: \"{options}\"");
Self(options)
}
fn options(&self) -> &FakerootOptions {
&self.0
}
fn run(&self, cmd: &[&str]) -> Result<Output, Error> {
let command_name = get_command("fakeroot")?;
let mut command = Command::new(command_name);
if let Some(option) = self.0.library.as_ref() {
command.arg(ARG_LIBRARY);
command.arg(option);
}
if let Some(option) = self.0.faked.as_ref() {
command.arg(ARG_FAKED);
command.arg(option);
}
if let Some(option) = self.0.save_file.as_ref() {
command.arg(ARG_SAVE_FILE);
command.arg(option);
}
if let Some(option) = self.0.load_file.as_ref() {
command.arg(ARG_LOAD_FILE);
command.arg(option);
}
if self.0.unknown_is_real {
command.arg(ARG_UNKNOWN_IS_REAL);
}
if let Some(option) = self.0.fd {
command.arg(ARG_FD);
command.arg(option.to_string());
}
command.arg(ARG_SEPARATOR);
for command_component in cmd.iter() {
command.arg(command_component);
}
debug!("Run rootless command: {command:?}");
command
.output()
.map_err(|source| crate::Error::CommandExec {
command: format!("{command:?}"),
source,
})
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::all_options(
FakerootOptions{
library: Some(PathBuf::from("custom-lib")),
faked: Some(PathBuf::from("custom-faked")),
save_file: Some(PathBuf::from("/custom/save/file")),
load_file: Some(PathBuf::from("/custom/load/file")),
unknown_is_real: true,
fd: Some(1024),
},
vec![
ARG_LIBRARY.to_string(),
"custom-lib".to_string(),
ARG_FAKED.to_string(),
"custom-faked".to_string(),
ARG_SAVE_FILE.to_string(),
"/custom/save/file".to_string(),
ARG_LOAD_FILE.to_string(),
"/custom/load/file".to_string(),
ARG_UNKNOWN_IS_REAL.to_string(),
ARG_FD.to_string(),
"1024".to_string(),
ARG_SEPARATOR.to_string(),
]
)]
#[case::default_options(FakerootOptions::default(), vec![ARG_SEPARATOR.to_string()])]
fn fakeroot_options_to_vec(#[case] options: FakerootOptions, #[case] to_vec: Vec<String>) {
assert_eq!(options.to_vec(), to_vec);
}
#[test]
fn fakeroot_backend_options() {
let backend = FakerootBackend::new(FakerootOptions::default());
assert_eq!(backend.options(), &FakerootOptions::default());
}
}