1use crate::config::Config;
2use crate::error::{MyError, MyResult};
3use crate::fs::entry::{Entry, EntryResult, FileEntry};
4use crate::fs::file::Signature;
5use crate::zip::clone::CloneEntry;
6use crate::zip::manager::PasswordManager;
7use crate::zip::wrapper::ZipKind;
8use std::cell::RefCell;
9use std::collections::HashMap;
10#[cfg(unix)]
11use std::collections::HashSet;
12#[cfg(unix)]
13use std::ffi::OsStr;
14use std::path::{Path, PathBuf};
15use std::rc::Rc;
16#[cfg(unix)]
17use uzers::{gid_t, uid_t, Group, User};
18use walkdir::{DirEntry, WalkDir};
19
20pub const OWNER_MASK: u32 = 0o100;
21pub const GROUP_MASK: u32 = 0o010;
22pub const OTHER_MASK: u32 = 0o001;
23pub const EXEC_MASK: u32 = 0o111;
24
25pub trait System {
26 fn walk_entries<F: Fn(EntryResult)>(
27 &self,
28 abs_root: &Path,
29 rel_root: &Path,
30 function: &F,
31 ) -> MyResult<()>;
32
33 fn get_entry(&self, path: &Path) -> MyResult<Rc<Box<dyn Entry>>>;
34
35 fn read_sig(&self, entry: &dyn Entry) -> Option<Signature>;
36
37 #[cfg(windows)]
38 fn read_version(&self, entry: &dyn Entry) -> Option<String>;
39
40 fn read_link(&self, entry: &dyn Entry) -> MyResult<Option<PathBuf>>;
41
42 #[cfg(unix)]
43 fn get_mask(&self, uid: uid_t, gid: gid_t) -> u32;
44
45 #[cfg(unix)]
46 fn find_user(&self, uid: uid_t) -> Option<Rc<String>>;
47
48 #[cfg(unix)]
49 fn find_group(&self, gid: gid_t) -> Option<Rc<String>>;
50}
51
52pub struct FileSystem<'a> {
53 config: &'a Config,
54 zip_entries: RefCell<HashMap<PathBuf, Rc<Box<dyn Entry>>>>,
55 zip_manager: RefCell<PasswordManager>,
56 #[cfg(unix)]
57 my_uid: uid_t,
58 #[cfg(unix)]
59 my_gids: HashSet<gid_t>,
60 #[cfg(unix)]
61 user_names: RefCell<HashMap<uid_t, Option<Rc<String>>>>,
62 #[cfg(unix)]
63 group_names: RefCell<HashMap<gid_t, Option<Rc<String>>>>,
64}
65
66impl<'a> FileSystem<'a> {
67 #[cfg(unix)]
68 pub fn new(config: &'a Config) -> Self {
69 let zip_entries = RefCell::new(HashMap::new());
70 let zip_manager = RefCell::new(PasswordManager::new(&config.zip_password));
71 let my_uid = uzers::get_effective_uid();
72 let my_gids = Self::get_gids(my_uid);
73 let user_names = RefCell::new(HashMap::new());
74 let group_names = RefCell::new(HashMap::new());
75 Self { config, zip_entries, zip_manager, my_uid, my_gids, user_names, group_names }
76 }
77
78 #[cfg(unix)]
79 fn get_gids(uid: uid_t) -> HashSet<gid_t> {
80 if let Some(groups) = uzers::get_user_by_uid(uid).as_ref().and_then(User::groups) {
81 groups.iter().map(Group::gid).collect()
82 } else {
83 HashSet::new()
84 }
85 }
86
87 #[cfg(not(unix))]
88 pub fn new(config: &'a Config) -> Self {
89 let zip_entries = RefCell::new(HashMap::new());
90 let zip_manager = RefCell::new(PasswordManager::new(&config.zip_password));
91 Self { config, zip_entries, zip_manager }
92 }
93
94 fn choose_filter(&self) -> fn(&DirEntry) -> bool {
95 if self.config.all_recurse {
96 |_| true
97 } else if self.config.all_files {
98 Self::filter_hidden_parent
99 } else {
100 Self::filter_hidden_entry
101 }
102 }
103
104 fn filter_hidden_parent(entry: &DirEntry) -> bool {
105 if entry.depth() > 1 {
106 if let Some(parent) = entry.path().parent() {
107 if let Some(name) = parent.file_name() {
108 return Self::filter_hidden_name(name.to_str());
109 }
110 }
111 }
112 true
113 }
114
115 fn filter_hidden_entry(entry: &DirEntry) -> bool {
116 Self::filter_hidden_name(entry.file_name().to_str())
117 }
118
119 pub fn filter_hidden_name(name: Option<&str>) -> bool {
120 if let Some(name) = name {
121 if name.starts_with(".") {
122 return false;
123 }
124 if name.starts_with("__") && name.ends_with("__") {
125 return false;
126 }
127 }
128 true
129 }
130
131 fn walk_entry<F: Fn(EntryResult)>(&self, entry: DirEntry, function: &F) -> MyResult<()> {
132 let zip_expand = self.config.zip_expand && entry.file_type().is_file();
133 if let Some(zip_kind) = ZipKind::from_path(entry.path(), zip_expand) {
134 let mut zip_manager = self.zip_manager.borrow_mut();
135 zip_kind.walk_entries(self.config, &entry, &mut zip_manager, &|result| {
136 match result {
137 Ok(entry) => {
138 self.clone_entry(entry);
139 function(Ok(entry));
140 }
141 Err(error) => {
142 function(Err(error));
143 }
144 }
145 })?;
146 let entry = FileEntry::from_entry(entry, true);
147 self.clone_entry(entry.as_ref());
148 function(Ok(entry.as_ref()));
149 } else {
150 let entry = FileEntry::from_entry(entry, false);
151 function(Ok(entry.as_ref()));
152 }
153 Ok(())
154 }
155
156 fn clone_entry(&self, entry: &dyn Entry) {
157 let path = PathBuf::from(entry.file_path());
158 let entry = CloneEntry::from_entry(entry);
159 self.zip_entries.borrow_mut().insert(path, entry);
160 }
161
162 #[cfg(unix)]
163 fn get_uid_name(uid: &uid_t) -> Option<Rc<String>> {
164 uzers::get_user_by_uid(*uid)
165 .as_ref()
166 .map(User::name)
167 .and_then(OsStr::to_str)
168 .map(str::to_string)
169 .map(Rc::new)
170 }
171
172 #[cfg(unix)]
173 fn get_gid_name(gid: &gid_t) -> Option<Rc<String>> {
174 uzers::get_group_by_gid(*gid)
175 .as_ref()
176 .map(Group::name)
177 .and_then(OsStr::to_str)
178 .map(str::to_string)
179 .map(Rc::new)
180 }
181}
182
183impl<'a> System for FileSystem<'a> {
184 fn walk_entries<F: Fn(EntryResult)>(
185 &self,
186 abs_root: &Path,
187 _rel_root: &Path,
188 function: &F,
189 ) -> MyResult<()> {
190 let mut walker = WalkDir::new(abs_root);
191 if let Some(depth) = self.config.max_depth {
192 walker = walker.max_depth(depth);
193 }
194 let filter = self.choose_filter();
195 for entry in walker.into_iter().filter_entry(filter) {
196 match entry {
197 Ok(entry) => self.walk_entry(entry, function)?,
198 Err(error) => function(Err(MyError::from(error))),
199 }
200 }
201 Ok(())
202 }
203
204 fn get_entry(&self, path: &Path) -> MyResult<Rc<Box<dyn Entry>>> {
205 if let Some(entry) = self.zip_entries.borrow().get(path) {
206 Ok(Rc::clone(entry))
207 } else {
208 let entry = FileEntry::from_path(path)?;
209 Ok(Rc::new(entry))
210 }
211 }
212
213 fn read_sig(&self, entry: &dyn Entry) -> Option<Signature> {
214 entry.read_sig()
215 }
216
217 #[cfg(windows)]
218 fn read_version(&self, entry: &dyn Entry) -> Option<String> {
219 entry.read_version()
220 }
221
222 fn read_link(&self, entry: &dyn Entry) -> MyResult<Option<PathBuf>> {
223 entry.read_link()
224 }
225
226 #[cfg(unix)]
227 fn get_mask(&self, uid: uid_t, gid: gid_t) -> u32 {
228 if uid == self.my_uid {
229 OWNER_MASK
230 } else if self.my_gids.contains(&gid) {
231 GROUP_MASK
232 } else {
233 OTHER_MASK
234 }
235 }
236
237 #[cfg(unix)]
238 fn find_user(&self, uid: uid_t) -> Option<Rc<String>> {
239 self.user_names.borrow_mut()
240 .entry(uid)
241 .or_insert_with_key(Self::get_uid_name)
242 .as_ref()
243 .map(Rc::clone)
244 }
245
246 #[cfg(unix)]
247 fn find_group(&self, gid: gid_t) -> Option<Rc<String>> {
248 self.group_names.borrow_mut()
249 .entry(gid)
250 .or_insert_with_key(Self::get_gid_name)
251 .as_ref()
252 .map(Rc::clone)
253 }
254}
255
256#[cfg(test)]
257pub mod tests {
258 use crate::config::Config;
259 use crate::error::{MyError, MyResult};
260 use crate::fs::entry::{Entry, EntryResult};
261 use crate::fs::file::Signature;
262 use crate::fs::metadata::Metadata;
263 #[cfg(unix)]
264 use crate::fs::system::EXEC_MASK;
265 use crate::fs::system::{FileEntry, FileSystem, System};
266 use pretty_assertions::assert_eq;
267 use std::collections::BTreeMap;
268 use std::path::{Path, PathBuf};
269 use std::rc::Rc;
270 #[cfg(unix)]
271 use uzers::{gid_t, uid_t};
272
273 #[test]
274 fn test_hides_hidden_files() {
275 assert_eq!(true, FileSystem::filter_hidden_name(None));
276 assert_eq!(true, FileSystem::filter_hidden_name(Some("")));
277 assert_eq!(true, FileSystem::filter_hidden_name(Some("visible")));
278 assert_eq!(true, FileSystem::filter_hidden_name(Some("visible__")));
279 assert_eq!(true, FileSystem::filter_hidden_name(Some("_visible_")));
280 assert_eq!(true, FileSystem::filter_hidden_name(Some("__visible")));
281 assert_eq!(false, FileSystem::filter_hidden_name(Some(".hidden")));
282 assert_eq!(false, FileSystem::filter_hidden_name(Some("__hidden__")));
283 }
284
285 pub struct MockSystem<'a> {
286 config: &'a Config,
287 current: PathBuf,
288 entries: BTreeMap<PathBuf, FileEntry>,
289 links: BTreeMap<PathBuf, PathBuf>,
290 #[cfg(unix)]
291 user_names: BTreeMap<uid_t, String>,
292 #[cfg(unix)]
293 group_names: BTreeMap<gid_t, String>,
294 }
295
296 impl<'a> MockSystem<'a> {
297 pub fn new(
298 config: &'a Config,
299 current: PathBuf,
300 #[cfg(unix)]
301 user_names: BTreeMap<uid_t, String>,
302 #[cfg(unix)]
303 group_names: BTreeMap<uid_t, String>,
304 ) -> Self {
305 let entries = BTreeMap::new();
306 let links = BTreeMap::new();
307 Self {
308 config,
309 current,
310 entries,
311 links,
312 #[cfg(unix)]
313 user_names,
314 #[cfg(unix)]
315 group_names,
316 }
317 }
318
319 pub fn insert_entry(
320 &mut self,
321 file_depth: usize,
322 file_type: char,
323 file_mode: u32,
324 owner_uid: u32, owner_gid: u32, file_size: u64,
327 file_year: i32,
328 file_month: u32,
329 file_day: u32,
330 file_path: &str,
331 link_path: Option<&str>,
332 ) {
333 let file_path = self.current.join(file_path);
334 let metadata = Metadata::from_fields(
335 file_type,
336 file_mode,
337 owner_uid,
338 owner_gid,
339 file_size,
340 file_year,
341 file_month,
342 file_day,
343 );
344 let entry = FileEntry::from_fields(
345 file_path.clone(),
346 file_depth,
347 file_type,
348 metadata.clone(),
349 );
350 self.entries.insert(file_path.clone(), entry);
351 if let Some(link_path) = link_path {
352 let link_path = PathBuf::from(link_path);
353 self.links.insert(file_path, link_path);
354 }
355 }
356
357 fn filter_depth(&self, entry: &FileEntry) -> bool {
358 match self.config.max_depth {
359 Some(depth) => entry.file_depth() <= depth,
360 None => true,
361 }
362 }
363 }
364
365 impl<'a> System for MockSystem<'a> {
366 fn walk_entries<F: Fn(EntryResult)>(
367 &self,
368 abs_root: &Path,
369 rel_root: &Path,
370 function: &F,
371 ) -> MyResult<()> {
372 let rel_depth = rel_root.components().count();
373 for (_, entry) in self.entries.iter() {
374 if let Some(entry) = entry.subtract_depth(rel_depth) {
375 if self.filter_depth(&entry) && entry.file_path().starts_with(abs_root) {
376 function(Ok(&entry));
377 }
378 }
379 }
380 Ok(())
381 }
382
383 fn get_entry(&self, path: &Path) -> MyResult<Rc<Box<dyn Entry>>> {
384 let entry = self.entries.get(path)
385 .map(|entry| entry.clone())
386 .ok_or(MyError::Text(format!("Entry not found: {}", path.display())))?;
387 Ok(Rc::new(Box::new(entry)))
388 }
389
390 fn read_sig(&self, _entry: &dyn Entry) -> Option<Signature> {
391 None
392 }
393
394 #[cfg(windows)]
395 fn read_version(&self, _entry: &dyn Entry) -> Option<String> {
396 None
397 }
398
399 fn read_link(&self, entry: &dyn Entry) -> MyResult<Option<PathBuf>> {
400 let path = entry.file_path();
401 match self.links.get(path) {
402 Some(link) => Ok(Some(link.clone())),
403 None => Err(MyError::Text(format!("Link not found: {}", path.display()))),
404 }
405 }
406
407 #[cfg(unix)]
408 fn get_mask(&self, _uid: uid_t, _gid: gid_t) -> u32 {
409 EXEC_MASK
410 }
411
412 #[cfg(unix)]
413 fn find_user(&self, uid: uid_t) -> Option<Rc<String>> {
414 self.user_names.get(&uid).map(String::clone).map(Rc::new)
415 }
416
417 #[cfg(unix)]
418 fn find_group(&self, gid: gid_t) -> Option<Rc<String>> {
419 self.group_names.get(&gid).map(String::clone).map(Rc::new)
420 }
421 }
422}