Skip to content

Commit 160e131

Browse files
committed
feat: report the number of unrecorded links
1 parent 32fe9b6 commit 160e131

1 file changed

Lines changed: 87 additions & 32 deletions

File tree

src/hardlink/hardlink_list/summary.rs

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use super::{iter::Item as IterItem, reflection::ReflectionEntry, HardlinkList, Reflection};
22
use crate::size;
33
use derive_more::{Add, AddAssign, Sum};
4-
use std::fmt::{self, Display};
4+
use std::{
5+
cmp::Ordering,
6+
fmt::{self, Display},
7+
};
58

69
#[cfg(feature = "json")]
710
use serde::{Deserialize, Serialize};
@@ -11,23 +14,34 @@ use serde::{Deserialize, Serialize};
1114
#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
1215
#[non_exhaustive]
1316
pub struct Summary<Size> {
14-
/// Number of unique files, each with more than 1 links.
17+
/// Number of shared inodes, each with more than 1 links (i.e. `nlink > 1`).
1518
pub inodes: usize,
16-
/// Total number of links of all the unique files as counted by [`Summary::inodes`].
17-
pub links: usize,
18-
/// Total size of all the unique files.
19-
pub size: Size,
20-
}
2119

22-
impl<Size> Summary<Size> {
23-
/// Create a new summary.
24-
pub fn new(inodes: usize, links: usize, size: Size) -> Self {
25-
Summary {
26-
inodes,
27-
links,
28-
size,
29-
}
30-
}
20+
/// Number of [shared inodes](Self::inodes) that don't have links outside the measured tree.
21+
///
22+
/// This number is expected to be less than or equal to [`Self::inodes`].
23+
pub owned_inodes: usize,
24+
25+
/// Totality of the numbers of links of all [shared inodes](Self::inodes).
26+
pub all_links: u64,
27+
28+
/// Total number of links of [shared inodes](Self::inodes) that were detected within the measured tree.
29+
///
30+
/// This number is expected to be less than or equal to [`Self::all_links`].
31+
pub detected_links: usize,
32+
33+
/// Total number of links of [shared inodes](Self::inodes) that don't have links outside the measured tree.
34+
///
35+
/// This number is expected to be less than or equal to [`Self::all_links`].
36+
pub owned_links: usize,
37+
38+
/// Totality of the sizes of all [shared inodes](Self::inodes).
39+
pub shared_size: Size,
40+
41+
/// Totality of the sizes of all [shared inodes](Self::inodes) that don't have links outside the measured tree.
42+
///
43+
/// This number is expected to be less than or equal to [`Self::all_links`].
44+
pub owned_shared_size: Size,
3145
}
3246

3347
/// Ability to summarize into a [`Summary`].
@@ -39,8 +53,10 @@ pub trait SummarizeHardlinks<Size>: Sized {
3953
/// Summary of a single unique file.
4054
#[derive(Debug, Clone, Copy)]
4155
pub struct SingleInodeSummary<Size> {
56+
/// Total number of all links to the file.
57+
links: u64,
4258
/// Number of detected links to the file.
43-
links: usize,
59+
paths: usize,
4460
/// Size of the file.
4561
size: Size,
4662
}
@@ -54,13 +70,22 @@ where
5470
fn summarize_hardlinks(self) -> Summary<Size> {
5571
let mut summary = Summary::default();
5672
for item in self {
57-
let SingleInodeSummary { links, size } = item.into();
58-
if links <= 1 {
59-
continue;
60-
}
73+
let SingleInodeSummary { links, paths, size } = item.into();
6174
summary.inodes += 1;
62-
summary.links += links;
63-
summary.size += size;
75+
summary.all_links += links;
76+
summary.detected_links += paths;
77+
summary.shared_size += size;
78+
match links.cmp(&(paths as u64)) {
79+
Ordering::Greater => {}
80+
Ordering::Equal => {
81+
summary.owned_inodes += 1;
82+
summary.owned_links += paths; // `links` and `paths` are both fine, but `paths` doesn't require type cast
83+
summary.owned_shared_size += size;
84+
}
85+
Ordering::Less => {
86+
panic!("Impossible! Total of nlink ({links}) is less than detected paths ({paths}). Something must have went wrong!");
87+
}
88+
}
6489
}
6590
summary
6691
}
@@ -116,14 +141,42 @@ impl<Size: size::Size> Display for SummaryDisplay<'_, Size> {
116141
let SummaryDisplay { format, summary } = self;
117142
let Summary {
118143
inodes,
119-
links,
120-
size,
144+
owned_inodes,
145+
all_links,
146+
detected_links,
147+
owned_links,
148+
shared_size,
149+
owned_shared_size,
121150
} = summary;
122-
let size = size.display(*format);
123-
write!(
124-
f,
125-
"Detected {links} hardlinks for {inodes} unique files (total: {size})"
126-
)
151+
152+
let shared_size = shared_size.display(*format);
153+
let owned_shared_size = owned_shared_size.display(*format);
154+
155+
macro_rules! ln {
156+
($($args:tt)*) => {
157+
writeln!(f, $($args)*)
158+
};
159+
}
160+
161+
write!(f, "Hardlinks detected! ")?;
162+
if owned_inodes == inodes {
163+
ln!("No files have links outside this tree")?;
164+
ln!("* Number of shared inodes: {inodes}")?;
165+
ln!("* Total number of links: {all_links}")?;
166+
ln!("* Total shared size: {shared_size}")?;
167+
} else if owned_inodes == &0 {
168+
ln!("All hardlinks within this tree have links without")?;
169+
ln!("* Number of shared inodes: {inodes}")?;
170+
ln!("* Total number of links: {all_links}, {detected_links} detected")?;
171+
ln!("* Total shared size: {shared_size}")?;
172+
} else {
173+
ln!("Some files have links outside this tree")?;
174+
ln!("* Number of shared inodes: {inodes} total, {owned_inodes} exclusive")?;
175+
ln!("* Total number of links: {all_links} total, {detected_links} detected, {owned_links} exclusive")?;
176+
ln!("* Total shared size: {shared_size} total, {owned_shared_size} exclusive")?;
177+
}
178+
179+
Ok(())
127180
}
128181
}
129182

@@ -146,7 +199,8 @@ impl<Size: Copy> From<ReflectionEntry<Size>> for SingleInodeSummary<Size> {
146199
impl<'r, Size: Copy> From<&'r ReflectionEntry<Size>> for SingleInodeSummary<Size> {
147200
fn from(reflection: &'r ReflectionEntry<Size>) -> Self {
148201
SingleInodeSummary {
149-
links: reflection.paths.len(),
202+
links: reflection.links,
203+
paths: reflection.paths.len(),
150204
size: reflection.size,
151205
}
152206
}
@@ -161,7 +215,8 @@ impl<'a, Size: Copy> From<IterItem<'a, Size>> for SingleInodeSummary<Size> {
161215
impl<'r, 'a, Size: Copy> From<&'r IterItem<'a, Size>> for SingleInodeSummary<Size> {
162216
fn from(value: &'r IterItem<'a, Size>) -> Self {
163217
SingleInodeSummary {
164-
links: value.paths().len(),
218+
links: value.links(),
219+
paths: value.paths().len(),
165220
size: *value.size(),
166221
}
167222
}

0 commit comments

Comments
 (0)