1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

du: put excludes in traversaloptions and make size_format enum

This commit is contained in:
Terts Diepraam 2023-12-07 11:58:11 +01:00
parent d6b10d4d72
commit 7861559f88

View file

@ -83,25 +83,20 @@ struct TraversalOptions {
dereference: Deref, dereference: Deref,
count_links: bool, count_links: bool,
verbose: bool, verbose: bool,
excludes: Vec<Pattern>,
} }
#[derive(Clone)] struct StatPrinter {
struct PrintingOptions {
total: bool, total: bool,
inodes: bool, inodes: bool,
max_depth: Option<usize>, max_depth: Option<usize>,
threshold: Option<Threshold>, threshold: Option<Threshold>,
apparent_size: bool, apparent_size: bool,
// TODO: the size conversion fields should be unified size_format: SizeFormat,
si: bool,
bytes: bool,
human_readable: bool,
block_size_1k: bool,
block_size_1m: bool,
block_size: u64,
time: Option<Time>, time: Option<Time>,
time_format: String, time_format: String,
line_ending: LineEnding, line_ending: LineEnding,
summarize: bool,
} }
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
@ -118,6 +113,12 @@ enum Time {
Created, Created,
} }
#[derive(Clone)]
enum SizeFormat {
Human(u64),
BlockSize(u64),
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct FileInfo { struct FileInfo {
file_id: u128, file_id: u128,
@ -295,18 +296,6 @@ fn read_block_size(s: Option<&str>) -> UResult<u64> {
} }
} }
fn choose_size(options: &PrintingOptions, stat: &Stat) -> u64 {
if options.inodes {
stat.inodes
} else if options.apparent_size || options.bytes {
stat.size
} else {
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
}
}
// this takes `my_stat` to avoid having to stat files multiple times. // this takes `my_stat` to avoid having to stat files multiple times.
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn du( fn du(
@ -314,7 +303,6 @@ fn du(
options: &TraversalOptions, options: &TraversalOptions,
depth: usize, depth: usize,
seen_inodes: &mut HashSet<FileInfo>, seen_inodes: &mut HashSet<FileInfo>,
exclude: &[Pattern],
print_tx: &mpsc::Sender<UResult<StatPrintInfo>>, print_tx: &mpsc::Sender<UResult<StatPrintInfo>>,
) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> { ) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> {
if my_stat.is_dir { if my_stat.is_dir {
@ -334,7 +322,7 @@ fn du(
match Stat::new(&entry.path(), options) { match Stat::new(&entry.path(), options) {
Ok(this_stat) => { Ok(this_stat) => {
// We have an exclude list // We have an exclude list
for pattern in exclude { for pattern in &options.excludes {
// Look at all patterns with both short and long paths // Look at all patterns with both short and long paths
// if we have 'du foo' but search to exclude 'foo/bar' // if we have 'du foo' but search to exclude 'foo/bar'
// we need the full path // we need the full path
@ -370,14 +358,8 @@ fn du(
} }
} }
let this_stat = du( let this_stat =
this_stat, du(this_stat, options, depth + 1, seen_inodes, print_tx)?;
options,
depth + 1,
seen_inodes,
exclude,
print_tx,
)?;
if !options.separate_dirs { if !options.separate_dirs {
my_stat.size += this_stat.size; my_stat.size += this_stat.size;
@ -413,52 +395,6 @@ fn du(
Ok(my_stat) Ok(my_stat)
} }
fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
for &(unit, power) in &UNITS {
let limit = multiplier.pow(power);
if size >= limit {
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
}
}
if size == 0 {
return "0".to_string();
}
format!("{size}B")
}
fn convert_size_b(size: u64, _multiplier: u64, _block_size: u64) -> String {
format!("{}", ((size as f64) / (1_f64)).ceil())
}
fn convert_size_k(size: u64, multiplier: u64, _block_size: u64) -> String {
format!("{}", ((size as f64) / (multiplier as f64)).ceil())
}
fn convert_size_m(size: u64, multiplier: u64, _block_size: u64) -> String {
format!(
"{}",
((size as f64) / ((multiplier * multiplier) as f64)).ceil()
)
}
fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String {
format!("{}", ((size as f64) / (block_size as f64)).ceil())
}
fn get_convert_size_fn(options: &PrintingOptions) -> Box<dyn Fn(u64, u64, u64) -> String + Send> {
if options.human_readable || options.si {
Box::new(convert_size_human)
} else if options.bytes {
Box::new(convert_size_b)
} else if options.block_size_1k {
Box::new(convert_size_k)
} else if options.block_size_1m {
Box::new(convert_size_m)
} else {
Box::new(convert_size_other)
}
}
#[derive(Debug)] #[derive(Debug)]
enum DuError { enum DuError {
InvalidMaxDepthArg(String), InvalidMaxDepthArg(String),
@ -554,29 +490,17 @@ struct StatPrintInfo {
depth: usize, depth: usize,
} }
struct StatPrinter {
summarize: bool,
options: PrintingOptions,
convert_size: Box<dyn Fn(u64) -> String + Send>,
}
impl StatPrinter { impl StatPrinter {
fn new(options: PrintingOptions, summarize: bool) -> UResult<Self> { fn choose_size(&self, stat: &Stat) -> u64 {
let multiplier: u64 = if options.si { 1000 } else { 1024 }; if self.inodes {
stat.inodes
let convert_size_fn = get_convert_size_fn(&options); } else if self.apparent_size {
stat.size
let convert_size: Box<dyn Fn(u64) -> String + Send> = if options.inodes {
Box::new(|size: u64| size.to_string())
} else { } else {
Box::new(move |size: u64| convert_size_fn(size, multiplier, options.block_size)) // The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
}; // See: http://linux.die.net/man/2/stat
stat.blocks * 512
Ok(Self { }
summarize,
options,
convert_size,
})
} }
fn print_stats(&self, rx: &mpsc::Receiver<UResult<StatPrintInfo>>) -> UResult<()> { fn print_stats(&self, rx: &mpsc::Receiver<UResult<StatPrintInfo>>) -> UResult<()> {
@ -587,18 +511,16 @@ impl StatPrinter {
match received { match received {
Ok(message) => match message { Ok(message) => match message {
Ok(stat_info) => { Ok(stat_info) => {
let size = choose_size(&self.options, &stat_info.stat); let size = self.choose_size(&stat_info.stat);
if stat_info.depth == 0 { if stat_info.depth == 0 {
grand_total += size; grand_total += size;
} }
if !self if !self
.options
.threshold .threshold
.map_or(false, |threshold| threshold.should_exclude(size)) .map_or(false, |threshold| threshold.should_exclude(size))
&& self && self
.options
.max_depth .max_depth
.map_or(true, |max_depth| stat_info.depth <= max_depth) .map_or(true, |max_depth| stat_info.depth <= max_depth)
&& (!self.summarize || stat_info.depth == 0) && (!self.summarize || stat_info.depth == 0)
@ -612,31 +534,59 @@ impl StatPrinter {
} }
} }
if self.options.total { if self.total {
print!("{}\ttotal", (self.convert_size)(grand_total)); print!("{}\ttotal", self.convert_size(grand_total));
print!("{}", self.options.line_ending); print!("{}", self.line_ending);
} }
Ok(()) Ok(())
} }
fn convert_size(&self, size: u64) -> String {
if self.inodes {
return size.to_string();
}
match self.size_format {
SizeFormat::Human(multiplier) => {
if size == 0 {
return "0".to_string();
}
for &(unit, power) in &UNITS {
let limit = multiplier.pow(power);
if size >= limit {
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
}
}
format!("{size}B")
}
SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(),
}
}
fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> { fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
if let Some(time) = self.options.time { if let Some(time) = self.time {
let secs = get_time_secs(time, stat)?; let secs = get_time_secs(time, stat)?;
let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs)); let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs));
let time_str = tm.format(&self.options.time_format).to_string(); let time_str = tm.format(&self.time_format).to_string();
print!("{}\t{}\t", (self.convert_size)(size), time_str); print!("{}\t{}\t", self.convert_size(size), time_str);
} else { } else {
print!("{}\t", (self.convert_size)(size)); print!("{}\t", self.convert_size(size));
} }
print_verbatim(&stat.path).unwrap(); print_verbatim(&stat.path).unwrap();
print!("{}", self.options.line_ending); print!("{}", self.line_ending);
Ok(()) Ok(())
} }
} }
// This can be replaced with u64::div_ceil once it is stabilized.
// This implementation approach is optimized for when `b` is a constant,
// particularly a power of two.
pub fn div_ceil(a: u64, b: u64) -> u64 {
(a + b - 1) / b
}
#[uucore::main] #[uucore::main]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@ -671,11 +621,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
}); });
let block_size = read_block_size( let size_format = if matches.get_flag(options::HUMAN_READABLE) {
matches SizeFormat::Human(1024)
.get_one::<String>(options::BLOCK_SIZE) } else if matches.get_flag(options::SI) {
.map(|s| s.as_str()), SizeFormat::Human(1000)
)?; } else if matches.get_flag(options::BYTES) {
SizeFormat::BlockSize(1)
} else if matches.get_flag(options::BLOCK_SIZE_1K) {
SizeFormat::BlockSize(1024)
} else if matches.get_flag(options::BLOCK_SIZE_1M) {
SizeFormat::BlockSize(1024 * 1024)
} else {
SizeFormat::BlockSize(read_block_size(
matches
.get_one::<String>(options::BLOCK_SIZE)
.map(AsRef::as_ref),
)?)
};
let traversal_options = TraversalOptions { let traversal_options = TraversalOptions {
all: matches.get_flag(options::ALL), all: matches.get_flag(options::ALL),
@ -691,13 +653,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}, },
count_links: matches.get_flag(options::COUNT_LINKS), count_links: matches.get_flag(options::COUNT_LINKS),
verbose: matches.get_flag(options::VERBOSE), verbose: matches.get_flag(options::VERBOSE),
excludes: build_exclude_patterns(&matches)?,
}; };
let printing_options = PrintingOptions { let stat_printer = StatPrinter {
max_depth, max_depth,
size_format,
summarize,
total: matches.get_flag(options::TOTAL), total: matches.get_flag(options::TOTAL),
inodes: matches.get_flag(options::INODES), inodes: matches.get_flag(options::INODES),
si: matches.get_flag(options::SI),
threshold: matches threshold: matches
.get_one::<String>(options::THRESHOLD) .get_one::<String>(options::THRESHOLD)
.map(|s| { .map(|s| {
@ -706,36 +670,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}) })
}) })
.transpose()?, .transpose()?,
apparent_size: matches.get_flag(options::APPARENT_SIZE), apparent_size: matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES),
bytes: matches.get_flag(options::BYTES),
time, time,
block_size,
human_readable: matches.get_flag(options::HUMAN_READABLE),
block_size_1k: matches.get_flag(options::BLOCK_SIZE_1K),
block_size_1m: matches.get_flag(options::BLOCK_SIZE_1M),
time_format: parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))? time_format: parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?
.to_string(), .to_string(),
line_ending: LineEnding::from_zero_flag(matches.get_flag(options::NULL)), line_ending: LineEnding::from_zero_flag(matches.get_flag(options::NULL)),
}; };
if printing_options.inodes if stat_printer.inodes
&& (matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES)) && (matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES))
{ {
show_warning!("options --apparent-size and -b are ineffective with --inodes"); show_warning!("options --apparent-size and -b are ineffective with --inodes");
} }
// Use separate thread to print output, so we can print finished results while computation is still running // Use separate thread to print output, so we can print finished results while computation is still running
let stat_printer = StatPrinter::new(printing_options.clone(), summarize)?;
let (print_tx, rx) = mpsc::channel::<UResult<StatPrintInfo>>(); let (print_tx, rx) = mpsc::channel::<UResult<StatPrintInfo>>();
let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx)); let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx));
let excludes = build_exclude_patterns(&matches)?;
'loop_file: for path in files { 'loop_file: for path in files {
// Skip if we don't want to ignore anything // Skip if we don't want to ignore anything
if !&excludes.is_empty() { if !&traversal_options.excludes.is_empty() {
let path_string = path.to_string_lossy(); let path_string = path.to_string_lossy();
for pattern in &excludes { for pattern in &traversal_options.excludes {
if pattern.matches(&path_string) { if pattern.matches(&path_string) {
// if the directory is ignored, leave early // if the directory is ignored, leave early
if traversal_options.verbose { if traversal_options.verbose {
@ -753,15 +709,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if let Some(inode) = stat.inode { if let Some(inode) = stat.inode {
seen_inodes.insert(inode); seen_inodes.insert(inode);
} }
let stat = du( let stat = du(stat, &traversal_options, 0, &mut seen_inodes, &print_tx)
stat, .map_err(|e| USimpleError::new(1, e.to_string()))?;
&traversal_options,
0,
&mut seen_inodes,
&excludes,
&print_tx,
)
.map_err(|e| USimpleError::new(1, e.to_string()))?;
print_tx print_tx
.send(Ok(StatPrintInfo { stat, depth: 0 })) .send(Ok(StatPrintInfo { stat, depth: 0 }))