summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2020-02-22 13:42:35 +0800
committerSebastian Thiel <sebastian.thiel@icloud.com>2020-02-22 13:42:35 +0800
commita6a4cf3705ba764ca0862fd3faaf0f7df31ac28d (patch)
treeb98941ce306a0f6aaf4611c0600c4e920dafbf1a
parent0c86b894caf99d3bee319c5af6f1dcf754b44011 (diff)
parent93b9e12a1de090d1c07968144f6d21061e6de50a (diff)
Merge branch 'Freaky-hardlink-tracking'
-rw-r--r--README.md2
-rw-r--r--src/aggregate.rs5
-rw-r--r--src/common.rs1
-rw-r--r--src/inodefilter.rs75
-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
9 files changed, 90 insertions, 6 deletions
diff --git a/README.md b/README.md
index d818beb..abf01f0 100644
--- a/README.md
+++ b/README.md
@@ -172,10 +172,8 @@ Thanks to [jwalk][jwalk], all there was left to do is to write a command-line in
### Limitations
* Interactive mode only looks good in dark terminals (see [this issue](https://github.com/Byron/dua-cli/issues/13))
-* _Hard links_ are not understood, thus hard-linked files will possibly be counted multiple times.
* _Symlinks_ are followed and we obtain the logical size of the file they point to. Ideally, we only
count their actual size.
-* _logical filesize_ is used instead of computed or estimating actual size on disk.
* _easy fix_: file names in main window are not truncated if too large. They are cut off on the right.
* There are plenty of examples in `tests/fixtures` which don't render correctly in interactive mode.
This can be due to graphemes not interpreted correctly. With Chinese characters for instance,
diff --git a/src/aggregate.rs b/src/aggregate.rs
index 63fed08..28a3ccd 100644
--- a/src/aggregate.rs
+++ b/src/aggregate.rs
@@ -1,4 +1,4 @@
-use crate::{WalkOptions, WalkResult};
+use crate::{InodeFilter, WalkOptions, WalkResult};
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..1523f1c
--- /dev/null
+++ b/src/inodefilter.rs
@@ -0,0 +1,75 @@
+#![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..13b1623 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, InodeFilter, WalkOptions};
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 {