mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 23:02:38 +00:00
Merge pull request #5660 from sylvestre/stat-free-color
ls: Improve the access to metadata of the files
This commit is contained in:
commit
6475e6f148
2 changed files with 87 additions and 53 deletions
|
@ -1812,6 +1812,8 @@ struct PathData {
|
||||||
// Result<MetaData> got from symlink_metadata() or metadata() based on config
|
// Result<MetaData> got from symlink_metadata() or metadata() based on config
|
||||||
md: OnceCell<Option<Metadata>>,
|
md: OnceCell<Option<Metadata>>,
|
||||||
ft: OnceCell<Option<FileType>>,
|
ft: OnceCell<Option<FileType>>,
|
||||||
|
// can be used to avoid reading the metadata. Can be also called d_type:
|
||||||
|
// https://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html
|
||||||
de: Option<DirEntry>,
|
de: Option<DirEntry>,
|
||||||
// Name of the file - will be empty for . or ..
|
// Name of the file - will be empty for . or ..
|
||||||
display_name: OsString,
|
display_name: OsString,
|
||||||
|
@ -1907,10 +1909,11 @@ impl PathData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn md(&self, out: &mut BufWriter<Stdout>) -> Option<&Metadata> {
|
fn get_metadata(&self, out: &mut BufWriter<Stdout>) -> Option<&Metadata> {
|
||||||
self.md
|
self.md
|
||||||
.get_or_init(|| {
|
.get_or_init(|| {
|
||||||
// check if we can use DirEntry metadata
|
// check if we can use DirEntry metadata
|
||||||
|
// it will avoid a call to stat()
|
||||||
if !self.must_dereference {
|
if !self.must_dereference {
|
||||||
if let Some(dir_entry) = &self.de {
|
if let Some(dir_entry) = &self.de {
|
||||||
return dir_entry.metadata().ok();
|
return dir_entry.metadata().ok();
|
||||||
|
@ -1918,7 +1921,7 @@ impl PathData {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not, check if we can use Path metadata
|
// if not, check if we can use Path metadata
|
||||||
match get_metadata(self.p_buf.as_path(), self.must_dereference) {
|
match get_metadata_with_deref_opt(self.p_buf.as_path(), self.must_dereference) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// FIXME: A bit tricky to propagate the result here
|
// FIXME: A bit tricky to propagate the result here
|
||||||
out.flush().unwrap();
|
out.flush().unwrap();
|
||||||
|
@ -1947,7 +1950,7 @@ impl PathData {
|
||||||
|
|
||||||
fn file_type(&self, out: &mut BufWriter<Stdout>) -> Option<&FileType> {
|
fn file_type(&self, out: &mut BufWriter<Stdout>) -> Option<&FileType> {
|
||||||
self.ft
|
self.ft
|
||||||
.get_or_init(|| self.md(out).map(|md| md.file_type()))
|
.get_or_init(|| self.get_metadata(out).map(|md| md.file_type()))
|
||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1980,7 +1983,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
||||||
// Proper GNU handling is don't show if dereferenced symlink DNE
|
// Proper GNU handling is don't show if dereferenced symlink DNE
|
||||||
// but only for the base dir, for a child dir show, and print ?s
|
// but only for the base dir, for a child dir show, and print ?s
|
||||||
// in long format
|
// in long format
|
||||||
if path_data.md(&mut out).is_none() {
|
if path_data.get_metadata(&mut out).is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2068,12 +2071,14 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter<S
|
||||||
match config.sort {
|
match config.sort {
|
||||||
Sort::Time => entries.sort_by_key(|k| {
|
Sort::Time => entries.sort_by_key(|k| {
|
||||||
Reverse(
|
Reverse(
|
||||||
k.md(out)
|
k.get_metadata(out)
|
||||||
.and_then(|md| get_system_time(md, config))
|
.and_then(|md| get_system_time(md, config))
|
||||||
.unwrap_or(UNIX_EPOCH),
|
.unwrap_or(UNIX_EPOCH),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Sort::Size => entries.sort_by_key(|k| Reverse(k.md(out).map(|md| md.len()).unwrap_or(0))),
|
Sort::Size => {
|
||||||
|
entries.sort_by_key(|k| Reverse(k.get_metadata(out).map(|md| md.len()).unwrap_or(0)));
|
||||||
|
}
|
||||||
// The default sort in GNU ls is case insensitive
|
// The default sort in GNU ls is case insensitive
|
||||||
Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)),
|
Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)),
|
||||||
Sort::Version => entries.sort_by(|a, b| {
|
Sort::Version => entries.sort_by(|a, b| {
|
||||||
|
@ -2114,7 +2119,8 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter<S
|
||||||
!match md {
|
!match md {
|
||||||
None | Some(None) => {
|
None | Some(None) => {
|
||||||
// If it metadata cannot be determined, treat as a file.
|
// If it metadata cannot be determined, treat as a file.
|
||||||
get_metadata(p.p_buf.as_path(), true).map_or_else(|_| false, |m| m.is_dir())
|
get_metadata_with_deref_opt(p.p_buf.as_path(), true)
|
||||||
|
.map_or_else(|_| false, |m| m.is_dir())
|
||||||
}
|
}
|
||||||
Some(Some(m)) => m.is_dir(),
|
Some(Some(m)) => m.is_dir(),
|
||||||
}
|
}
|
||||||
|
@ -2289,7 +2295,7 @@ fn enter_directory(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_metadata(p_buf: &Path, dereference: bool) -> std::io::Result<Metadata> {
|
fn get_metadata_with_deref_opt(p_buf: &Path, dereference: bool) -> std::io::Result<Metadata> {
|
||||||
if dereference {
|
if dereference {
|
||||||
p_buf.metadata()
|
p_buf.metadata()
|
||||||
} else {
|
} else {
|
||||||
|
@ -2303,7 +2309,7 @@ fn display_dir_entry_size(
|
||||||
out: &mut BufWriter<std::io::Stdout>,
|
out: &mut BufWriter<std::io::Stdout>,
|
||||||
) -> (usize, usize, usize, usize, usize, usize) {
|
) -> (usize, usize, usize, usize, usize, usize) {
|
||||||
// TODO: Cache/memorize the display_* results so we don't have to recalculate them.
|
// TODO: Cache/memorize the display_* results so we don't have to recalculate them.
|
||||||
if let Some(md) = entry.md(out) {
|
if let Some(md) = entry.get_metadata(out) {
|
||||||
let (size_len, major_len, minor_len) = match display_len_or_rdev(md, config) {
|
let (size_len, major_len, minor_len) = match display_len_or_rdev(md, config) {
|
||||||
SizeOrDeviceId::Device(major, minor) => (
|
SizeOrDeviceId::Device(major, minor) => (
|
||||||
(major.len() + minor.len() + 2usize),
|
(major.len() + minor.len() + 2usize),
|
||||||
|
@ -2341,7 +2347,7 @@ fn return_total(
|
||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
for item in items {
|
for item in items {
|
||||||
total_size += item
|
total_size += item
|
||||||
.md(out)
|
.get_metadata(out)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |md| get_block_size(md, config));
|
.map_or(0, |md| get_block_size(md, config));
|
||||||
}
|
}
|
||||||
|
@ -2365,7 +2371,7 @@ fn display_additional_leading_info(
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
if config.inode {
|
if config.inode {
|
||||||
let i = if let Some(md) = item.md(out) {
|
let i = if let Some(md) = item.get_metadata(out) {
|
||||||
get_inode(md)
|
get_inode(md)
|
||||||
} else {
|
} else {
|
||||||
"?".to_owned()
|
"?".to_owned()
|
||||||
|
@ -2375,7 +2381,7 @@ fn display_additional_leading_info(
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.alloc_size {
|
if config.alloc_size {
|
||||||
let s = if let Some(md) = item.md(out) {
|
let s = if let Some(md) = item.get_metadata(out) {
|
||||||
display_size(get_block_size(md, config), config)
|
display_size(get_block_size(md, config), config)
|
||||||
} else {
|
} else {
|
||||||
"?".to_owned()
|
"?".to_owned()
|
||||||
|
@ -2590,7 +2596,7 @@ fn display_item_long(
|
||||||
if config.dired {
|
if config.dired {
|
||||||
output_display += " ";
|
output_display += " ";
|
||||||
}
|
}
|
||||||
if let Some(md) = item.md(out) {
|
if let Some(md) = item.get_metadata(out) {
|
||||||
write!(
|
write!(
|
||||||
output_display,
|
output_display,
|
||||||
"{}{} {}",
|
"{}{} {}",
|
||||||
|
@ -3017,7 +3023,7 @@ fn classify_file(path: &PathData, out: &mut BufWriter<Stdout>) -> Option<char> {
|
||||||
} else if file_type.is_file()
|
} else if file_type.is_file()
|
||||||
// Safe unwrapping if the file was removed between listing and display
|
// Safe unwrapping if the file was removed between listing and display
|
||||||
// See https://github.com/uutils/coreutils/issues/5371
|
// See https://github.com/uutils/coreutils/issues/5371
|
||||||
&& path.md(out).map(file_is_executable).unwrap_or_default()
|
&& path.get_metadata(out).map(file_is_executable).unwrap_or_default()
|
||||||
{
|
{
|
||||||
Some('*')
|
Some('*')
|
||||||
} else {
|
} else {
|
||||||
|
@ -3064,18 +3070,7 @@ fn display_item_name(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ls_colors) = &config.color {
|
if let Some(ls_colors) = &config.color {
|
||||||
let md = path.md(out);
|
name = color_name(name, path, ls_colors, style_manager, out, None);
|
||||||
name = if md.is_some() {
|
|
||||||
color_name(name, &path.p_buf, md, ls_colors, style_manager)
|
|
||||||
} else {
|
|
||||||
color_name(
|
|
||||||
name,
|
|
||||||
&path.p_buf,
|
|
||||||
path.p_buf.symlink_metadata().ok().as_ref(),
|
|
||||||
ls_colors,
|
|
||||||
style_manager,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.format != Format::Long && !more_info.is_empty() {
|
if config.format != Format::Long && !more_info.is_empty() {
|
||||||
|
@ -3141,28 +3136,22 @@ fn display_item_name(
|
||||||
// Because we use an absolute path, we can assume this is guaranteed to exist.
|
// Because we use an absolute path, we can assume this is guaranteed to exist.
|
||||||
// Otherwise, we use path.md(), which will guarantee we color to the same
|
// Otherwise, we use path.md(), which will guarantee we color to the same
|
||||||
// color of non-existent symlinks according to style_for_path_with_metadata.
|
// color of non-existent symlinks according to style_for_path_with_metadata.
|
||||||
if path.md(out).is_none()
|
if path.get_metadata(out).is_none()
|
||||||
&& get_metadata(target_data.p_buf.as_path(), target_data.must_dereference)
|
&& get_metadata_with_deref_opt(
|
||||||
.is_err()
|
target_data.p_buf.as_path(),
|
||||||
|
target_data.must_dereference,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
{
|
{
|
||||||
name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy());
|
name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy());
|
||||||
} else {
|
} else {
|
||||||
// Use fn get_metadata instead of md() here and above because ls
|
|
||||||
// should not exit with an err, if we are unable to obtain the target_metadata
|
|
||||||
let target_metadata = match get_metadata(
|
|
||||||
target_data.p_buf.as_path(),
|
|
||||||
target_data.must_dereference,
|
|
||||||
) {
|
|
||||||
Ok(md) => md,
|
|
||||||
Err(_) => path.md(out).unwrap().clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
name.push_str(&color_name(
|
name.push_str(&color_name(
|
||||||
escape_name(target.as_os_str(), &config.quoting_style),
|
escape_name(target.as_os_str(), &config.quoting_style),
|
||||||
&target_data.p_buf,
|
path,
|
||||||
Some(&target_metadata),
|
|
||||||
ls_colors,
|
ls_colors,
|
||||||
style_manager,
|
style_manager,
|
||||||
|
out,
|
||||||
|
Some(&target_data),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -3259,17 +3248,56 @@ impl StyleManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Colors the provided name based on the style determined for the given path.
|
fn apply_style_based_on_metadata(
|
||||||
fn color_name(
|
path: &PathData,
|
||||||
name: String,
|
md_option: Option<&Metadata>,
|
||||||
path: &Path,
|
|
||||||
md: Option<&Metadata>,
|
|
||||||
ls_colors: &LsColors,
|
ls_colors: &LsColors,
|
||||||
style_manager: &mut StyleManager,
|
style_manager: &mut StyleManager,
|
||||||
|
name: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
match ls_colors.style_for_path_with_metadata(path, md) {
|
match ls_colors.style_for_path_with_metadata(&path.p_buf, md_option) {
|
||||||
Some(style) => style_manager.apply_style(style, &name),
|
Some(style) => style_manager.apply_style(style, name),
|
||||||
None => name,
|
None => name.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Colors the provided name based on the style determined for the given path
|
||||||
|
/// This function is quite long because it tries to leverage DirEntry to avoid
|
||||||
|
/// unnecessary calls to stat()
|
||||||
|
/// and manages the symlink errors
|
||||||
|
fn color_name(
|
||||||
|
name: String,
|
||||||
|
path: &PathData,
|
||||||
|
ls_colors: &LsColors,
|
||||||
|
style_manager: &mut StyleManager,
|
||||||
|
out: &mut BufWriter<Stdout>,
|
||||||
|
target_symlink: Option<&PathData>,
|
||||||
|
) -> String {
|
||||||
|
if !path.must_dereference {
|
||||||
|
// If we need to dereference (follow) a symlink, we will need to get the metadata
|
||||||
|
if let Some(de) = &path.de {
|
||||||
|
// There is a DirEntry, we don't need to get the metadata for the color
|
||||||
|
return match ls_colors.style_for(de) {
|
||||||
|
Some(style) => style_manager.apply_style(style, &name),
|
||||||
|
None => name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target) = target_symlink {
|
||||||
|
// use the optional target_symlink
|
||||||
|
// Use fn get_metadata_with_deref_opt instead of get_metadata() here because ls
|
||||||
|
// should not exit with an err, if we are unable to obtain the target_metadata
|
||||||
|
let md = get_metadata_with_deref_opt(target.p_buf.as_path(), path.must_dereference)
|
||||||
|
.unwrap_or_else(|_| target.get_metadata(out).unwrap().clone());
|
||||||
|
|
||||||
|
apply_style_based_on_metadata(path, Some(&md), ls_colors, style_manager, &name)
|
||||||
|
} else {
|
||||||
|
let md_option = path.get_metadata(out);
|
||||||
|
let symlink_metadata = path.p_buf.symlink_metadata().ok();
|
||||||
|
let md = md_option.or(symlink_metadata.as_ref());
|
||||||
|
|
||||||
|
apply_style_based_on_metadata(path, md, ls_colors, style_manager, &name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3299,7 +3327,7 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -
|
||||||
// does not support SELinux.
|
// does not support SELinux.
|
||||||
// Conforms to the GNU coreutils where a dangling symlink results in exit code 1.
|
// Conforms to the GNU coreutils where a dangling symlink results in exit code 1.
|
||||||
if must_dereference {
|
if must_dereference {
|
||||||
match get_metadata(p_buf, must_dereference) {
|
match get_metadata_with_deref_opt(p_buf, must_dereference) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// The Path couldn't be dereferenced, so return early and set exit code 1
|
// The Path couldn't be dereferenced, so return early and set exit code 1
|
||||||
// to indicate a minor error
|
// to indicate a minor error
|
||||||
|
@ -3364,7 +3392,7 @@ fn calculate_padding_collection(
|
||||||
for item in items {
|
for item in items {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if config.inode {
|
if config.inode {
|
||||||
let inode_len = if let Some(md) = item.md(out) {
|
let inode_len = if let Some(md) = item.get_metadata(out) {
|
||||||
display_inode(md).len()
|
display_inode(md).len()
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -3373,7 +3401,7 @@ fn calculate_padding_collection(
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.alloc_size {
|
if config.alloc_size {
|
||||||
if let Some(md) = item.md(out) {
|
if let Some(md) = item.get_metadata(out) {
|
||||||
let block_size_len = display_size(get_block_size(md, config), config).len();
|
let block_size_len = display_size(get_block_size(md, config), config).len();
|
||||||
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
|
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
|
||||||
}
|
}
|
||||||
|
@ -3423,7 +3451,7 @@ fn calculate_padding_collection(
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
if config.alloc_size {
|
if config.alloc_size {
|
||||||
if let Some(md) = item.md(out) {
|
if let Some(md) = item.get_metadata(out) {
|
||||||
let block_size_len = display_size(get_block_size(md, config), config).len();
|
let block_size_len = display_size(get_block_size(md, config), config).len();
|
||||||
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
|
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,3 +296,9 @@ ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"lo
|
||||||
# "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better
|
# "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better
|
||||||
# "hostid BEFORE --help AFTER " same for this
|
# "hostid BEFORE --help AFTER " same for this
|
||||||
sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/help/help-version-getopt.sh
|
sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/help/help-version-getopt.sh
|
||||||
|
|
||||||
|
# Add debug info + we have less syscall then GNU's. Adjust our check.
|
||||||
|
sed -i -e '/test \$n_stat1 = \$n_stat2 \\/c\
|
||||||
|
echo "n_stat1 = \$n_stat1"\n\
|
||||||
|
echo "n_stat2 = \$n_stat2"\n\
|
||||||
|
test \$n_stat1 -ge \$n_stat2 \\' tests/ls/stat-free-color.sh
|
||||||
|
|
Loading…
Reference in a new issue