stor_age/analysis/
universal.rs1use std::collections::HashMap;
2use std::fs::{self, ReadDir};
3use std::io::ErrorKind;
4use std::path::Path;
5use std::time::{Duration, SystemTime};
6
7#[cfg(target_family = "unix")]
8use std::os::unix::fs::MetadataExt;
9
10use anyhow::Result;
11
12use crate::Data;
13
14pub fn run(
21 dir: &str,
22 ages_in_days: &[u64],
23 #[allow(unused_variables)] one_file_system: bool,
25) -> Result<Data> {
26 let thresholds = thresholds(ages_in_days);
27
28 #[cfg(target_family = "unix")]
29 let dev = if one_file_system {
30 Some(fs::metadata(dir)?.dev())
31 } else {
32 None
33 };
34
35 #[cfg(not(target_family = "unix"))]
36 let dev = None;
37
38 walk(Path::new(dir), &thresholds, ages_in_days, dev)
39}
40
41fn thresholds(ages_in_days: &[u64]) -> HashMap<u64, SystemTime> {
42 let now = SystemTime::now();
43
44 let mut thresholds = HashMap::with_capacity(ages_in_days.len());
45
46 for age in ages_in_days {
47 let duration = Duration::from_secs(60 * 60 * 24 * age);
48 let threshold = now - duration;
49
50 thresholds.insert(*age, threshold);
51 }
52
53 thresholds
54}
55
56fn walk(
57 dir: &Path,
58 thresholds: &HashMap<u64, SystemTime>,
59 ages_in_days: &[u64],
60 dev: Option<u64>,
61) -> Result<Data> {
62 let data = Data::default().with_ages(ages_in_days);
63
64 match fs::read_dir(dir) {
65 Ok(entries) => iterate(entries, data, thresholds, ages_in_days, dev),
66
67 Err(error) if error.kind() == ErrorKind::PermissionDenied => {
68 log::info!("skipping permission denied: {dir:?}");
69 Ok(data)
70 }
71
72 Err(error) => Err(error.into()),
73 }
74}
75
76fn iterate(
77 entries: ReadDir,
78 mut data: Data,
79 thresholds: &HashMap<u64, SystemTime>,
80 ages_in_days: &[u64],
81 dev: Option<u64>,
82) -> Result<Data> {
83 for entry in entries {
84 let entry = entry?;
85 let path = entry.path();
86 let meta = entry.metadata()?;
87 let file_type = meta.file_type();
88
89 if dev_check(dev, &meta) {
90 log::debug!("skipping different file system: {path:?}");
91 } else if file_type.is_file() {
92 log::debug!("visiting: {path:?}");
93
94 let bytes = meta.len();
95
96 let mut current =
97 Data::default().with_total_bytes(bytes).with_total_files(1);
98
99 for (age, threshold) in thresholds {
100 let (a_b, a_f) = if meta.accessed()? > *threshold {
101 (bytes, 1)
102 } else {
103 (0, 0)
104 };
105
106 let (m_b, m_f) = if meta.modified()? > *threshold {
107 (bytes, 1)
108 } else {
109 (0, 0)
110 };
111
112 current.insert(*age, a_b, m_b, a_f, m_f);
113 }
114
115 data += current;
116 } else if file_type.is_dir() {
117 log::debug!("descending: {path:?}");
118
119 data += walk(&path, thresholds, ages_in_days, dev)?;
120 } else {
121 log::debug!(
122 "skipping neither regular file nor directory: {path:?}"
123 );
124 }
125 }
126
127 Ok(data)
128}
129
130#[cfg(target_family = "unix")]
131fn dev_check(dev: Option<u64>, meta: &fs::Metadata) -> bool {
132 dev.map_or(false, |dev| dev != meta.dev())
133}
134
135#[cfg(not(target_family = "unix"))]
136const fn dev_check(_dev: Option<u64>, _meta: &fs::Metadata) -> bool {
137 false
138}