Skip to main content

asar_rust/
crawlfs.rs

1use crate::disk::AsarError;
2use glob::glob;
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7/// Metadata for a file discovered during filesystem crawling.
8#[derive(Debug, Clone)]
9pub struct FileMetadata {
10    pub file_type: FileType,
11    pub mode: u32,
12    pub size: u64,
13}
14
15/// The type of a filesystem entry discovered during crawling.
16#[derive(Debug, Clone, PartialEq)]
17pub enum FileType {
18    File,
19    Directory,
20    Link,
21}
22
23/// Collect every path whose `FileType` is `Link` from the metadata map.
24fn 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    // Exclude files whose ancestor directory is a symlink.
77    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}