1#[cfg(doc)]
6use std::path::Path;
7use std::{
8 fmt::Display,
9 path::PathBuf,
10 process::{Command, Output},
11};
12
13use log::debug;
14
15use crate::{Error, RootlessBackend, RootlessOptions, utils::get_command};
16
17const ARG_LIBRARY: &str = "--lib";
19const ARG_FAKED: &str = "--faked";
21const ARG_SAVE_FILE: &str = "-s";
23const ARG_LOAD_FILE: &str = "-i";
25const ARG_UNKNOWN_IS_REAL: &str = "--unknown-is-real";
27const ARG_FD: &str = "-b";
29const ARG_SEPARATOR: &str = "--";
31
32#[derive(Clone, Debug, Default, Eq, PartialEq)]
36pub struct FakerootOptions {
37 pub library: Option<PathBuf>,
41
42 pub faked: Option<PathBuf>,
46
47 pub save_file: Option<PathBuf>,
51
52 pub load_file: Option<PathBuf>,
56
57 pub unknown_is_real: bool,
61
62 pub fd: Option<usize>,
66}
67
68impl Display for FakerootOptions {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(f, "{}", self.to_vec().join(" "))
71 }
72}
73
74impl RootlessOptions for FakerootOptions {
75 fn to_vec(&self) -> Vec<String> {
83 let mut options = Vec::new();
84 if let Some(option) = self.library.as_ref() {
85 options.push(ARG_LIBRARY.to_string());
86 options.push(option.to_string_lossy().to_string());
87 }
88 if let Some(option) = self.faked.as_ref() {
89 options.push(ARG_FAKED.to_string());
90 options.push(option.to_string_lossy().to_string());
91 }
92 if let Some(option) = self.save_file.as_ref() {
93 options.push(ARG_SAVE_FILE.to_string());
94 options.push(option.to_string_lossy().to_string());
95 }
96 if let Some(option) = self.load_file.as_ref() {
97 options.push(ARG_LOAD_FILE.to_string());
98 options.push(option.to_string_lossy().to_string());
99 }
100 if self.unknown_is_real {
101 options.push(ARG_UNKNOWN_IS_REAL.to_string());
102 }
103 if let Some(option) = self.fd {
104 options.push(ARG_FD.to_string());
105 options.push(option.to_string());
106 }
107 options.push(ARG_SEPARATOR.to_string());
108
109 options
110 }
111}
112
113#[derive(Debug)]
117pub struct FakerootBackend(FakerootOptions);
118
119impl RootlessBackend<FakerootOptions> for FakerootBackend {
120 type Err = Error;
121
122 fn new(options: FakerootOptions) -> Self {
124 debug!("Creating a new fakeroot backend with options: \"{options}\"");
125 Self(options)
126 }
127
128 fn options(&self) -> &FakerootOptions {
130 &self.0
131 }
132
133 fn run(&self, cmd: &[&str]) -> Result<Output, Error> {
144 let command_name = get_command("fakeroot")?;
145 let mut command = Command::new(command_name);
146
147 if let Some(option) = self.0.library.as_ref() {
149 command.arg(ARG_LIBRARY);
150 command.arg(option);
151 }
152 if let Some(option) = self.0.faked.as_ref() {
153 command.arg(ARG_FAKED);
154 command.arg(option);
155 }
156 if let Some(option) = self.0.save_file.as_ref() {
157 command.arg(ARG_SAVE_FILE);
158 command.arg(option);
159 }
160 if let Some(option) = self.0.load_file.as_ref() {
161 command.arg(ARG_LOAD_FILE);
162 command.arg(option);
163 }
164 if self.0.unknown_is_real {
165 command.arg(ARG_UNKNOWN_IS_REAL);
166 }
167 if let Some(option) = self.0.fd {
168 command.arg(ARG_FD);
169 command.arg(option.to_string());
170 }
171 command.arg(ARG_SEPARATOR);
172
173 for command_component in cmd.iter() {
175 command.arg(command_component);
176 }
177
178 debug!("Run rootless command: {command:?}");
179
180 command
181 .output()
182 .map_err(|source| crate::Error::CommandExec {
183 command: format!("{command:?}"),
184 source,
185 })
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use rstest::rstest;
192
193 use super::*;
194
195 #[rstest]
197 #[case::all_options(
198 FakerootOptions{
199 library: Some(PathBuf::from("custom-lib")),
200 faked: Some(PathBuf::from("custom-faked")),
201 save_file: Some(PathBuf::from("/custom/save/file")),
202 load_file: Some(PathBuf::from("/custom/load/file")),
203 unknown_is_real: true,
204 fd: Some(1024),
205 },
206 vec![
207 ARG_LIBRARY.to_string(),
208 "custom-lib".to_string(),
209 ARG_FAKED.to_string(),
210 "custom-faked".to_string(),
211 ARG_SAVE_FILE.to_string(),
212 "/custom/save/file".to_string(),
213 ARG_LOAD_FILE.to_string(),
214 "/custom/load/file".to_string(),
215 ARG_UNKNOWN_IS_REAL.to_string(),
216 ARG_FD.to_string(),
217 "1024".to_string(),
218 ARG_SEPARATOR.to_string(),
219 ]
220 )]
221 #[case::default_options(FakerootOptions::default(), vec![ARG_SEPARATOR.to_string()])]
222 fn fakeroot_options_to_vec(#[case] options: FakerootOptions, #[case] to_vec: Vec<String>) {
223 assert_eq!(options.to_vec(), to_vec);
224 }
225
226 #[test]
228 fn fakeroot_backend_options() {
229 let backend = FakerootBackend::new(FakerootOptions::default());
230 assert_eq!(backend.options(), &FakerootOptions::default());
231 }
232}