Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a6a4cf3

Browse files
committed
Merge branch 'Freaky-hardlink-tracking'
2 parents 0c86b89 + 93b9e12 commit a6a4cf3

9 files changed

Lines changed: 90 additions & 6 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,8 @@ Thanks to [jwalk][jwalk], all there was left to do is to write a command-line in
172172
### Limitations
173173

174174
* Interactive mode only looks good in dark terminals (see [this issue](https://github.com/Byron/dua-cli/issues/13))
175-
* _Hard links_ are not understood, thus hard-linked files will possibly be counted multiple times.
176175
* _Symlinks_ are followed and we obtain the logical size of the file they point to. Ideally, we only
177176
count their actual size.
178-
* _logical filesize_ is used instead of computed or estimating actual size on disk.
179177
* _easy fix_: file names in main window are not truncated if too large. They are cut off on the right.
180178
* There are plenty of examples in `tests/fixtures` which don't render correctly in interactive mode.
181179
This can be due to graphemes not interpreted correctly. With Chinese characters for instance,

src/aggregate.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{WalkOptions, WalkResult};
1+
use crate::{InodeFilter, WalkOptions, WalkResult};
22
use failure::Error;
33
use std::borrow::Cow;
44
use std::{fmt, io, path::Path};
@@ -20,6 +20,7 @@ pub fn aggregate(
2020
let mut total = 0;
2121
let mut num_roots = 0;
2222
let mut aggregates = Vec::new();
23+
let mut inodes = InodeFilter::default();
2324
for path in paths.into_iter() {
2425
num_roots += 1;
2526
let mut num_bytes = 0u64;
@@ -29,7 +30,7 @@ pub fn aggregate(
2930
match entry {
3031
Ok(entry) => {
3132
let file_size = match entry.metadata {
32-
Some(Ok(ref m)) if !m.is_dir() => {
33+
Some(Ok(ref m)) if !m.is_dir() && (options.count_links || inodes.add(m)) => {
3334
if options.apparent_size {
3435
m.len()
3536
} else {

src/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ pub struct WalkOptions {
152152
/// for more information.
153153
pub threads: usize,
154154
pub byte_format: ByteFormat,
155+
pub count_links: bool,
155156
pub apparent_size: bool,
156157
pub color: Color,
157158
pub sorting: TraversalSorting,

src/inodefilter.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#![cfg_attr(windows, feature(windows_by_handle))]
2+
3+
use std::collections::HashMap;
4+
5+
#[derive(Debug, Default, Clone)]
6+
pub struct InodeFilter {
7+
inner: HashMap<u64, u64>,
8+
}
9+
10+
impl InodeFilter {
11+
#[cfg(unix)]
12+
pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool {
13+
use std::os::unix::fs::MetadataExt;
14+
15+
self.add_inode(metadata.ino(), metadata.nlink())
16+
}
17+
18+
#[cfg(windows)]
19+
pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool {
20+
use std::os::windows::fs::MetadataExt;
21+
22+
if let (Some(inode), Some(nlinks)) = (metadata.file_index(), metadata.number_of_links()) {
23+
self.add_inode(inode, nlinks as u64)
24+
} else {
25+
true
26+
}
27+
}
28+
29+
#[cfg(not(any(unix, windows)))]
30+
pub fn add(&mut self, metadata: &std::fs::Metadata) -> bool {
31+
true
32+
}
33+
34+
pub fn add_inode(&mut self, inode: u64, nlinks: u64) -> bool {
35+
if nlinks <= 1 {
36+
return true;
37+
}
38+
39+
match self.inner.get_mut(&inode) {
40+
Some(count) => {
41+
*count -= 1;
42+
43+
if *count == 0 {
44+
self.inner.remove(&inode);
45+
}
46+
47+
false
48+
}
49+
None => {
50+
self.inner.insert(inode, nlinks - 1);
51+
true
52+
}
53+
}
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use super::*;
60+
61+
#[test]
62+
fn it_filters_inodes() {
63+
let mut inodes = InodeFilter::default();
64+
65+
assert!(inodes.add_inode(1, 2));
66+
assert!(!inodes.add_inode(1, 2));
67+
68+
assert!(inodes.add_inode(1, 3));
69+
assert!(!inodes.add_inode(1, 3));
70+
assert!(!inodes.add_inode(1, 3));
71+
72+
assert!(inodes.add_inode(1, 1));
73+
assert!(inodes.add_inode(1, 1));
74+
}
75+
}

src/interactive/app_test/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ pub fn initialized_app_and_terminal_with_closure<P: AsRef<Path>>(
165165
threads: 1,
166166
byte_format: ByteFormat::Metric,
167167
apparent_size: true,
168+
count_links: false,
168169
color: Color::None,
169170
sorting: TraversalSorting::AlphabeticalByFileName,
170171
},

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ extern crate jwalk;
55

66
mod aggregate;
77
mod common;
8+
mod inodefilter;
89

910
pub mod traverse;
1011

1112
pub use aggregate::aggregate;
1213
pub use common::*;
14+
pub(crate) use inodefilter::InodeFilter;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fn run() -> Result<(), Error> {
3030
Color::None
3131
},
3232
apparent_size: opt.apparent_size,
33+
count_links: opt.count_links,
3334
sorting: TraversalSorting::None,
3435
};
3536
let res = match opt.command {

src/options.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ pub struct Args {
5656
#[structopt(short = "A", long = "apparent-size")]
5757
pub apparent_size: bool,
5858

59+
/// Count hard-linked files each time they are seen
60+
#[structopt(short = "l", long = "count-links")]
61+
pub count_links: bool,
62+
5963
/// One or more input files or directories. If unset, we will use all entries in the current working directory.
6064
#[structopt(parse(from_os_str))]
6165
pub input: Vec<PathBuf>,

src/traverse.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{get_size_or_panic, WalkOptions};
1+
use crate::{get_size_or_panic, InodeFilter, WalkOptions};
22
use failure::Error;
33
use petgraph::{graph::NodeIndex, stable_graph::StableGraph, Directed, Direction};
44
use std::{ffi::OsString, path::PathBuf, time::Duration, time::Instant};
@@ -66,6 +66,7 @@ impl Traversal {
6666
let mut sizes_per_depth_level = Vec::new();
6767
let mut current_size_at_depth = 0;
6868
let mut previous_depth = 0;
69+
let mut inodes = InodeFilter::default();
6970

7071
let mut last_checked = Instant::now();
7172

@@ -93,7 +94,7 @@ impl Traversal {
9394
entry.file_name
9495
};
9596
let file_size = match entry.metadata {
96-
Some(Ok(ref m)) if !m.is_dir() => {
97+
Some(Ok(ref m)) if !m.is_dir() && (walk_options.count_links || inodes.add(m)) => {
9798
if walk_options.apparent_size {
9899
m.len()
99100
} else {

0 commit comments

Comments
 (0)