66// file in the root directory of this project.
77// SPDX-License-Identifier: MIT
88//
9- // TODO:
10- // - implement -H, -L, -x
11- //
129
1310use clap:: Parser ;
1411use 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
105179fn 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