summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Hurst <tom@hur.st>2020-02-22 04:51:14 +0000
committerThomas Hurst <tom@hur.st>2020-02-22 05:13:28 +0000
commit5b522946adb5bb71dd51068eee5f1136e6403b31 (patch)
tree25ffceb6698d76c4c5cecf2017289ce6eaa98ed9
parent0c86b894caf99d3bee319c5af6f1dcf754b44011 (diff)
Add hardlink tracking, and an option to disable it
-rw-r--r--src/aggregate.rs5
-rw-r--r--src/common.rs1
-rw-r--r--src/inodefilter.rs76
-rw-r--r--src/interactive/app_test/utils.rs1
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs1
-rw-r--r--src/options.rs4
-rw-r--r--src/traverse.rs5
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,
},
diff --git a/src/lib.rs b/src/lib.rs
index 9c1c3f2..ae6f953 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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 {