mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2026-01-19 11:41:10 +00:00
ls: skip reading metadata
This commit is contained in:
parent
8554cdf35b
commit
e241f3ad69
1 changed files with 93 additions and 83 deletions
|
|
@ -19,11 +19,11 @@ mod version_cmp;
|
|||
use clap::{App, Arg};
|
||||
use globset::{self, Glob, GlobSet, GlobSetBuilder};
|
||||
use number_prefix::NumberPrefix;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use quoting_style::{escape_name, QuotingStyle};
|
||||
#[cfg(unix)]
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::fs::{DirEntry, FileType, Metadata};
|
||||
use std::fs::{self, DirEntry, FileType, Metadata};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
|
|
@ -492,6 +492,10 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
if files != Files::Normal {
|
||||
ignore_patterns.add(Glob::new(".*").unwrap());
|
||||
}
|
||||
|
||||
let ignore_patterns = ignore_patterns.build().unwrap();
|
||||
|
||||
let dereference = if options.is_present(options::dereference::ALL) {
|
||||
|
|
@ -1043,29 +1047,66 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
/// Caching data here helps eliminate redundant syscalls to fetch same information
|
||||
struct PathData {
|
||||
// Result<MetaData> got from symlink_metadata() or metadata() based on config
|
||||
md: std::io::Result<Metadata>,
|
||||
// String formed from get_lossy_string() for the path
|
||||
lossy_string: String,
|
||||
md: OnceCell<Option<Metadata>>,
|
||||
ft: OnceCell<Option<FileType>>,
|
||||
// Name of the file - will be empty for . or ..
|
||||
file_name: String,
|
||||
// PathBuf that all above data corresponds to
|
||||
p_buf: PathBuf,
|
||||
must_dereference: bool,
|
||||
}
|
||||
|
||||
impl PathData {
|
||||
fn new(p_buf: PathBuf, config: &Config, command_line: bool) -> Self {
|
||||
let md = get_metadata(&p_buf, config, command_line);
|
||||
let lossy_string = p_buf.to_string_lossy().into_owned();
|
||||
fn new(
|
||||
p_buf: PathBuf,
|
||||
file_type: Option<std::io::Result<FileType>>,
|
||||
config: &Config,
|
||||
command_line: bool,
|
||||
) -> Self {
|
||||
let name = p_buf
|
||||
.file_name()
|
||||
.map_or(String::new(), |s| s.to_string_lossy().into_owned());
|
||||
let must_dereference = match &config.dereference {
|
||||
Dereference::All => true,
|
||||
Dereference::Args => command_line,
|
||||
Dereference::DirArgs => {
|
||||
if command_line {
|
||||
if let Ok(md) = p_buf.metadata() {
|
||||
md.is_dir()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Dereference::None => false,
|
||||
};
|
||||
let ft = match file_type {
|
||||
Some(ft) => OnceCell::from(ft.ok()),
|
||||
None => OnceCell::new(),
|
||||
};
|
||||
|
||||
Self {
|
||||
md,
|
||||
lossy_string,
|
||||
md: OnceCell::new(),
|
||||
ft,
|
||||
file_name: name,
|
||||
p_buf,
|
||||
must_dereference,
|
||||
}
|
||||
}
|
||||
|
||||
fn md(&self) -> Option<&Metadata> {
|
||||
self.md
|
||||
.get_or_init(|| get_metadata(&self.p_buf, self.must_dereference).ok())
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
fn file_type(&self) -> Option<&FileType> {
|
||||
self.ft
|
||||
.get_or_init(|| self.md().map(|md| md.file_type()))
|
||||
.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||
|
|
@ -1085,10 +1126,10 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
|||
continue;
|
||||
}
|
||||
|
||||
let path_data = PathData::new(p, &config, true);
|
||||
let path_data = PathData::new(p, None, &config, true);
|
||||
|
||||
let show_dir_contents = if let Ok(md) = path_data.md.as_ref() {
|
||||
!config.directory && md.is_dir()
|
||||
let show_dir_contents = if let Some(ft) = path_data.file_type() {
|
||||
!config.directory && ft.is_dir()
|
||||
} else {
|
||||
has_failed = true;
|
||||
false
|
||||
|
|
@ -1106,7 +1147,7 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
|||
sort_entries(&mut dirs, &config);
|
||||
for dir in dirs {
|
||||
if number_of_locs > 1 {
|
||||
println!("\n{}:", dir.lossy_string);
|
||||
println!("\n{}:", dir.p_buf.display());
|
||||
}
|
||||
enter_directory(&dir, &config);
|
||||
}
|
||||
|
|
@ -1121,17 +1162,16 @@ fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
|||
match config.sort {
|
||||
Sort::Time => entries.sort_by_key(|k| {
|
||||
Reverse(
|
||||
k.md.as_ref()
|
||||
.ok()
|
||||
k.md()
|
||||
.and_then(|md| get_system_time(&md, config))
|
||||
.unwrap_or(UNIX_EPOCH),
|
||||
)
|
||||
}),
|
||||
Sort::Size => {
|
||||
entries.sort_by_key(|k| Reverse(k.md.as_ref().map(|md| md.len()).unwrap_or(0)))
|
||||
entries.sort_by_key(|k| Reverse(k.md().as_ref().map(|md| md.len()).unwrap_or(0)))
|
||||
}
|
||||
// The default sort in GNU ls is case insensitive
|
||||
Sort::Name => entries.sort_by_key(|k| k.file_name.to_lowercase()),
|
||||
Sort::Name => entries.sort_by_key(|k| k.file_name.to_ascii_lowercase()),
|
||||
Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)),
|
||||
Sort::None => {}
|
||||
}
|
||||
|
|
@ -1169,8 +1209,8 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
|
|||
fn enter_directory(dir: &PathData, config: &Config) {
|
||||
let mut entries: Vec<_> = if config.files == Files::All {
|
||||
vec![
|
||||
PathData::new(dir.p_buf.join("."), config, false),
|
||||
PathData::new(dir.p_buf.join(".."), config, false),
|
||||
PathData::new(dir.p_buf.join("."), None, config, false),
|
||||
PathData::new(dir.p_buf.join(".."), None, config, false),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
|
|
@ -1179,7 +1219,7 @@ fn enter_directory(dir: &PathData, config: &Config) {
|
|||
let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf))
|
||||
.map(|res| safe_unwrap!(res))
|
||||
.filter(|e| should_display(e, config))
|
||||
.map(|e| PathData::new(DirEntry::path(&e), config, false))
|
||||
.map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), config, false))
|
||||
.collect();
|
||||
|
||||
sort_entries(&mut temp, config);
|
||||
|
|
@ -1192,31 +1232,15 @@ fn enter_directory(dir: &PathData, config: &Config) {
|
|||
for e in entries
|
||||
.iter()
|
||||
.skip(if config.files == Files::All { 2 } else { 0 })
|
||||
.filter(|p| p.md.as_ref().map(|md| md.is_dir()).unwrap_or(false))
|
||||
.filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
|
||||
{
|
||||
println!("\n{}:", e.lossy_string);
|
||||
println!("\n{}:", e.p_buf.display());
|
||||
enter_directory(&e, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_metadata(entry: &Path, config: &Config, command_line: bool) -> std::io::Result<Metadata> {
|
||||
let dereference = match &config.dereference {
|
||||
Dereference::All => true,
|
||||
Dereference::Args => command_line,
|
||||
Dereference::DirArgs => {
|
||||
if command_line {
|
||||
if let Ok(md) = entry.metadata() {
|
||||
md.is_dir()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Dereference::None => false,
|
||||
};
|
||||
fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
|
||||
if dereference {
|
||||
entry.metadata().or_else(|_| entry.symlink_metadata())
|
||||
} else {
|
||||
|
|
@ -1225,7 +1249,7 @@ fn get_metadata(entry: &Path, config: &Config, command_line: bool) -> std::io::R
|
|||
}
|
||||
|
||||
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
|
||||
if let Ok(md) = entry.md.as_ref() {
|
||||
if let Some(md) = entry.md() {
|
||||
(
|
||||
display_symlink_count(&md).len(),
|
||||
display_file_size(&md, config).len(),
|
||||
|
|
@ -1251,17 +1275,9 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) {
|
|||
display_item_long(item, strip, max_links, max_size, config);
|
||||
}
|
||||
} else {
|
||||
let names = items.iter().filter_map(|i| {
|
||||
let md = i.md.as_ref();
|
||||
match md {
|
||||
Err(e) => {
|
||||
let filename = get_file_name(&i.p_buf, strip);
|
||||
show_error!("'{}': {}", filename, e);
|
||||
None
|
||||
}
|
||||
Ok(md) => Some(display_file_name(&i.p_buf, strip, &md, config)),
|
||||
}
|
||||
});
|
||||
let names = items
|
||||
.iter()
|
||||
.filter_map(|i| display_file_name(&i, strip, config));
|
||||
|
||||
match (&config.format, config.width) {
|
||||
(Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom),
|
||||
|
|
@ -1325,13 +1341,13 @@ fn display_item_long(
|
|||
max_size: usize,
|
||||
config: &Config,
|
||||
) {
|
||||
let md = match &item.md {
|
||||
Err(e) => {
|
||||
let md = match item.md() {
|
||||
None => {
|
||||
let filename = get_file_name(&item.p_buf, strip);
|
||||
show_error!("{}: {}", filename, e);
|
||||
show_error!("could not show file: {}", filename);
|
||||
return;
|
||||
}
|
||||
Ok(md) => md,
|
||||
Some(md) => md,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
@ -1366,7 +1382,10 @@ fn display_item_long(
|
|||
" {} {} {}",
|
||||
pad_left(display_file_size(&md, config), max_size),
|
||||
display_date(&md, config),
|
||||
display_file_name(&item.p_buf, strip, &md, config).contents,
|
||||
// unwrap is fine because it fails when metadata is not available
|
||||
// but we already know that it is because it's checked at the
|
||||
// start of the function.
|
||||
display_file_name(&item, strip, config).unwrap().contents,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1537,14 +1556,9 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
|
|||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn display_file_name(
|
||||
path: &Path,
|
||||
strip: Option<&Path>,
|
||||
metadata: &Metadata,
|
||||
config: &Config,
|
||||
) -> Cell {
|
||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
||||
let file_type = metadata.file_type();
|
||||
fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option<Cell> {
|
||||
let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style);
|
||||
let file_type = path.file_type()?;
|
||||
|
||||
match config.indicator_style {
|
||||
IndicatorStyle::Classify | IndicatorStyle::FileType => {
|
||||
|
|
@ -1563,8 +1577,8 @@ fn display_file_name(
|
|||
_ => (),
|
||||
};
|
||||
|
||||
if config.format == Format::Long && metadata.file_type().is_symlink() {
|
||||
if let Ok(target) = path.read_link() {
|
||||
if config.format == Format::Long && path.file_type()?.is_symlink() {
|
||||
if let Ok(target) = path.p_buf.read_link() {
|
||||
// We don't bother updating width here because it's not used for long listings
|
||||
let target_name = target.to_string_lossy().to_string();
|
||||
name.push_str(" -> ");
|
||||
|
|
@ -1572,7 +1586,7 @@ fn display_file_name(
|
|||
}
|
||||
}
|
||||
|
||||
name.into()
|
||||
Some(name.into())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
@ -1604,26 +1618,22 @@ macro_rules! has {
|
|||
|
||||
#[cfg(unix)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn display_file_name(
|
||||
path: &Path,
|
||||
strip: Option<&Path>,
|
||||
metadata: &Metadata,
|
||||
config: &Config,
|
||||
) -> Cell {
|
||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
||||
fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option<Cell> {
|
||||
let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style);
|
||||
if config.format != Format::Long && config.inode {
|
||||
name = get_inode(metadata) + " " + &name;
|
||||
name = get_inode(path.md()?) + " " + &name;
|
||||
}
|
||||
let mut width = UnicodeWidthStr::width(&*name);
|
||||
|
||||
let ext;
|
||||
if config.color || config.indicator_style != IndicatorStyle::None {
|
||||
let file_type = metadata.file_type();
|
||||
let metadata = path.md()?;
|
||||
let file_type = path.file_type()?;
|
||||
|
||||
let (code, sym) = if file_type.is_dir() {
|
||||
("di", Some('/'))
|
||||
} else if file_type.is_symlink() {
|
||||
if path.exists() {
|
||||
if path.p_buf.exists() {
|
||||
("ln", Some('@'))
|
||||
} else {
|
||||
("or", Some('@'))
|
||||
|
|
@ -1657,7 +1667,7 @@ fn display_file_name(
|
|||
("ex", sym)
|
||||
} else if metadata.nlink() > 1 {
|
||||
("mh", sym)
|
||||
} else if let Some(e) = path.extension() {
|
||||
} else if let Some(e) = path.p_buf.extension() {
|
||||
ext = format!("*.{}", e.to_string_lossy());
|
||||
(ext.as_str(), None)
|
||||
} else {
|
||||
|
|
@ -1696,8 +1706,8 @@ fn display_file_name(
|
|||
}
|
||||
}
|
||||
|
||||
if config.format == Format::Long && metadata.file_type().is_symlink() {
|
||||
if let Ok(target) = path.read_link() {
|
||||
if config.format == Format::Long && path.file_type()?.is_symlink() {
|
||||
if let Ok(target) = path.p_buf.read_link() {
|
||||
// We don't bother updating width here because it's not used for long listings
|
||||
let code = if target.exists() { "fi" } else { "mi" };
|
||||
let target_name = color_name(target.to_string_lossy().to_string(), code);
|
||||
|
|
@ -1706,10 +1716,10 @@ fn display_file_name(
|
|||
}
|
||||
}
|
||||
|
||||
Cell {
|
||||
Some(Cell {
|
||||
contents: name,
|
||||
width,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue