Skip to content

Commit 6475e6f

Browse files
authored
Merge pull request #5660 from sylvestre/stat-free-color
ls: Improve the access to metadata of the files
2 parents f2ca22e + c5217b3 commit 6475e6f

File tree

2 files changed

+85
-51
lines changed

2 files changed

+85
-51
lines changed

src/uu/ls/src/ls.rs

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,8 @@ struct PathData {
18121812
// Result<MetaData> got from symlink_metadata() or metadata() based on config
18131813
md: OnceCell<Option<Metadata>>,
18141814
ft: OnceCell<Option<FileType>>,
1815+
// can be used to avoid reading the metadata. Can be also called d_type:
1816+
// https://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html
18151817
de: Option<DirEntry>,
18161818
// Name of the file - will be empty for . or ..
18171819
display_name: OsString,
@@ -1907,18 +1909,19 @@ impl PathData {
19071909
}
19081910
}
19091911

1910-
fn md(&self, out: &mut BufWriter<Stdout>) -> Option<&Metadata> {
1912+
fn get_metadata(&self, out: &mut BufWriter<Stdout>) -> Option<&Metadata> {
19111913
self.md
19121914
.get_or_init(|| {
19131915
// check if we can use DirEntry metadata
1916+
// it will avoid a call to stat()
19141917
if !self.must_dereference {
19151918
if let Some(dir_entry) = &self.de {
19161919
return dir_entry.metadata().ok();
19171920
}
19181921
}
19191922

19201923
// if not, check if we can use Path metadata
1921-
match get_metadata(self.p_buf.as_path(), self.must_dereference) {
1924+
match get_metadata_with_deref_opt(self.p_buf.as_path(), self.must_dereference) {
19221925
Err(err) => {
19231926
// FIXME: A bit tricky to propagate the result here
19241927
out.flush().unwrap();
@@ -1947,7 +1950,7 @@ impl PathData {
19471950

19481951
fn file_type(&self, out: &mut BufWriter<Stdout>) -> Option<&FileType> {
19491952
self.ft
1950-
.get_or_init(|| self.md(out).map(|md| md.file_type()))
1953+
.get_or_init(|| self.get_metadata(out).map(|md| md.file_type()))
19511954
.as_ref()
19521955
}
19531956
}
@@ -1980,7 +1983,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
19801983
// Proper GNU handling is don't show if dereferenced symlink DNE
19811984
// but only for the base dir, for a child dir show, and print ?s
19821985
// in long format
1983-
if path_data.md(&mut out).is_none() {
1986+
if path_data.get_metadata(&mut out).is_none() {
19841987
continue;
19851988
}
19861989

@@ -2068,12 +2071,14 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter<S
20682071
match config.sort {
20692072
Sort::Time => entries.sort_by_key(|k| {
20702073
Reverse(
2071-
k.md(out)
2074+
k.get_metadata(out)
20722075
.and_then(|md| get_system_time(md, config))
20732076
.unwrap_or(UNIX_EPOCH),
20742077
)
20752078
}),
2076-
Sort::Size => entries.sort_by_key(|k| Reverse(k.md(out).map(|md| md.len()).unwrap_or(0))),
2079+
Sort::Size => {
2080+
entries.sort_by_key(|k| Reverse(k.get_metadata(out).map(|md| md.len()).unwrap_or(0)));
2081+
}
20772082
// The default sort in GNU ls is case insensitive
20782083
Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)),
20792084
Sort::Version => entries.sort_by(|a, b| {
@@ -2114,7 +2119,8 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter<S
21142119
!match md {
21152120
None | Some(None) => {
21162121
// If it metadata cannot be determined, treat as a file.
2117-
get_metadata(p.p_buf.as_path(), true).map_or_else(|_| false, |m| m.is_dir())
2122+
get_metadata_with_deref_opt(p.p_buf.as_path(), true)
2123+
.map_or_else(|_| false, |m| m.is_dir())
21182124
}
21192125
Some(Some(m)) => m.is_dir(),
21202126
}
@@ -2289,7 +2295,7 @@ fn enter_directory(
22892295
Ok(())
22902296
}
22912297

2292-
fn get_metadata(p_buf: &Path, dereference: bool) -> std::io::Result<Metadata> {
2298+
fn get_metadata_with_deref_opt(p_buf: &Path, dereference: bool) -> std::io::Result<Metadata> {
22932299
if dereference {
22942300
p_buf.metadata()
22952301
} else {
@@ -2303,7 +2309,7 @@ fn display_dir_entry_size(
23032309
out: &mut BufWriter<std::io::Stdout>,
23042310
) -> (usize, usize, usize, usize, usize, usize) {
23052311
// TODO: Cache/memorize the display_* results so we don't have to recalculate them.
2306-
if let Some(md) = entry.md(out) {
2312+
if let Some(md) = entry.get_metadata(out) {
23072313
let (size_len, major_len, minor_len) = match display_len_or_rdev(md, config) {
23082314
SizeOrDeviceId::Device(major, minor) => (
23092315
(major.len() + minor.len() + 2usize),
@@ -2341,7 +2347,7 @@ fn return_total(
23412347
let mut total_size = 0;
23422348
for item in items {
23432349
total_size += item
2344-
.md(out)
2350+
.get_metadata(out)
23452351
.as_ref()
23462352
.map_or(0, |md| get_block_size(md, config));
23472353
}
@@ -2365,7 +2371,7 @@ fn display_additional_leading_info(
23652371
#[cfg(unix)]
23662372
{
23672373
if config.inode {
2368-
let i = if let Some(md) = item.md(out) {
2374+
let i = if let Some(md) = item.get_metadata(out) {
23692375
get_inode(md)
23702376
} else {
23712377
"?".to_owned()
@@ -2375,7 +2381,7 @@ fn display_additional_leading_info(
23752381
}
23762382

23772383
if config.alloc_size {
2378-
let s = if let Some(md) = item.md(out) {
2384+
let s = if let Some(md) = item.get_metadata(out) {
23792385
display_size(get_block_size(md, config), config)
23802386
} else {
23812387
"?".to_owned()
@@ -2590,7 +2596,7 @@ fn display_item_long(
25902596
if config.dired {
25912597
output_display += " ";
25922598
}
2593-
if let Some(md) = item.md(out) {
2599+
if let Some(md) = item.get_metadata(out) {
25942600
write!(
25952601
output_display,
25962602
"{}{} {}",
@@ -3017,7 +3023,7 @@ fn classify_file(path: &PathData, out: &mut BufWriter<Stdout>) -> Option<char> {
30173023
} else if file_type.is_file()
30183024
// Safe unwrapping if the file was removed between listing and display
30193025
// See https://github.com/uutils/coreutils/issues/5371
3020-
&& path.md(out).map(file_is_executable).unwrap_or_default()
3026+
&& path.get_metadata(out).map(file_is_executable).unwrap_or_default()
30213027
{
30223028
Some('*')
30233029
} else {
@@ -3064,18 +3070,7 @@ fn display_item_name(
30643070
}
30653071

30663072
if let Some(ls_colors) = &config.color {
3067-
let md = path.md(out);
3068-
name = if md.is_some() {
3069-
color_name(name, &path.p_buf, md, ls_colors, style_manager)
3070-
} else {
3071-
color_name(
3072-
name,
3073-
&path.p_buf,
3074-
path.p_buf.symlink_metadata().ok().as_ref(),
3075-
ls_colors,
3076-
style_manager,
3077-
)
3078-
};
3073+
name = color_name(name, path, ls_colors, style_manager, out, None);
30793074
}
30803075

30813076
if config.format != Format::Long && !more_info.is_empty() {
@@ -3141,28 +3136,22 @@ fn display_item_name(
31413136
// Because we use an absolute path, we can assume this is guaranteed to exist.
31423137
// Otherwise, we use path.md(), which will guarantee we color to the same
31433138
// color of non-existent symlinks according to style_for_path_with_metadata.
3144-
if path.md(out).is_none()
3145-
&& get_metadata(target_data.p_buf.as_path(), target_data.must_dereference)
3146-
.is_err()
3139+
if path.get_metadata(out).is_none()
3140+
&& get_metadata_with_deref_opt(
3141+
target_data.p_buf.as_path(),
3142+
target_data.must_dereference,
3143+
)
3144+
.is_err()
31473145
{
31483146
name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy());
31493147
} else {
3150-
// Use fn get_metadata instead of md() here and above because ls
3151-
// should not exit with an err, if we are unable to obtain the target_metadata
3152-
let target_metadata = match get_metadata(
3153-
target_data.p_buf.as_path(),
3154-
target_data.must_dereference,
3155-
) {
3156-
Ok(md) => md,
3157-
Err(_) => path.md(out).unwrap().clone(),
3158-
};
3159-
31603148
name.push_str(&color_name(
31613149
escape_name(target.as_os_str(), &config.quoting_style),
3162-
&target_data.p_buf,
3163-
Some(&target_metadata),
3150+
path,
31643151
ls_colors,
31653152
style_manager,
3153+
out,
3154+
Some(&target_data),
31663155
));
31673156
}
31683157
} else {
@@ -3259,17 +3248,56 @@ impl StyleManager {
32593248
}
32603249
}
32613250

3262-
/// Colors the provided name based on the style determined for the given path.
3251+
fn apply_style_based_on_metadata(
3252+
path: &PathData,
3253+
md_option: Option<&Metadata>,
3254+
ls_colors: &LsColors,
3255+
style_manager: &mut StyleManager,
3256+
name: &str,
3257+
) -> String {
3258+
match ls_colors.style_for_path_with_metadata(&path.p_buf, md_option) {
3259+
Some(style) => style_manager.apply_style(style, name),
3260+
None => name.to_owned(),
3261+
}
3262+
}
3263+
3264+
/// Colors the provided name based on the style determined for the given path
3265+
/// This function is quite long because it tries to leverage DirEntry to avoid
3266+
/// unnecessary calls to stat()
3267+
/// and manages the symlink errors
32633268
fn color_name(
32643269
name: String,
3265-
path: &Path,
3266-
md: Option<&Metadata>,
3270+
path: &PathData,
32673271
ls_colors: &LsColors,
32683272
style_manager: &mut StyleManager,
3273+
out: &mut BufWriter<Stdout>,
3274+
target_symlink: Option<&PathData>,
32693275
) -> String {
3270-
match ls_colors.style_for_path_with_metadata(path, md) {
3271-
Some(style) => style_manager.apply_style(style, &name),
3272-
None => name,
3276+
if !path.must_dereference {
3277+
// If we need to dereference (follow) a symlink, we will need to get the metadata
3278+
if let Some(de) = &path.de {
3279+
// There is a DirEntry, we don't need to get the metadata for the color
3280+
return match ls_colors.style_for(de) {
3281+
Some(style) => style_manager.apply_style(style, &name),
3282+
None => name,
3283+
};
3284+
}
3285+
}
3286+
3287+
if let Some(target) = target_symlink {
3288+
// use the optional target_symlink
3289+
// Use fn get_metadata_with_deref_opt instead of get_metadata() here because ls
3290+
// should not exit with an err, if we are unable to obtain the target_metadata
3291+
let md = get_metadata_with_deref_opt(target.p_buf.as_path(), path.must_dereference)
3292+
.unwrap_or_else(|_| target.get_metadata(out).unwrap().clone());
3293+
3294+
apply_style_based_on_metadata(path, Some(&md), ls_colors, style_manager, &name)
3295+
} else {
3296+
let md_option = path.get_metadata(out);
3297+
let symlink_metadata = path.p_buf.symlink_metadata().ok();
3298+
let md = md_option.or(symlink_metadata.as_ref());
3299+
3300+
apply_style_based_on_metadata(path, md, ls_colors, style_manager, &name)
32733301
}
32743302
}
32753303

@@ -3299,7 +3327,7 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -
32993327
// does not support SELinux.
33003328
// Conforms to the GNU coreutils where a dangling symlink results in exit code 1.
33013329
if must_dereference {
3302-
match get_metadata(p_buf, must_dereference) {
3330+
match get_metadata_with_deref_opt(p_buf, must_dereference) {
33033331
Err(err) => {
33043332
// The Path couldn't be dereferenced, so return early and set exit code 1
33053333
// to indicate a minor error
@@ -3364,7 +3392,7 @@ fn calculate_padding_collection(
33643392
for item in items {
33653393
#[cfg(unix)]
33663394
if config.inode {
3367-
let inode_len = if let Some(md) = item.md(out) {
3395+
let inode_len = if let Some(md) = item.get_metadata(out) {
33683396
display_inode(md).len()
33693397
} else {
33703398
continue;
@@ -3373,7 +3401,7 @@ fn calculate_padding_collection(
33733401
}
33743402

33753403
if config.alloc_size {
3376-
if let Some(md) = item.md(out) {
3404+
if let Some(md) = item.get_metadata(out) {
33773405
let block_size_len = display_size(get_block_size(md, config), config).len();
33783406
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
33793407
}
@@ -3423,7 +3451,7 @@ fn calculate_padding_collection(
34233451

34243452
for item in items {
34253453
if config.alloc_size {
3426-
if let Some(md) = item.md(out) {
3454+
if let Some(md) = item.get_metadata(out) {
34273455
let block_size_len = display_size(get_block_size(md, config), config).len();
34283456
padding_collections.block_size = block_size_len.max(padding_collections.block_size);
34293457
}

util/build-gnu.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,9 @@ ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"lo
296296
# "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better
297297
# "hostid BEFORE --help AFTER " same for this
298298
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
299+
300+
# Add debug info + we have less syscall then GNU's. Adjust our check.
301+
sed -i -e '/test \$n_stat1 = \$n_stat2 \\/c\
302+
echo "n_stat1 = \$n_stat1"\n\
303+
echo "n_stat2 = \$n_stat2"\n\
304+
test \$n_stat1 -ge \$n_stat2 \\' tests/ls/stat-free-color.sh

0 commit comments

Comments
 (0)