1use crate::disk::AsarError;
2use glob::glob;
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
9pub struct FileMetadata {
10 pub file_type: FileType,
11 pub mode: u32,
12 pub size: u64,
13}
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum FileType {
18 File,
19 Directory,
20 Link,
21}
22
23fn links_in(metadata: &HashMap<PathBuf, FileMetadata>) -> Option<Vec<PathBuf>> {
25 let links: Vec<_> = metadata
26 .iter()
27 .filter_map(|(p, m)| (m.file_type == FileType::Link).then_some(p.clone()))
28 .collect();
29 if links.is_empty() {
30 None
31 } else {
32 Some(links)
33 }
34}
35
36pub fn crawl(pattern: &str) -> Result<(Vec<PathBuf>, HashMap<PathBuf, FileMetadata>), AsarError> {
37 let mut metadata: HashMap<PathBuf, FileMetadata> = HashMap::new();
38 let mut paths = Vec::new();
39
40 let entries = glob(pattern).map_err(|e| AsarError::Other(e.to_string()))?;
41
42 for entry in entries {
43 let path = entry.map_err(|e| AsarError::Other(e.to_string()))?;
44 if let Some(ft) = determine_file_type(&path) {
45 let meta = fs::symlink_metadata(&path).map_err(|e| AsarError::Other(e.to_string()))?;
46 let mode = {
47 #[cfg(unix)]
48 {
49 if let Ok(regular_meta) = fs::metadata(&path) {
50 use std::os::unix::fs::PermissionsExt;
51 regular_meta.permissions().mode()
52 } else {
53 0o644
54 }
55 }
56 #[cfg(not(unix))]
57 {
58 0o644
59 }
60 };
61
62 metadata.insert(
63 path.clone(),
64 FileMetadata {
65 file_type: ft,
66 mode,
67 size: meta.len(),
68 },
69 );
70 paths.push(path);
71 }
72 }
73
74 paths.sort_unstable();
75
76 let paths = if let Some(link_dirs) = links_in(&metadata) {
78 let link_set: std::collections::HashSet<_> = link_dirs.iter().cloned().collect();
79 paths
80 .into_iter()
81 .filter(|filename| {
82 if link_set.contains(filename) {
83 return true;
84 }
85 let mut ancestor = filename.as_path();
86 while let Some(parent) = ancestor.parent() {
87 if parent.as_os_str().is_empty() {
88 break;
89 }
90 if link_set.contains(parent) {
91 return false;
92 }
93 ancestor = parent;
94 }
95 true
96 })
97 .collect()
98 } else {
99 paths
100 };
101
102 Ok((paths, metadata))
103}
104
105fn determine_file_type(path: &Path) -> Option<FileType> {
106 let meta = fs::symlink_metadata(path).ok()?;
107 let file_type = meta.file_type();
108 if file_type.is_file() {
109 Some(FileType::File)
110 } else if file_type.is_dir() {
111 Some(FileType::Directory)
112 } else if file_type.is_symlink() {
113 Some(FileType::Link)
114 } else {
115 None
116 }
117}