Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 70 additions & 23 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub mod options {
pub static IGNORE_BACKUPS: &str = "ignore-backups";
pub static DIRECTORY: &str = "directory";
pub static INODE: &str = "inode";
pub static BLOCKS: &str = "blocks";
pub static REVERSE: &str = "reverse";
pub static RECURSIVE: &str = "recursive";
pub static COLOR: &str = "color";
Expand Down Expand Up @@ -230,6 +231,7 @@ struct Config {
time: Time,
#[cfg(unix)]
inode: bool,
blocks: bool,
color: Option<LsColors>,
long: LongFormat,
width: Option<u16>,
Expand Down Expand Up @@ -571,6 +573,7 @@ impl Config {
color,
#[cfg(unix)]
inode: options.is_present(options::INODE),
blocks: options.is_present(options::BLOCKS),
long,
width,
quoting_style,
Expand Down Expand Up @@ -1038,6 +1041,12 @@ only ignore '.' and '..'.",
.long(options::INODE)
.help("print the index number of each file"),
)
.arg(
Arg::with_name(options::BLOCKS)
.short("s")
.long(options::BLOCKS)
.help("print the number of file system blocks of each file"),
)
.arg(
Arg::with_name(options::REVERSE)
.short("r")
Expand Down Expand Up @@ -1377,42 +1386,59 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
}
}

fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
if let Some(md) = entry.md() {
(
display_symlink_count(md).len(),
display_size_or_rdev(md, config).len(),
)
} else {
(0, 0)
fn get_display_size(md: &Metadata) -> u64 {
#[cfg(windows)]
{
if md.is_dir() {
return 0;
}
md.len() as u64
}

#[cfg(unix)]
{
md.blocks() as u64
}
}

fn pad_left(string: String, count: usize) -> String {
// Get maximum string sizes for:
// - number of links of each items
// - size
// - blocks
fn get_max_sizes(items: &[PathData], config: &Config) -> (usize, usize, usize, u64) {
let (mut max_links, mut max_width, mut max_blocks) = (1, 1, 1);
let mut total_size = 0;

for item in items {
if let Some(md) = item.md() {
max_links = display_symlink_count(md).len().max(max_links);
max_width = display_size_or_rdev(md, config).len().max(max_width);
max_blocks = get_display_size(md).to_string().len().max(max_blocks);
total_size += get_block_size(md, config);
}
}

(max_links, max_width, max_blocks, total_size)
}

fn pad_left(string: impl Display, count: usize) -> String {
format!("{:>width$}", string, width = count)
}

fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
let (max_links, max_width, max_blocks, total_size) = get_max_sizes(items, config);
if config.format == Format::Long {
let (mut max_links, mut max_width) = (1, 1);
let mut total_size = 0;

for item in items {
let (links, width) = display_dir_entry_size(item, config);
max_links = links.max(max_links);
max_width = width.max(max_width);
total_size += item.md().map_or(0, |md| get_block_size(md, config));
}

if total_size > 0 {
let _ = writeln!(out, "total {}", display_size(total_size, config));
}

for item in items {
display_item_long(item, max_links, max_width, config, out);
display_item_long(item, max_links, max_width, max_blocks, config, out);
}
} else {
let names = items.iter().filter_map(|i| display_file_name(i, config));
let names = items
.iter()
.filter_map(|i| display_file_name(i, config, max_blocks));

match (&config.format, config.width) {
(Format::Columns, Some(width)) => {
Expand Down Expand Up @@ -1507,6 +1533,7 @@ fn display_item_long(
item: &PathData,
max_links: usize,
max_size: usize,
max_blocks: usize,
config: &Config,
out: &mut BufWriter<Stdout>,
) {
Expand All @@ -1525,6 +1552,14 @@ fn display_item_long(
}
}

if config.blocks {
let _ = write!(
out,
"{} ",
pad_left(get_display_size(md).to_string(), max_blocks)
);
}

let _ = write!(
out,
"{} {}",
Expand Down Expand Up @@ -1554,7 +1589,7 @@ fn display_item_long(
// 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, config).unwrap().contents,
display_file_name(item, config, 1).unwrap().contents,
);
}

Expand Down Expand Up @@ -1765,7 +1800,7 @@ fn classify_file(path: &PathData) -> Option<char> {
}
}

fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
fn display_file_name(path: &PathData, config: &Config, max_blocks: usize) -> Option<Cell> {
let mut name = escape_name(&path.display_name, &config.quoting_style);

#[cfg(unix)]
Expand Down Expand Up @@ -1815,6 +1850,18 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
}
}

if config.format != Format::Long && config.blocks {
width += max_blocks + 1;
if let Some(md) = path.md() {
name.insert_str(
0,
&format!("{} ", pad_left(get_display_size(md), max_blocks)),
);
} else {
name.insert_str(0, &format!("{} ", pad_left(String::from(""), max_blocks)));
}
}

if config.format == Format::Long && path.file_type()?.is_symlink() {
if let Ok(target) = path.p_buf.read_link() {
name.push_str(" -> ");
Expand Down
55 changes: 55 additions & 0 deletions tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2046,3 +2046,58 @@ fn test_ls_dangling_symlinks() {
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
}

#[test]
fn test_ls_block_size_display_short() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.mkdir("dir-test");
at.touch("file-1");
at.append("file-1", &"x".repeat(1000));
at.touch("file-2");
at.append("file-2", &"x".repeat(100000));

let result = scene.ucmd().arg("-s").arg("-w=0").succeeds();

// Block sizes vary by OS. Only values for the following OS'es are checked
#[cfg(target_os = "macos")]
result.stdout_only(" 0 dir-test\n 8 file-1\n200 file-2\n");
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
result.stdout_only(" 8 dir-test\n 8 file-1\n200 file-2\n");
#[cfg(target_os = "windows")]
result.stdout_only(" 0 dir-test\n 1000 file-1\n100000 file-2\n");
}

#[test]
fn test_ls_block_size_display_long() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.mkdir("dir-test");
at.touch("file-1");
at.append("file-1", &"x".repeat(1000));
at.touch("file-2");
at.append("file-2", &"x".repeat(100000));

let result = scene.ucmd().arg("-ls").succeeds();
// Block sizes vary by OS. Only values for the following OS'es are checked
#[cfg(target_os = "macos")]
{
result.stdout_contains("\n 0 ");
result.stdout_contains("\n 8 "); // for file-1
result.stdout_contains("\n200 "); // for file-
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
result.stdout_contains("\n 8 "); // for directory
result.stdout_contains("\n 8 "); // for file-1
result.stdout_contains("\n200 "); // for file-2
}
#[cfg(target_os = "windows")]
{
result.stdout_contains("\n 0 ");
result.stdout_contains("\n 1000 "); // for file-1
result.stdout_contains("\n100000 "); // for file-2
}
}