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:
parent
d6b10d4d72
commit
7861559f88
1 changed files with 86 additions and 137 deletions
|
@ -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) {
|
||||||
|
SizeFormat::Human(1024)
|
||||||
|
} else if matches.get_flag(options::SI) {
|
||||||
|
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
|
matches
|
||||||
.get_one::<String>(options::BLOCK_SIZE)
|
.get_one::<String>(options::BLOCK_SIZE)
|
||||||
.map(|s| s.as_str()),
|
.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,14 +709,7 @@ 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,
|
|
||||||
&traversal_options,
|
|
||||||
0,
|
|
||||||
&mut seen_inodes,
|
|
||||||
&excludes,
|
|
||||||
&print_tx,
|
|
||||||
)
|
|
||||||
.map_err(|e| USimpleError::new(1, e.to_string()))?;
|
.map_err(|e| USimpleError::new(1, e.to_string()))?;
|
||||||
|
|
||||||
print_tx
|
print_tx
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue