Skip to content

Commit bc8790b

Browse files
authored
Merge pull request #478 from rustcoreutils/du
Du updates
2 parents 65700a3 + 2294dc5 commit bc8790b

File tree

4 files changed

+542
-69
lines changed

4 files changed

+542
-69
lines changed

Cargo.lock

Lines changed: 31 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tree/du.rs

Lines changed: 119 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
// file in the root directory of this project.
77
// SPDX-License-Identifier: MIT
88
//
9-
// TODO:
10-
// - implement -H, -L, -x
11-
//
129

1310
use clap::Parser;
1411
use gettextrs::{bind_textdomain_codeset, gettext, setlocale, textdomain, LocaleCategory};
15-
use std::os::unix::fs::MetadataExt;
16-
use std::path::Path;
17-
use std::{fs, io};
12+
use std::{
13+
cell::RefCell,
14+
collections::{HashSet, LinkedList},
15+
os::unix::fs::MetadataExt,
16+
};
1817

1918
/// du - estimate file space usage
2019
#[derive(Parser)]
@@ -56,50 +55,125 @@ fn calc_size(kilo: bool, size: u64) -> u64 {
5655
}
5756
}
5857

59-
fn print_pathinfo(args: &Args, filename: &str, size: u64, toplevel: bool) {
60-
if args.sum && !toplevel {
61-
return;
62-
}
63-
64-
// print the file size
65-
println!("{}\t{}", size, filename);
58+
struct Node {
59+
total_blocks: u64,
6660
}
6761

68-
fn du_cli_arg(
69-
args: &Args,
70-
filename: &str,
71-
total: &mut u64,
72-
toplevel: bool,
73-
) -> Result<(), io::Error> {
74-
let path = Path::new(filename);
75-
let metadata = fs::metadata(path)?;
76-
77-
// recursively process directories
78-
if metadata.is_dir() {
79-
let mut sub_total = 0;
80-
for entry in fs::read_dir(path)? {
81-
let entry = entry?;
82-
let path = entry.path();
83-
let filename = path.to_str().unwrap();
84-
if let Err(e) = du_cli_arg(args, filename, &mut sub_total, false) {
85-
eprintln!("{}: {}", filename, e);
62+
fn du_impl(args: &Args, filename: &str) -> bool {
63+
let terminate = RefCell::new(false);
64+
let stack: RefCell<LinkedList<Node>> = RefCell::new(LinkedList::new());
65+
// Track seen (dev, ino) pairs for hard link deduplication
66+
let seen: RefCell<HashSet<(u64, u64)>> = RefCell::new(HashSet::new());
67+
// Track initial device for -x option
68+
let initial_dev: RefCell<Option<u64>> = RefCell::new(None);
69+
70+
let path = std::path::Path::new(filename);
71+
72+
ftw::traverse_directory(
73+
path,
74+
|entry| {
75+
if *terminate.borrow() {
76+
return Ok(false);
8677
}
87-
}
88-
print_pathinfo(args, filename, sub_total, toplevel);
89-
90-
*total += sub_total;
91-
return Ok(());
92-
}
9378

94-
// print the file size
95-
let size = calc_size(args.kilo, metadata.blocks());
96-
*total += size;
79+
let md = entry
80+
.metadata()
81+
.expect("ftw::traverse_directory yielded an entry without metadata");
82+
83+
// -x: skip files on different filesystems
84+
if args.one_fs {
85+
let dev = md.dev();
86+
let mut init_dev = initial_dev.borrow_mut();
87+
if init_dev.is_none() {
88+
*init_dev = Some(dev);
89+
} else if Some(dev) != *init_dev {
90+
// Different filesystem, skip this entry
91+
return Ok(false);
92+
}
93+
}
9794

98-
if args.all {
99-
print_pathinfo(args, filename, size, toplevel);
100-
}
95+
let is_dir = md.is_dir();
96+
97+
// Handle hard link deduplication: files with nlink > 1 should only be counted once
98+
let size = if !is_dir && md.nlink() > 1 {
99+
let key = (md.dev(), md.ino());
100+
let mut seen_set = seen.borrow_mut();
101+
if seen_set.contains(&key) {
102+
// Already counted this file, use size 0
103+
0
104+
} else {
105+
seen_set.insert(key);
106+
calc_size(args.kilo, md.blocks())
107+
}
108+
} else {
109+
calc_size(args.kilo, md.blocks())
110+
};
111+
112+
let mut stack = stack.borrow_mut();
113+
114+
// Check if this is the original file operand (root of traversal)
115+
let is_root = entry.path().as_inner() == path;
116+
117+
if is_dir {
118+
// For directories, push onto stack. Don't add to parent here -
119+
// the directory's total will be added when we exit the directory.
120+
stack.push_back(Node { total_blocks: size });
121+
} else {
122+
// For files, add size to parent directory's total
123+
if let Some(back) = stack.back_mut() {
124+
back.total_blocks += size;
125+
}
126+
// For non-directories:
127+
// - Always print if it's the original file operand (POSIX BSD behavior)
128+
// - Print if -a is specified
129+
// - Don't print with -s (handled separately)
130+
let display_size = size;
131+
if is_root {
132+
// File operands are always listed
133+
println!("{}\t{}", display_size, entry.path());
134+
} else if args.all && !args.sum {
135+
// -a: report all files within directories
136+
println!("{}\t{}", display_size, entry.path());
137+
}
138+
}
101139

102-
Ok(())
140+
Ok(is_dir)
141+
},
142+
|entry| {
143+
let mut stack = stack.borrow_mut();
144+
if let Some(node) = stack.pop_back() {
145+
let size = node.total_blocks;
146+
147+
// Recursively sum the block size
148+
if let Some(back) = stack.back_mut() {
149+
back.total_blocks += size;
150+
}
151+
152+
if args.sum {
153+
// -s: report only the total sum for the file operand
154+
let entry_path = entry.path();
155+
if entry_path.as_inner() == path {
156+
println!("{}\t{}", size, entry_path);
157+
}
158+
} else {
159+
println!("{}\t{}", size, entry.path());
160+
}
161+
}
162+
Ok(())
163+
},
164+
|_entry, error| {
165+
*terminate.borrow_mut() = true;
166+
eprintln!("du: {}", error.inner());
167+
},
168+
ftw::TraverseDirectoryOpts {
169+
follow_symlinks_on_args: args.follow_cli,
170+
follow_symlinks: args.dereference,
171+
..Default::default()
172+
},
173+
);
174+
175+
let failed = *terminate.borrow();
176+
!failed
103177
}
104178

105179
fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -114,13 +188,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
114188
args.files.push(".".to_string());
115189
}
116190
let mut exit_code = 0;
117-
let mut total = 0;
118191

119192
// apply the group to each file
120193
for filename in &args.files {
121-
if let Err(e) = du_cli_arg(&args, filename, &mut total, true) {
194+
if !du_impl(&args, filename) {
122195
exit_code = 1;
123-
eprintln!("{}: {}", filename, e);
124196
}
125197
}
126198

0 commit comments

Comments
 (0)