|
| 1 | +use crate::size; |
| 2 | +use dashmap::{iter::Iter as DashIter, mapref::multiple::RefMulti, DashMap}; |
| 3 | +use derive_more::{Display, Error}; |
| 4 | +use pipe_trait::Pipe; |
| 5 | +use std::{ |
| 6 | + fmt::Debug, |
| 7 | + path::{Path, PathBuf}, |
| 8 | +}; |
| 9 | + |
| 10 | +/// Storage to be used by [`crate::hook::RecordHardLink`]. |
| 11 | +#[derive(Debug, Clone)] |
| 12 | +pub struct RecordHardLinkStorage<Size>( |
| 13 | + /// Map an inode number to its size and detected paths. |
| 14 | + DashMap<u64, (Size, Vec<PathBuf>)>, // TODO: benchmark against Mutex<HashMap<u64, (Size, Vec<PathBuf>)>> |
| 15 | +); |
| 16 | + |
| 17 | +impl<Size> RecordHardLinkStorage<Size> { |
| 18 | + /// Create a new record. |
| 19 | + pub fn new() -> Self { |
| 20 | + RecordHardLinkStorage(DashMap::new()) |
| 21 | + } |
| 22 | + |
| 23 | + /// Iterate over the recorded entries. |
| 24 | + pub fn iter(&self) -> Iter<Size> { |
| 25 | + self.0.iter().pipe(Iter) |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +impl<Size> Default for RecordHardLinkStorage<Size> { |
| 30 | + fn default() -> Self { |
| 31 | + RecordHardLinkStorage::new() |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Error that occurs when a different size was detected for the same [`ino`](std::os::unix::fs::MetadataExt::ino). |
| 36 | +#[derive(Debug, Display, Error)] |
| 37 | +#[display(bound(Size: Debug))] |
| 38 | +#[display("Size for inode {ino} changed from {recorded:?} to {detected:?}")] |
| 39 | +pub struct SizeConflictError<Size> { |
| 40 | + pub ino: u64, // TODO: define a newtype of ino. |
| 41 | + pub recorded: Size, |
| 42 | + pub detected: Size, |
| 43 | +} |
| 44 | + |
| 45 | +/// Error type of [`RecordHardLinkStorage::add`]. |
| 46 | +#[derive(Debug, Display, Error)] |
| 47 | +#[display(bound(Size: Debug))] |
| 48 | +#[non_exhaustive] |
| 49 | +pub enum AddError<Size> { |
| 50 | + SizeConflict(SizeConflictError<Size>), |
| 51 | +} |
| 52 | + |
| 53 | +impl<Size> RecordHardLinkStorage<Size> |
| 54 | +where |
| 55 | + Size: size::Size, |
| 56 | +{ |
| 57 | + /// Add an entry to the record. |
| 58 | + pub(crate) fn add(&self, ino: u64, size: Size, path: &Path) -> Result<(), AddError<Size>> { |
| 59 | + let mut size_assertion = Ok(()); |
| 60 | + self.0 |
| 61 | + .entry(ino) |
| 62 | + .and_modify(|(recorded, paths)| { |
| 63 | + let (detected, recorded) = (size, *recorded); |
| 64 | + if size == recorded { |
| 65 | + paths.push(path.to_path_buf()); |
| 66 | + } else { |
| 67 | + size_assertion = Err(SizeConflictError { |
| 68 | + ino, |
| 69 | + recorded, |
| 70 | + detected, |
| 71 | + }); |
| 72 | + } |
| 73 | + }) |
| 74 | + .or_insert_with(|| (size, vec![path.to_path_buf()])); |
| 75 | + size_assertion.map_err(AddError::SizeConflict) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +/// Iterator over entries in [`RecordHardLinkStorage`]. |
| 80 | +#[derive(derive_more::Debug)] |
| 81 | +#[debug(bound())] |
| 82 | +#[debug("Iter(..)")] |
| 83 | +pub struct Iter<'a, Size>(DashIter<'a, u64, (Size, Vec<PathBuf>)>); |
| 84 | + |
| 85 | +/// [Item](Iterator::Item) of [`Iter`]. |
| 86 | +#[derive(derive_more::Debug)] |
| 87 | +#[debug(bound())] |
| 88 | +#[debug("IterItem(..)")] |
| 89 | +pub struct IterItem<'a, Size>(RefMulti<'a, u64, (Size, Vec<PathBuf>)>); |
| 90 | + |
| 91 | +impl<'a, Size> Iterator for Iter<'a, Size> { |
| 92 | + type Item = IterItem<'a, Size>; |
| 93 | + fn next(&mut self) -> Option<Self::Item> { |
| 94 | + self.0.next().map(IterItem) |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +impl<'a, Size> IterItem<'a, Size> { |
| 99 | + /// Number of the inode. |
| 100 | + pub fn ino(&self) -> u64 { |
| 101 | + *self.0.key() |
| 102 | + } |
| 103 | + |
| 104 | + /// Size of the inode. |
| 105 | + pub fn size(&self) -> &Size { |
| 106 | + &self.0.value().0 |
| 107 | + } |
| 108 | + |
| 109 | + /// Links of the inode. |
| 110 | + pub fn links(&self) -> &[PathBuf] { |
| 111 | + &self.0.value().1 |
| 112 | + } |
| 113 | +} |
0 commit comments