Skip to content

Commit 48429d3

Browse files
committed
Basic functionality
1 parent 72788d8 commit 48429d3

File tree

4 files changed

+232
-59
lines changed

4 files changed

+232
-59
lines changed

Cargo.lock

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

tree/du.rs

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,10 @@
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, setlocale, textdomain, LocaleCategory};
15-
use std::os::unix::fs::MetadataExt;
16-
use std::path::Path;
17-
use std::{fs, io};
12+
use std::{cell::RefCell, collections::LinkedList, os::unix::fs::MetadataExt};
1813

1914
/// du - estimate file space usage
2015
#[derive(Parser)]
@@ -56,50 +51,80 @@ fn calc_size(kilo: bool, size: u64) -> u64 {
5651
}
5752
}
5853

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);
54+
struct Node {
55+
total_blocks: u64,
6656
}
6757

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);
58+
fn du_impl(args: &Args, filename: &str) -> bool {
59+
let terminate = RefCell::new(false);
60+
61+
let stack: RefCell<LinkedList<Node>> = RefCell::new(LinkedList::new());
62+
63+
let path = std::path::Path::new(filename);
64+
65+
// TODO: Comment this
66+
ftw::traverse_directory(
67+
path,
68+
|entry| {
69+
if *terminate.borrow() {
70+
return Ok(false);
8671
}
87-
}
88-
print_pathinfo(args, filename, sub_total, toplevel);
8972

90-
*total += sub_total;
91-
return Ok(());
92-
}
73+
let md = entry.metadata().unwrap();
74+
let size = calc_size(args.kilo, md.blocks());
75+
let recurse = md.is_dir();
9376

94-
// print the file size
95-
let size = calc_size(args.kilo, metadata.blocks());
96-
*total += size;
77+
let mut stack = stack.borrow_mut();
78+
if let Some(back) = stack.back_mut() {
79+
back.total_blocks += size;
80+
}
9781

98-
if args.all {
99-
print_pathinfo(args, filename, size, toplevel);
100-
}
82+
// -a
83+
if !recurse && !args.sum {
84+
println!("{}\t{}", size, entry.path());
85+
}
10186

102-
Ok(())
87+
if recurse {
88+
stack.push_back(Node { total_blocks: size });
89+
}
90+
91+
Ok(recurse)
92+
},
93+
|entry| {
94+
let mut stack = stack.borrow_mut();
95+
if let Some(node) = stack.pop_back() {
96+
let size = node.total_blocks;
97+
98+
// Recursively sum the block size
99+
if let Some(back) = stack.back_mut() {
100+
back.total_blocks += size;
101+
}
102+
103+
if args.sum {
104+
// -s, report only the total sum for the args
105+
let entry_path = entry.path();
106+
if entry_path.as_inner() == path {
107+
println!("{}\t{}", size, entry_path);
108+
}
109+
} else {
110+
println!("{}\t{}", size, entry.path());
111+
}
112+
}
113+
Ok(())
114+
},
115+
|_entry, error| {
116+
*terminate.borrow_mut() = true;
117+
eprintln!("du: {}", error.inner());
118+
},
119+
ftw::TraverseDirectoryOpts {
120+
follow_symlinks_on_args: args.follow_cli,
121+
follow_symlinks: args.dereference,
122+
..Default::default()
123+
},
124+
);
125+
126+
let failed = *terminate.borrow();
127+
!failed
103128
}
104129

105130
fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -114,13 +139,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
114139
args.files.push(".".to_string());
115140
}
116141
let mut exit_code = 0;
117-
let mut total = 0;
118142

119143
// apply the group to each file
120144
for filename in &args.files {
121-
if let Err(e) = du_cli_arg(&args, filename, &mut total, true) {
145+
if !du_impl(&args, filename) {
122146
exit_code = 1;
123-
eprintln!("{}: {}", filename, e);
124147
}
125148
}
126149

tree/tests/du/mod.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//
2+
// Copyright (c) 2024 Hemi Labs, Inc.
3+
//
4+
// This file is part of the posixutils-rs project covered under
5+
// the MIT License. For the full license text, please see the LICENSE
6+
// file in the root directory of this project.
7+
// SPDX-License-Identifier: MIT
8+
//
9+
10+
use plib::testing::{run_test, run_test_with_checker, TestPlan};
11+
use std::{fs, io::Write, os::unix::fs::MetadataExt, process::Output};
12+
13+
fn du_test(args: &[&str], expected_output: &str, expected_error: &str, expected_exit_code: i32) {
14+
let str_args: Vec<String> = args.iter().map(|s| String::from(*s)).collect();
15+
16+
run_test(TestPlan {
17+
cmd: String::from("du"),
18+
args: str_args,
19+
stdin_data: String::new(),
20+
expected_out: String::from(expected_output),
21+
expected_err: String::from(expected_error),
22+
expected_exit_code,
23+
});
24+
}
25+
26+
fn du_test2(args: &[&str], checker: impl FnMut(&TestPlan, &Output)) {
27+
let str_args: Vec<String> = args.iter().map(|s| String::from(*s)).collect();
28+
29+
run_test_with_checker(
30+
TestPlan {
31+
cmd: String::from("du"),
32+
args: str_args,
33+
stdin_data: String::new(),
34+
expected_out: String::new(),
35+
expected_err: String::new(),
36+
expected_exit_code: 0,
37+
},
38+
checker,
39+
);
40+
}
41+
42+
// Partial port of coreutils/tests/du/basic.sh
43+
// Omits the nonstandard --block-size and -S options
44+
#[test]
45+
fn test_du_basic() {
46+
let test_dir = &format!("{}/test_du_basic", env!("CARGO_TARGET_TMPDIR"));
47+
48+
// DEBUG
49+
let _ = fs::remove_dir_all(test_dir);
50+
51+
let a = &format!("{test_dir}/a");
52+
53+
let a_b = &format!("{test_dir}/a/b");
54+
let d = &format!("{test_dir}/d");
55+
let d_sub = &format!("{test_dir}/d/sub");
56+
57+
let a_b_f = &format!("{a_b}/F");
58+
let d_1 = &format!("{d}/1");
59+
let d_sub_2 = &format!("{d_sub}/2");
60+
61+
fs::create_dir(test_dir).unwrap();
62+
for dir in [a_b, d, d_sub] {
63+
fs::create_dir_all(dir).unwrap();
64+
}
65+
66+
{
67+
// Create a > 257 bytes file
68+
let mut file1 = fs::File::create(a_b_f).unwrap();
69+
write!(file1, "{:>257}", "x").unwrap();
70+
71+
// Create a 4 KiB file
72+
let mut file2 = fs::File::create(d_1).unwrap();
73+
write!(file2, "{:>4096}", "x").unwrap();
74+
75+
fs::copy(d_1, d_sub_2).unwrap();
76+
}
77+
78+
// Manually calculate the block sizes for directory a
79+
let [size_abf, mut size_ab, mut size_a] =
80+
[a_b_f, a_b, a].map(|filename| fs::metadata(filename).unwrap().blocks());
81+
size_ab += size_abf;
82+
size_a += size_ab;
83+
84+
du_test(
85+
&["-a", a],
86+
&format!(
87+
"{size_abf}\t{a_b_f}\
88+
\n{size_ab}\t{a_b}\
89+
\n{size_a}\t{a}\n"
90+
),
91+
"",
92+
0,
93+
);
94+
95+
du_test(&["-s", a], &format!("{size_a}\t{a}\n"), "", 0);
96+
97+
// Manually calculate the block sizes for directory d
98+
let [size_dsub2, mut size_dsub, size_d1, mut size_d] =
99+
[d_sub_2, d_sub, d_1, d].map(|filename| fs::metadata(filename).unwrap().blocks());
100+
size_dsub += size_dsub2;
101+
size_d += size_d1 + size_dsub;
102+
103+
du_test2(&["-a", d], |_, output| {
104+
// d/1 is not guaranteed to be listed after d/sub
105+
//
106+
// Case #1:
107+
// 8 d/sub/2
108+
// 8 d/sub
109+
// 8 d/1
110+
// 16 d
111+
//
112+
// Case #2:
113+
// 8 d/1
114+
// 8 d/sub/2
115+
// 8 d/sub
116+
// 16 d
117+
//
118+
// So instead, we compare the sorted lines to check for correctness
119+
120+
// Split the output wrt to new lines and then sort them.
121+
let split_then_sort = |s: String| -> Vec<String> {
122+
let mut lines: Vec<String> = s.lines().map(|s| s.to_string()).collect();
123+
lines.sort();
124+
lines
125+
};
126+
127+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
128+
129+
let expected = format!(
130+
"{size_dsub2}\t{d_sub_2}\
131+
\n{size_dsub}\t{d_sub}\
132+
\n{size_d1}\t{d_1}\
133+
\n{size_d}\t{d}\n"
134+
);
135+
136+
assert_eq!(split_then_sort(stdout), split_then_sort(expected));
137+
});
138+
139+
fs::remove_dir_all(test_dir).unwrap();
140+
}

tree/tests/tree-tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod chgrp;
1111
mod chmod;
1212
mod chown;
1313
mod cp;
14+
mod du;
1415
mod link;
1516
mod ls;
1617
mod mkdir;

0 commit comments

Comments
 (0)