diff options
author | Thomas Hurst <tom@hur.st> | 2020-02-22 04:51:14 +0000 |
---|---|---|
committer | Thomas Hurst <tom@hur.st> | 2020-02-22 05:13:28 +0000 |
commit | 5b522946adb5bb71dd51068eee5f1136e6403b31 (patch) | |
tree | 25ffceb6698d76c4c5cecf2017289ce6eaa98ed9 | |
parent | 0c86b894caf99d3bee319c5af6f1dcf754b44011 (diff) |
Add hardlink tracking, and an option to disable it
-rw-r--r-- | src/aggregate.rs | 5 | ||||
-rw-r--r-- | src/common.rs | 1 | ||||
-rw-r--r-- | src/inodefilter.rs | 76 | ||||
-rw-r--r-- | src/interactive/app_test/utils.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/options.rs | 4 | ||||
-rw-r--r-- | src/traverse.rs | 5 |
8 files changed, 91 insertions, 4 deletions
diff --git a/src/aggregate.rs b/src/aggregate.rs index 63fed08..b847de5 100644 --- a/src/aggregate.rs +++ b/src/aggregate.rs @@ -1,4 +1,4 @@ -use crate::{WalkOptions, WalkResult}; +use crate::{WalkOptions, WalkResult, InodeFilter}; use failure::Error; use std::borrow::Cow; use std::{fmt, io, path::Path}; @@ -20,6 +20,7 @@ pub fn aggregate( let mut total = 0; let mut num_roots = 0; let mut aggregates = Vec::new(); + let mut inodes = InodeFilter::default(); for path in paths.into_iter() { num_roots += 1; let mut num_bytes = 0u64; @@ -29,7 +30,7 @@ pub fn aggregate( match entry { Ok(entry) => { let file_size = match entry.metadata { - Some(Ok(ref m)) if !m.is_dir() => { + Some(Ok(ref m)) if !m.is_dir() && (options.count_links || inodes.add(m)) => { if options.apparent_size { m.len() } else { diff --git a/src/common.rs b/src/common.rs index 60d0ee3..2d5fcba 100644 --- a/src/common.rs +++ b/src/common.rs @@ -152,6 +152,7 @@ pub struct WalkOptions { /// for more information. pub threads: usize, pub byte_format: ByteFormat, + pub count_links: bool, pub apparent_size: bool, pub color: Color, pub sorting: TraversalSorting, diff --git a/src/inodefilter.rs b/src/inodefilter.rs new file mode 100644 index 0000000..6db12e1 --- /dev/null +++ b/src/inodefilter.rs @@ -0,0 +1,76 @@ + +#![cfg_attr(windows, feature(windows_by_handle))] + +use std::collections::HashMap; + +#[derive(Debug, Default, Clone)] +pub struct InodeFilter { + inner: HashMap<u64, u64> +} + +impl InodeFilter { + #[cfg(unix)] + pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool { + use std::os::unix::fs::MetadataExt; + + self.add_inode(metadata.ino(), metadata.nlink()) + } + + #[cfg(windows)] + pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool { + use std::os::windows::fs::MetadataExt; + + if let (Some(inode), Some(nlinks)) = (metadata.file_index(), metadata.number_of_links()) { + self.add_inode(inode, nlinks as u64) + } else { + true + } + } + + #[cfg(not(any(unix, windows)))] + pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool { + true + } + + pub fn add_inode(&mut self, inode: u64, nlinks: u64) -> bool { + if nlinks <= 1 { + return true; + } + + match self.inner.get_mut(&inode) { + Some(count) => { + *count -= 1; + + if *count == 0 { + self.inner.remove(&inode); + } + + false + }, + None => { + self.inner.insert(inode, nlinks - 1); + true + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_filters_inodes() { + let mut inodes = InodeFilter::default(); + + assert!(inodes.add_inode(1, 2)); + assert!(!inodes.add_inode(1, 2)); + + assert!(inodes.add_inode(1, 3)); + assert!(!inodes.add_inode(1, 3)); + assert!(!inodes.add_inode(1, 3)); + + assert!(inodes.add_inode(1, 1)); + assert!(inodes.add_inode(1, 1)); + } +} diff --git a/src/interactive/app_test/utils.rs b/src/interactive/app_test/utils.rs index f421665..d696f6c 100644 --- a/src/interactive/app_test/utils.rs +++ b/src/interactive/app_test/utils.rs @@ -165,6 +165,7 @@ pub fn initialized_app_and_terminal_with_closure<P: AsRef<Path>>( threads: 1, byte_format: ByteFormat::Metric, apparent_size: true, + count_links: false, color: Color::None, sorting: TraversalSorting::AlphabeticalByFileName, }, @@ -5,8 +5,10 @@ extern crate jwalk; mod aggregate; mod common; +mod inodefilter; pub mod traverse; pub use aggregate::aggregate; pub use common::*; +pub(crate) use inodefilter::InodeFilter; diff --git a/src/main.rs b/src/main.rs index df7639a..c1643a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ fn run() -> Result<(), Error> { Color::None }, apparent_size: opt.apparent_size, + count_links: opt.count_links, sorting: TraversalSorting::None, }; let res = match opt.command { diff --git a/src/options.rs b/src/options.rs index 4dc1aff..e3e44a2 100644 --- a/src/options.rs +++ b/src/options.rs @@ -56,6 +56,10 @@ pub struct Args { #[structopt(short = "A", long = "apparent-size")] pub apparent_size: bool, + /// Count hard-linked files each time they are seen + #[structopt(short = "l", long = "count-links")] + pub count_links: bool, + /// One or more input files or directories. If unset, we will use all entries in the current working directory. #[structopt(parse(from_os_str))] pub input: Vec<PathBuf>, diff --git a/src/traverse.rs b/src/traverse.rs index c9bb4a9..6e866c2 100644 --- a/src/traverse.rs +++ b/src/traverse.rs @@ -1,4 +1,4 @@ -use crate::{get_size_or_panic, WalkOptions}; +use crate::{get_size_or_panic, WalkOptions, InodeFilter}; use failure::Error; use petgraph::{graph::NodeIndex, stable_graph::StableGraph, Directed, Direction}; use std::{ffi::OsString, path::PathBuf, time::Duration, time::Instant}; @@ -66,6 +66,7 @@ impl Traversal { let mut sizes_per_depth_level = Vec::new(); let mut current_size_at_depth = 0; let mut previous_depth = 0; + let mut inodes = InodeFilter::default(); let mut last_checked = Instant::now(); @@ -93,7 +94,7 @@ impl Traversal { entry.file_name }; let file_size = match entry.metadata { - Some(Ok(ref m)) if !m.is_dir() => { + Some(Ok(ref m)) if !m.is_dir() && (walk_options.count_links || inodes.add(m)) => { if walk_options.apparent_size { m.len() } else { |