Skip to main content

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