1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2115 from tertsdiepraam/ls/reduce_write_calls

`ls`: reduce write syscalls & cleanup
This commit is contained in:
Sylvestre Ledru 2021-04-25 11:52:51 +02:00 committed by GitHub
commit e667cc2641
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -22,19 +22,22 @@ use lscolors::LsColors;
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use quoting_style::{escape_name, QuotingStyle}; use quoting_style::{escape_name, QuotingStyle};
#[cfg(unix)]
use std::collections::HashMap;
use std::fs::{self, DirEntry, FileType, Metadata};
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::{
cmp::Reverse,
fs::{self, DirEntry, FileType, Metadata},
io::{stdout, BufWriter, Stdout, Write},
path::{Path, PathBuf},
process::exit,
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(unix)] #[cfg(unix)]
use std::time::Duration; use std::{
use std::time::{SystemTime, UNIX_EPOCH}; collections::HashMap,
use std::{cmp::Reverse, process::exit}; os::unix::fs::{FileTypeExt, MetadataExt},
time::Duration,
};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use time::{strftime, Timespec}; use time::{strftime, Timespec};
#[cfg(unix)] #[cfg(unix)]
@ -86,10 +89,8 @@ pub mod options {
pub static C: &str = "quote-name"; pub static C: &str = "quote-name";
} }
pub static QUOTING_STYLE: &str = "quoting-style"; pub static QUOTING_STYLE: &str = "quoting-style";
pub mod indicator_style { pub mod indicator_style {
pub static NONE: &str = "none"; pub static SLASH: &str = "p";
pub static SLASH: &str = "slash";
pub static FILE_TYPE: &str = "file-type"; pub static FILE_TYPE: &str = "file-type";
pub static CLASSIFY: &str = "classify"; pub static CLASSIFY: &str = "classify";
} }
@ -108,9 +109,6 @@ pub mod options {
pub static TIME: &str = "time"; pub static TIME: &str = "time";
pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static IGNORE_BACKUPS: &str = "ignore-backups";
pub static DIRECTORY: &str = "directory"; pub static DIRECTORY: &str = "directory";
pub static CLASSIFY: &str = "classify";
pub static FILE_TYPE: &str = "file-type";
pub static SLASH: &str = "p";
pub static INODE: &str = "inode"; pub static INODE: &str = "inode";
pub static REVERSE: &str = "reverse"; pub static REVERSE: &str = "reverse";
pub static RECURSIVE: &str = "recursive"; pub static RECURSIVE: &str = "recursive";
@ -425,19 +423,11 @@ impl Config {
"slash" => IndicatorStyle::Slash, "slash" => IndicatorStyle::Slash,
&_ => IndicatorStyle::None, &_ => IndicatorStyle::None,
} }
} else if options.is_present(options::indicator_style::NONE) { } else if options.is_present(options::indicator_style::CLASSIFY) {
IndicatorStyle::None
} else if options.is_present(options::indicator_style::CLASSIFY)
|| options.is_present(options::CLASSIFY)
{
IndicatorStyle::Classify IndicatorStyle::Classify
} else if options.is_present(options::indicator_style::SLASH) } else if options.is_present(options::indicator_style::SLASH) {
|| options.is_present(options::SLASH)
{
IndicatorStyle::Slash IndicatorStyle::Slash
} else if options.is_present(options::indicator_style::FILE_TYPE) } else if options.is_present(options::indicator_style::FILE_TYPE) {
|| options.is_present(options::FILE_TYPE)
{
IndicatorStyle::FileType IndicatorStyle::FileType
} else { } else {
IndicatorStyle::None IndicatorStyle::None
@ -963,45 +953,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true) .takes_value(true)
.possible_values(&["none", "slash", "file-type", "classify"]) .possible_values(&["none", "slash", "file-type", "classify"])
.overrides_with_all(&[ .overrides_with_all(&[
options::FILE_TYPE, options::indicator_style::FILE_TYPE,
options::SLASH, options::indicator_style::SLASH,
options::CLASSIFY, options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE, options::INDICATOR_STYLE,
])) ]))
.arg( .arg(
Arg::with_name(options::CLASSIFY) Arg::with_name(options::indicator_style::CLASSIFY)
.short("F") .short("F")
.long(options::CLASSIFY) .long(options::indicator_style::CLASSIFY)
.help("Append a character to each file name indicating the file type. Also, for \ .help("Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \ regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.") '>' for doors, and nothing for regular files.")
.overrides_with_all(&[ .overrides_with_all(&[
options::FILE_TYPE, options::indicator_style::FILE_TYPE,
options::SLASH, options::indicator_style::SLASH,
options::CLASSIFY, options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE, options::INDICATOR_STYLE,
]) ])
) )
.arg( .arg(
Arg::with_name(options::FILE_TYPE) Arg::with_name(options::indicator_style::FILE_TYPE)
.long(options::FILE_TYPE) .long(options::indicator_style::FILE_TYPE)
.help("Same as --classify, but do not append '*'") .help("Same as --classify, but do not append '*'")
.overrides_with_all(&[ .overrides_with_all(&[
options::FILE_TYPE, options::indicator_style::FILE_TYPE,
options::SLASH, options::indicator_style::SLASH,
options::CLASSIFY, options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE, options::INDICATOR_STYLE,
])) ]))
.arg( .arg(
Arg::with_name(options::SLASH) Arg::with_name(options::indicator_style::SLASH)
.short(options::SLASH) .short(options::indicator_style::SLASH)
.help("Append / indicator to directories." .help("Append / indicator to directories."
) )
.overrides_with_all(&[ .overrides_with_all(&[
options::FILE_TYPE, options::indicator_style::FILE_TYPE,
options::SLASH, options::indicator_style::SLASH,
options::CLASSIFY, options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE, options::INDICATOR_STYLE,
])) ]))
@ -1092,6 +1082,8 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
let mut dirs = Vec::<PathData>::new(); let mut dirs = Vec::<PathData>::new();
let mut has_failed = false; let mut has_failed = false;
let mut out = BufWriter::new(stdout());
for loc in locs { for loc in locs {
let p = PathBuf::from(&loc); let p = PathBuf::from(&loc);
if !p.exists() { if !p.exists() {
@ -1118,14 +1110,14 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
} }
} }
sort_entries(&mut files, &config); sort_entries(&mut files, &config);
display_items(&files, None, &config); display_items(&files, None, &config, &mut out);
sort_entries(&mut dirs, &config); sort_entries(&mut dirs, &config);
for dir in dirs { for dir in dirs {
if number_of_locs > 1 { if number_of_locs > 1 {
println!("\n{}:", dir.p_buf.display()); let _ = writeln!(out, "\n{}:", dir.p_buf.display());
} }
enter_directory(&dir, &config); enter_directory(&dir, &config, &mut out);
} }
if has_failed { if has_failed {
1 1
@ -1178,7 +1170,7 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
!config.ignore_patterns.is_match(&ffi_name) !config.ignore_patterns.is_match(&ffi_name)
} }
fn enter_directory(dir: &PathData, config: &Config) { fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>) {
let mut entries: Vec<_> = if config.files == Files::All { let mut entries: Vec<_> = if config.files == Files::All {
vec![ vec![
PathData::new(dir.p_buf.join("."), None, config, false), PathData::new(dir.p_buf.join("."), None, config, false),
@ -1198,7 +1190,7 @@ fn enter_directory(dir: &PathData, config: &Config) {
entries.append(&mut temp); entries.append(&mut temp);
display_items(&entries, Some(&dir.p_buf), config); display_items(&entries, Some(&dir.p_buf), config, out);
if config.recursive { if config.recursive {
for e in entries for e in entries
@ -1206,8 +1198,8 @@ fn enter_directory(dir: &PathData, config: &Config) {
.skip(if config.files == Files::All { 2 } else { 0 }) .skip(if config.files == Files::All { 2 } else { 0 })
.filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
{ {
println!("\n{}:", e.p_buf.display()); let _ = writeln!(out, "\n{}:", e.p_buf.display());
enter_directory(&e, config); enter_directory(&e, config, out);
} }
} }
} }
@ -1235,7 +1227,12 @@ fn pad_left(string: String, count: usize) -> String {
format!("{:>width$}", string, width = count) format!("{:>width$}", string, width = count)
} }
fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { fn display_items(
items: &[PathData],
strip: Option<&Path>,
config: &Config,
out: &mut BufWriter<Stdout>,
) {
if config.format == Format::Long { if config.format == Format::Long {
let (mut max_links, mut max_size) = (1, 1); let (mut max_links, mut max_size) = (1, 1);
for item in items { for item in items {
@ -1244,7 +1241,7 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) {
max_size = size.max(max_size); max_size = size.max(max_size);
} }
for item in items { for item in items {
display_item_long(item, strip, max_links, max_size, config); display_item_long(item, strip, max_links, max_size, config, out);
} }
} else { } else {
let names = items let names = items
@ -1252,42 +1249,51 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) {
.filter_map(|i| display_file_name(&i, strip, config)); .filter_map(|i| display_file_name(&i, strip, config));
match (&config.format, config.width) { match (&config.format, config.width) {
(Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom), (Format::Columns, Some(width)) => {
(Format::Across, Some(width)) => display_grid(names, width, Direction::LeftToRight), display_grid(names, width, Direction::TopToBottom, out)
}
(Format::Across, Some(width)) => {
display_grid(names, width, Direction::LeftToRight, out)
}
(Format::Commas, width_opt) => { (Format::Commas, width_opt) => {
let term_width = width_opt.unwrap_or(1); let term_width = width_opt.unwrap_or(1);
let mut current_col = 0; let mut current_col = 0;
let mut names = names; let mut names = names;
if let Some(name) = names.next() { if let Some(name) = names.next() {
print!("{}", name.contents); let _ = write!(out, "{}", name.contents);
current_col = name.width as u16 + 2; current_col = name.width as u16 + 2;
} }
for name in names { for name in names {
let name_width = name.width as u16; let name_width = name.width as u16;
if current_col + name_width + 1 > term_width { if current_col + name_width + 1 > term_width {
current_col = name_width + 2; current_col = name_width + 2;
print!(",\n{}", name.contents); let _ = write!(out, ",\n{}", name.contents);
} else { } else {
current_col += name_width + 2; current_col += name_width + 2;
print!(", {}", name.contents); let _ = write!(out, ", {}", name.contents);
} }
} }
// Current col is never zero again if names have been printed. // Current col is never zero again if names have been printed.
// So we print a newline. // So we print a newline.
if current_col > 0 { if current_col > 0 {
println!(); let _ = writeln!(out,);
} }
} }
_ => { _ => {
for name in names { for name in names {
println!("{}", name.contents); let _ = writeln!(out, "{}", name.contents);
} }
} }
} }
} }
} }
fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direction) { fn display_grid(
names: impl Iterator<Item = Cell>,
width: u16,
direction: Direction,
out: &mut BufWriter<Stdout>,
) {
let mut grid = Grid::new(GridOptions { let mut grid = Grid::new(GridOptions {
filling: Filling::Spaces(2), filling: Filling::Spaces(2),
direction, direction,
@ -1298,9 +1304,13 @@ fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direct
} }
match grid.fit_into_width(width as usize) { match grid.fit_into_width(width as usize) {
Some(output) => print!("{}", output), Some(output) => {
let _ = write!(out, "{}", output);
}
// Width is too small for the grid, so we fit it in one column // Width is too small for the grid, so we fit it in one column
None => print!("{}", grid.fit_into_columns(1)), None => {
let _ = write!(out, "{}", grid.fit_into_columns(1));
}
} }
} }
@ -1312,6 +1322,7 @@ fn display_item_long(
max_links: usize, max_links: usize,
max_size: usize, max_size: usize,
config: &Config, config: &Config,
out: &mut BufWriter<Stdout>,
) { ) {
let md = match item.md() { let md = match item.md() {
None => { None => {
@ -1325,11 +1336,12 @@ fn display_item_long(
#[cfg(unix)] #[cfg(unix)]
{ {
if config.inode { if config.inode {
print!("{} ", get_inode(&md)); let _ = write!(out, "{} ", get_inode(&md));
} }
} }
print!( let _ = write!(
out,
"{}{} {}", "{}{} {}",
display_file_type(md.file_type()), display_file_type(md.file_type()),
display_permissions(&md), display_permissions(&md),
@ -1337,20 +1349,21 @@ fn display_item_long(
); );
if config.long.owner { if config.long.owner {
print!(" {}", display_uname(&md, config)); let _ = write!(out, " {}", display_uname(&md, config));
} }
if config.long.group { if config.long.group {
print!(" {}", display_group(&md, config)); let _ = write!(out, " {}", display_group(&md, config));
} }
// Author is only different from owner on GNU/Hurd, so we reuse // Author is only different from owner on GNU/Hurd, so we reuse
// the owner, since GNU/Hurd is not currently supported by Rust. // the owner, since GNU/Hurd is not currently supported by Rust.
if config.long.author { if config.long.author {
print!(" {}", display_uname(&md, config)); let _ = write!(out, " {}", display_uname(&md, config));
} }
println!( let _ = writeln!(
out,
" {} {} {}", " {} {} {}",
pad_left(display_file_size(&md, config), max_size), pad_left(display_file_size(&md, config), max_size),
display_date(&md, config), display_date(&md, config),
@ -1380,14 +1393,10 @@ fn cached_uid2usr(uid: u32) -> String {
} }
let mut uid_cache = UID_CACHE.lock().unwrap(); let mut uid_cache = UID_CACHE.lock().unwrap();
match uid_cache.get(&uid) { uid_cache
Some(usr) => usr.clone(), .entry(uid)
None => { .or_insert_with(|| entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()))
let usr = entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()); .clone()
uid_cache.insert(uid, usr.clone());
usr
}
}
} }
#[cfg(unix)] #[cfg(unix)]
@ -1406,14 +1415,10 @@ fn cached_gid2grp(gid: u32) -> String {
} }
let mut gid_cache = GID_CACHE.lock().unwrap(); let mut gid_cache = GID_CACHE.lock().unwrap();
match gid_cache.get(&gid) { gid_cache
Some(grp) => grp.clone(), .entry(gid)
None => { .or_insert_with(|| entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()))
let grp = entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()); .clone()
gid_cache.insert(gid, grp.clone());
grp
}
}
} }
#[cfg(unix)] #[cfg(unix)]
@ -1431,7 +1436,6 @@ fn display_uname(_metadata: &Metadata, _config: &Config) -> String {
} }
#[cfg(not(unix))] #[cfg(not(unix))]
#[allow(unused_variables)]
fn display_group(_metadata: &Metadata, _config: &Config) -> String { fn display_group(_metadata: &Metadata, _config: &Config) -> String {
"somegroup".to_string() "somegroup".to_string()
} }
@ -1506,13 +1510,13 @@ fn display_file_size(metadata: &Metadata, config: &Config) -> String {
} }
} }
fn display_file_type(file_type: FileType) -> String { fn display_file_type(file_type: FileType) -> char {
if file_type.is_dir() { if file_type.is_dir() {
"d".to_string() 'd'
} else if file_type.is_symlink() { } else if file_type.is_symlink() {
"l".to_string() 'l'
} else { } else {
"-".to_string() '-'
} }
} }