parallel_disk_usage/hardlink/
hardlink_list.rs

1pub mod iter;
2pub mod reflection;
3pub mod summary;
4
5pub use iter::Iter;
6pub use reflection::Reflection;
7pub use summary::Summary;
8
9pub use Reflection as HardlinkListReflection;
10pub use Summary as SharedLinkSummary;
11
12use crate::{hardlink::LinkPathList, inode::InodeNumber, size};
13use dashmap::DashMap;
14use derive_more::{Display, Error};
15use smart_default::SmartDefault;
16use std::fmt::Debug;
17
18#[cfg(any(unix, test))]
19use pipe_trait::Pipe;
20#[cfg(any(unix, test))]
21use std::path::Path;
22
23/// Map value in [`HardlinkList`].
24#[derive(Debug, Clone)]
25struct Value<Size> {
26    /// The size of the file.
27    size: Size,
28    /// Total number of links of the file, both listed (in [`Self::paths`]) and unlisted.
29    links: u64,
30    /// Paths to the detected links of the file.
31    paths: LinkPathList,
32}
33
34/// Storage to be used by [`crate::hardlink::RecordHardlinks`].
35///
36/// **Reflection:** `HardlinkList` does not implement `PartialEq`, `Eq`,
37/// `Deserialize`, and `Serialize` directly. Instead, it can be converted into a
38/// [`Reflection`] which implement these traits.
39#[derive(Debug, SmartDefault, Clone)]
40pub struct HardlinkList<Size>(
41    /// Map an inode number to its size, number of links, and detected paths.
42    DashMap<InodeNumber, Value<Size>>,
43);
44
45impl<Size> HardlinkList<Size> {
46    /// Create a new record.
47    pub fn new() -> Self {
48        HardlinkList::default()
49    }
50
51    /// Get the number of entries in the list.
52    pub fn len(&self) -> usize {
53        self.0.len()
54    }
55
56    /// Check whether the list is empty.
57    pub fn is_empty(&self) -> bool {
58        self.0.is_empty()
59    }
60
61    /// Create reflection.
62    pub fn into_reflection(self) -> Reflection<Size> {
63        self.into()
64    }
65}
66
67/// Error that occurs when a different size was detected for the same [`ino`][ino].
68///
69/// <!-- Should have been `std::os::unix::fs::MetadataExt::ino` but it would error on Windows -->
70/// [ino]: https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.ino
71#[derive(Debug, Display, Error)]
72#[cfg_attr(test, derive(PartialEq, Eq))]
73#[display(bound(Size: Debug))]
74#[display("Size for inode {ino} changed from {recorded:?} to {detected:?}")]
75pub struct SizeConflictError<Size> {
76    pub ino: InodeNumber,
77    pub recorded: Size,
78    pub detected: Size,
79}
80
81/// Error that occurs when a different [`nlink`][nlink] was detected for the same [`ino`][ino].
82///
83/// <!-- Should have been `std::os::unix::fs::MetadataExt::nlink` but it would error on Windows -->
84/// [nlink]: https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.nlink
85/// <!-- Should have been `std::os::unix::fs::MetadataExt::ino` but it would error on Windows -->
86/// [ino]: https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.ino
87#[derive(Debug, Display, Error)]
88#[cfg_attr(test, derive(PartialEq, Eq))]
89#[display("Number of links of inode {ino} changed from {recorded:?} to {detected:?}")]
90pub struct NumberOfLinksConflictError {
91    pub ino: InodeNumber,
92    pub recorded: u64,
93    pub detected: u64,
94}
95
96/// Error that occurs when it fails to add an item to [`HardlinkList`].
97#[derive(Debug, Display, Error)]
98#[cfg_attr(test, derive(PartialEq, Eq))]
99#[display(bound(Size: Debug))]
100#[non_exhaustive]
101pub enum AddError<Size> {
102    SizeConflict(SizeConflictError<Size>),
103    NumberOfLinksConflict(NumberOfLinksConflictError),
104}
105
106impl<Size> HardlinkList<Size>
107where
108    Size: size::Size,
109{
110    /// Add an entry to the record.
111    #[cfg(any(unix, test))] // this function isn't used on non-POSIX except in tests
112    pub(crate) fn add(
113        &self,
114        ino: InodeNumber,
115        size: Size,
116        links: u64,
117        path: &Path,
118    ) -> Result<(), AddError<Size>> {
119        let mut assertions = Ok(());
120        self.0
121            .entry(ino)
122            .and_modify(|recorded| {
123                if size != recorded.size {
124                    assertions = Err(AddError::SizeConflict(SizeConflictError {
125                        ino,
126                        recorded: recorded.size,
127                        detected: size,
128                    }));
129                    return;
130                }
131
132                if links != recorded.links {
133                    assertions = Err(AddError::NumberOfLinksConflict(
134                        NumberOfLinksConflictError {
135                            ino,
136                            recorded: recorded.links,
137                            detected: links,
138                        },
139                    ));
140                    return;
141                }
142
143                recorded.paths.add(path.to_path_buf());
144            })
145            .or_insert_with(|| {
146                let paths = path.to_path_buf().pipe(LinkPathList::single);
147                Value { size, links, paths }
148            });
149        assertions
150    }
151}
152
153#[cfg(test)]
154mod test;