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

ls: Fix display of inodes and add allocation size feature (#3052)

This commit is contained in:
kimono-koans 2022-03-15 10:27:43 -05:00 committed by GitHub
parent 748d414946
commit fa6af85b8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 486 additions and 122 deletions

View file

@ -26,8 +26,9 @@ use std::os::windows::fs::MetadataExt;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
error::Error, error::Error,
ffi::{OsStr, OsString},
fmt::Display, fmt::Display,
fs::{self, DirEntry, FileType, Metadata}, fs::{self, DirEntry, FileType, Metadata, ReadDir},
io::{stdout, BufWriter, ErrorKind, Stdout, Write}, io::{stdout, BufWriter, ErrorKind, Stdout, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
@ -38,17 +39,18 @@ use std::{
os::unix::fs::{FileTypeExt, MetadataExt}, os::unix::fs::{FileTypeExt, MetadataExt},
time::Duration, time::Duration,
}; };
use std::{ffi::OsString, fs::ReadDir};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use uucore::{
display::Quotable,
error::{set_exit_code, UError, UResult},
};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
#[cfg(unix)] #[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::{format_usage, fs::display_permissions, version_cmp::version_cmp}; use uucore::{
display::Quotable,
error::{set_exit_code, UError, UResult},
format_usage,
fs::display_permissions,
parse_size::parse_size,
version_cmp::version_cmp,
};
#[cfg(not(feature = "selinux"))] #[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)"; static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
@ -89,8 +91,11 @@ pub mod options {
} }
pub mod size { pub mod size {
pub static ALLOCATION_SIZE: &str = "size";
pub static BLOCK_SIZE: &str = "block-size";
pub static HUMAN_READABLE: &str = "human-readable"; pub static HUMAN_READABLE: &str = "human-readable";
pub static SI: &str = "si"; pub static SI: &str = "si";
pub static KIBIBYTES: &str = "kibibytes";
} }
pub mod quoting { pub mod quoting {
@ -136,19 +141,25 @@ pub mod options {
} }
const DEFAULT_TERM_WIDTH: u16 = 80; const DEFAULT_TERM_WIDTH: u16 = 80;
const POSIXLY_CORRECT_BLOCK_SIZE: u64 = 512;
#[cfg(unix)]
const DEFAULT_BLOCK_SIZE: u64 = 1024;
#[derive(Debug)] #[derive(Debug)]
enum LsError { enum LsError {
InvalidLineWidth(String), InvalidLineWidth(String),
IOError(std::io::Error), IOError(std::io::Error),
IOErrorContext(std::io::Error, PathBuf), IOErrorContext(std::io::Error, PathBuf),
BlockSizeParseError(String),
} }
impl UError for LsError { impl UError for LsError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
LsError::InvalidLineWidth(_) => 2, LsError::InvalidLineWidth(_) => 2,
LsError::IOError(_) | LsError::IOErrorContext(_, _) => 1, LsError::IOError(_) => 1,
LsError::IOErrorContext(_, _) => 1,
LsError::BlockSizeParseError(_) => 1,
} }
} }
} }
@ -158,6 +169,9 @@ impl Error for LsError {}
impl Display for LsError { impl Display for LsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
LsError::BlockSizeParseError(s) => {
write!(f, "invalid --block-size argument {}", s.quote())
}
LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()), LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()),
LsError::IOError(e) => write!(f, "general io error: {}", e), LsError::IOError(e) => write!(f, "general io error: {}", e),
LsError::IOErrorContext(e, p) => { LsError::IOErrorContext(e, p) => {
@ -245,6 +259,7 @@ enum Sort {
Extension, Extension,
} }
#[derive(PartialEq)]
enum SizeFormat { enum SizeFormat {
Bytes, Bytes,
Binary, // Powers of 1024, --human-readable, -h Binary, // Powers of 1024, --human-readable, -h
@ -303,6 +318,8 @@ struct Config {
inode: bool, inode: bool,
color: Option<LsColors>, color: Option<LsColors>,
long: LongFormat, long: LongFormat,
alloc_size: bool,
block_size: Option<u64>,
width: u16, width: u16,
quoting_style: QuotingStyle, quoting_style: QuotingStyle,
indicator_style: IndicatorStyle, indicator_style: IndicatorStyle,
@ -332,6 +349,7 @@ struct PaddingCollection {
major: usize, major: usize,
#[cfg(unix)] #[cfg(unix)]
minor: usize, minor: usize,
block_size: usize,
} }
impl Config { impl Config {
@ -467,14 +485,67 @@ impl Config {
None None
}; };
let size_format = if options.is_present(options::size::HUMAN_READABLE) { let cmd_line_bs = options.value_of(options::size::BLOCK_SIZE);
SizeFormat::Binary let opt_si = cmd_line_bs.is_some()
} else if options.is_present(options::size::SI) { && options
.value_of(options::size::BLOCK_SIZE)
.unwrap()
.eq("si")
|| options.is_present(options::size::SI);
let opt_hr = (cmd_line_bs.is_some()
&& options
.value_of(options::size::BLOCK_SIZE)
.unwrap()
.eq("human-readable"))
|| options.is_present(options::size::HUMAN_READABLE);
let opt_kb = options.is_present(options::size::KIBIBYTES);
let bs_env_var = std::env::var_os("BLOCK_SIZE");
let ls_bs_env_var = std::env::var_os("LS_BLOCK_SIZE");
let pc_env_var = std::env::var_os("POSIXLY_CORRECT");
let size_format = if opt_si {
SizeFormat::Decimal SizeFormat::Decimal
} else if opt_hr {
SizeFormat::Binary
} else { } else {
SizeFormat::Bytes SizeFormat::Bytes
}; };
let raw_bs = if let Some(cmd_line_bs) = cmd_line_bs {
OsString::from(cmd_line_bs)
} else if !opt_kb {
if let Some(ls_bs_env_var) = ls_bs_env_var {
ls_bs_env_var
} else if let Some(bs_env_var) = bs_env_var {
bs_env_var
} else {
OsString::from("")
}
} else {
OsString::from("")
};
let block_size: Option<u64> = if !opt_si && !opt_hr && !raw_bs.is_empty() {
match parse_size(&raw_bs.to_string_lossy()) {
Ok(size) => Some(size),
Err(_) => {
show!(LsError::BlockSizeParseError(
cmd_line_bs.unwrap().to_owned()
));
None
}
}
} else if let Some(pc) = pc_env_var {
if pc.as_os_str() == OsStr::new("true") || pc == OsStr::new("1") {
Some(POSIXLY_CORRECT_BLOCK_SIZE)
} else {
None
}
} else {
None
};
let long = { let long = {
let author = options.is_present(options::AUTHOR); let author = options.is_present(options::AUTHOR);
let group = !options.is_present(options::NO_GROUP) let group = !options.is_present(options::NO_GROUP)
@ -523,8 +594,12 @@ impl Config {
!atty::is(atty::Stream::Stdout) !atty::is(atty::Stream::Stdout)
}; };
let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) { let opt_quoting_style = options
match style { .value_of(options::QUOTING_STYLE)
.map(|cmd_line_qs| cmd_line_qs.to_owned());
let quoting_style = if let Some(style) = opt_quoting_style {
match style.as_str() {
"literal" => QuotingStyle::Literal { show_control }, "literal" => QuotingStyle::Literal { show_control },
"shell" => QuotingStyle::Shell { "shell" => QuotingStyle::Shell {
escape: false, escape: false,
@ -684,6 +759,8 @@ impl Config {
#[cfg(unix)] #[cfg(unix)]
inode: options.is_present(options::INODE), inode: options.is_present(options::INODE),
long, long,
alloc_size: options.is_present(options::size::ALLOCATION_SIZE),
block_size,
width, width,
quoting_style, quoting_style,
indicator_style, indicator_style,
@ -1148,11 +1225,25 @@ only ignore '.' and '..'.",
.help("Print human readable file sizes (e.g. 1K 234M 56G).") .help("Print human readable file sizes (e.g. 1K 234M 56G).")
.overrides_with(options::size::SI), .overrides_with(options::size::SI),
) )
.arg(
Arg::new(options::size::KIBIBYTES)
.short('k')
.long(options::size::KIBIBYTES)
.help("default to 1024-byte blocks for file system usage; used only with -s and per directory totals"),
)
.arg( .arg(
Arg::new(options::size::SI) Arg::new(options::size::SI)
.long(options::size::SI) .long(options::size::SI)
.help("Print human readable file sizes using powers of 1000 instead of 1024."), .help("Print human readable file sizes using powers of 1000 instead of 1024."),
) )
.arg(
Arg::new(options::size::BLOCK_SIZE)
.long(options::size::BLOCK_SIZE)
.takes_value(true)
.require_equals(true)
.value_name("BLOCK_SIZE")
.help("scale sizes by BLOCK_SIZE when printing them"),
)
.arg( .arg(
Arg::new(options::INODE) Arg::new(options::INODE)
.short('i') .short('i')
@ -1182,6 +1273,12 @@ only ignore '.' and '..'.",
.value_name("COLS") .value_name("COLS")
.takes_value(true), .takes_value(true),
) )
.arg(
Arg::new(options::size::ALLOCATION_SIZE)
.short('s')
.long(options::size::ALLOCATION_SIZE)
.help("print the allocated size of each file, in blocks"),
)
.arg( .arg(
Arg::new(options::COLOR) Arg::new(options::COLOR)
.long(options::COLOR) .long(options::COLOR)
@ -1615,7 +1712,7 @@ fn enter_directory(
entries.append(&mut vec_path_data); entries.append(&mut vec_path_data);
// Print total after any error display // Print total after any error display
if config.format == Format::Long { if config.format == Format::Long || config.alloc_size {
display_total(&entries, config, out)?; display_total(&entries, config, out)?;
} }
@ -1658,10 +1755,10 @@ fn display_dir_entry_size(
entry: &PathData, entry: &PathData,
config: &Config, config: &Config,
out: &mut BufWriter<std::io::Stdout>, out: &mut BufWriter<std::io::Stdout>,
) -> (usize, 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.md(out) {
let (size_len, major_len, minor_len) = match display_size_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),
major.len(), major.len(),
@ -1676,10 +1773,9 @@ fn display_dir_entry_size(
size_len, size_len,
major_len, major_len,
minor_len, minor_len,
display_inode(md).len(),
) )
} else { } else {
(0, 0, 0, 0, 0, 0, 0) (0, 0, 0, 0, 0, 0)
} }
} }
@ -1703,6 +1799,36 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
Ok(()) Ok(())
} }
fn display_additional_leading_info(
item: &PathData,
padding: &PaddingCollection,
config: &Config,
out: &mut BufWriter<Stdout>,
) -> UResult<String> {
let mut result = String::new();
#[cfg(unix)]
{
if config.inode {
let i = if let Some(md) = item.md(out) {
get_inode(md)
} else {
"?".to_owned()
};
result.push_str(&format!("{} ", pad_left(&i, padding.inode)));
}
}
if config.alloc_size {
let s = if let Some(md) = item.md(out) {
display_size(get_block_size(md, config), config)
} else {
"?".to_owned()
};
result.push_str(&format!("{} ", pad_left(&s, padding.block_size),));
}
Ok(result)
}
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) -> UResult<()> { fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) -> UResult<()> {
// `-Z`, `--context`: // `-Z`, `--context`:
// Display the SELinux security context or '?' if none is found. When used with the `-l` // Display the SELinux security context or '?' if none is found. When used with the `-l`
@ -1712,6 +1838,18 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
let padding_collection = calculate_padding_collection(items, config, out); let padding_collection = calculate_padding_collection(items, config, out);
for item in items { for item in items {
#[cfg(unix)]
if config.inode || config.alloc_size {
let more_info =
display_additional_leading_info(item, &padding_collection, config, out)?;
write!(out, "{}", more_info)?;
}
#[cfg(not(unix))]
if config.alloc_size {
let more_info =
display_additional_leading_info(item, &padding_collection, config, out)?;
write!(out, "{}", more_info)?;
}
display_item_long(item, &padding_collection, config, out)?; display_item_long(item, &padding_collection, config, out)?;
} }
} else { } else {
@ -1726,27 +1864,17 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
None None
}; };
#[cfg(not(unix))] let padding = calculate_padding_collection(items, config, out);
let longest_inode_len = 1;
#[cfg(unix)] let mut names_vec = Vec::new();
let mut longest_inode_len = 1;
#[cfg(unix)] for i in items {
if config.inode { let more_info = display_additional_leading_info(i, &padding, config, out)?;
for item in items { let cell = display_file_name(i, config, prefix_context, more_info, out);
let inode_len = if let Some(md) = item.md(out) { names_vec.push(cell);
display_inode(md).len()
} else {
continue;
};
longest_inode_len = inode_len.max(longest_inode_len);
}
} }
let names: std::vec::IntoIter<Cell> = items let names = names_vec.into_iter();
.iter()
.map(|i| display_file_name(i, config, prefix_context, longest_inode_len, out))
.collect::<Vec<Cell>>()
.into_iter();
match config.format { match config.format {
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out)?, Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out)?,
@ -1786,24 +1914,35 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
Ok(()) Ok(())
} }
#[allow(unused_variables)]
fn get_block_size(md: &Metadata, config: &Config) -> u64 { fn get_block_size(md: &Metadata, config: &Config) -> u64 {
/* GNU ls will display sizes in terms of block size /* GNU ls will display sizes in terms of block size
md.len() will differ from this value when the file has some holes md.len() will differ from this value when the file has some holes
*/ */
#[cfg(unix)] #[cfg(unix)]
{ {
// hard-coded for now - enabling setting this remains a TODO let raw_blocks = if md.file_type().is_char_device() || md.file_type().is_block_device() {
let ls_block_size = 1024; 0u64
} else {
md.blocks() * 512
};
match config.size_format { match config.size_format {
SizeFormat::Binary | SizeFormat::Decimal => md.blocks() * 512, SizeFormat::Binary | SizeFormat::Decimal => raw_blocks,
SizeFormat::Bytes => md.blocks() * 512 / ls_block_size, SizeFormat::Bytes => {
if cfg!(unix) {
if let Some(user_block_size) = config.block_size {
raw_blocks / user_block_size
} else {
raw_blocks / DEFAULT_BLOCK_SIZE
}
} else {
raw_blocks
}
}
} }
} }
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
// Silence linter warning about `config` being unused for windows.
let _ = config;
// no way to get block size for windows, fall-back to file size // no way to get block size for windows, fall-back to file size
md.len() md.len()
} }
@ -1860,7 +1999,7 @@ fn display_grid(
/// * `owner` ([`display_uname`], config-optional) /// * `owner` ([`display_uname`], config-optional)
/// * `group` ([`display_group`], config-optional) /// * `group` ([`display_group`], config-optional)
/// * `author` ([`display_uname`], config-optional) /// * `author` ([`display_uname`], config-optional)
/// * `size / rdev` ([`display_size_or_rdev`]) /// * `size / rdev` ([`display_len_or_rdev`])
/// * `system_time` ([`get_system_time`]) /// * `system_time` ([`get_system_time`])
/// * `file_name` ([`display_file_name`]) /// * `file_name` ([`display_file_name`])
/// ///
@ -1886,13 +2025,6 @@ fn display_item_long(
out: &mut BufWriter<Stdout>, out: &mut BufWriter<Stdout>,
) -> UResult<()> { ) -> UResult<()> {
if let Some(md) = item.md(out) { if let Some(md) = item.md(out) {
#[cfg(unix)]
{
if config.inode {
write!(out, "{} ", pad_left(&get_inode(md), padding.inode))?;
}
}
write!( write!(
out, out,
"{}{} {}", "{}{} {}",
@ -1941,7 +2073,7 @@ fn display_item_long(
)?; )?;
} }
match display_size_or_rdev(md, config) { match display_len_or_rdev(md, config) {
SizeOrDeviceId::Size(size) => { SizeOrDeviceId::Size(size) => {
write!(out, " {}", pad_left(&size, padding.size))?; write!(out, " {}", pad_left(&size, padding.size))?;
} }
@ -1971,18 +2103,10 @@ fn display_item_long(
} }
}; };
let dfn = display_file_name(item, config, None, 0, out).contents; let dfn = display_file_name(item, config, None, "".to_owned(), out).contents;
writeln!(out, " {} {}", display_date(md, config), dfn)?; writeln!(out, " {} {}", display_date(md, config), dfn)?;
} else { } else {
// this 'else' is expressly for the case of a dangling symlink/restricted file
#[cfg(unix)]
{
if config.inode {
write!(out, "{} ", pad_left("?", padding.inode))?;
}
}
#[cfg(unix)] #[cfg(unix)]
let leading_char = { let leading_char = {
if let Some(Some(ft)) = item.ft.get() { if let Some(Some(ft)) = item.ft.get() {
@ -2052,7 +2176,7 @@ fn display_item_long(
write!(out, " {}", pad_right("?", padding.uname))?; write!(out, " {}", pad_right("?", padding.uname))?;
} }
let dfn = display_file_name(item, config, None, 0, out).contents; let dfn = display_file_name(item, config, None, "".to_owned(), out).contents;
let date_len = 12; let date_len = 12;
writeln!( writeln!(
@ -2069,7 +2193,7 @@ fn display_item_long(
#[cfg(unix)] #[cfg(unix)]
fn get_inode(metadata: &Metadata) -> String { fn get_inode(metadata: &Metadata) -> String {
format!("{:8}", metadata.ino()) format!("{}", metadata.ino())
} }
// Currently getpwuid is `linux` target only. If it's broken out into // Currently getpwuid is `linux` target only. If it's broken out into
@ -2224,7 +2348,7 @@ enum SizeOrDeviceId {
Device(String, String), Device(String, String),
} }
fn display_size_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId { fn display_len_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId {
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
{ {
let ft = metadata.file_type(); let ft = metadata.file_type();
@ -2245,8 +2369,26 @@ fn display_size_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId
return SizeOrDeviceId::Device(major.to_string(), minor.to_string()); return SizeOrDeviceId::Device(major.to_string(), minor.to_string());
} }
} }
// Reported file len only adjusted for block_size when block_size is set
SizeOrDeviceId::Size(display_size(metadata.len(), config)) if let Some(user_block_size) = config.block_size {
// ordinary division of unsigned integers rounds down,
// this is similar to the Rust API for division that rounds up,
// currently in nightly only, however once
// https://github.com/rust-lang/rust/pull/88582 : "div_ceil"
// is stable we should use that instead
let len_adjusted = {
let d = metadata.len() / user_block_size;
let r = metadata.len() % user_block_size;
if r == 0 {
d
} else {
d + 1
}
};
SizeOrDeviceId::Size(display_size(len_adjusted, config))
} else {
SizeOrDeviceId::Size(display_size(metadata.len(), config))
}
} }
fn display_size(size: u64, config: &Config) -> String { fn display_size(size: u64, config: &Config) -> String {
@ -2312,7 +2454,7 @@ fn display_file_name(
path: &PathData, path: &PathData,
config: &Config, config: &Config,
prefix_context: Option<usize>, prefix_context: Option<usize>,
longest_inode_len: usize, more_info: String,
out: &mut BufWriter<Stdout>, out: &mut BufWriter<Stdout>,
) -> Cell { ) -> Cell {
// This is our return value. We start by `&path.display_name` and modify it along the way. // This is our return value. We start by `&path.display_name` and modify it along the way.
@ -2328,18 +2470,11 @@ fn display_file_name(
} }
} }
#[cfg(unix)] if config.format != Format::Long && !more_info.is_empty() {
{ // increment width here b/c name was given colors and name.width() is now the wrong
if config.inode && config.format != Format::Long { // size for display
let inode = match path.md(out) { width += more_info.width();
Some(md) => pad_left(&get_inode(md), longest_inode_len), name = more_info + &name;
None => pad_left("?", longest_inode_len),
};
// increment width here b/c name was given colors and name.width() is now the wrong
// size for display
width += inode.width();
name = inode + " " + &name;
}
} }
if config.indicator_style != IndicatorStyle::None { if config.indicator_style != IndicatorStyle::None {
@ -2465,16 +2600,9 @@ fn display_symlink_count(metadata: &Metadata) -> String {
metadata.nlink().to_string() metadata.nlink().to_string()
} }
#[allow(unused_variables)] #[cfg(unix)]
fn display_inode(metadata: &Metadata) -> String { fn display_inode(metadata: &Metadata) -> String {
#[cfg(unix)] get_inode(metadata)
{
get_inode(metadata)
}
#[cfg(not(unix))]
{
"".to_string()
}
} }
// This returns the SELinux security context as UTF8 `String`. // This returns the SELinux security context as UTF8 `String`.
@ -2531,29 +2659,48 @@ fn calculate_padding_collection(
size: 1, size: 1,
major: 1, major: 1,
minor: 1, minor: 1,
block_size: 1,
}; };
for item in items { for item in items {
let context_len = item.security_context.len(); #[cfg(unix)]
let (link_count_len, uname_len, group_len, size_len, major_len, minor_len, inode_len) = if config.inode {
display_dir_entry_size(item, config, out); let inode_len = if let Some(md) = item.md(out) {
padding_collections.inode = inode_len.max(padding_collections.inode); display_inode(md).len()
padding_collections.link_count = link_count_len.max(padding_collections.link_count); } else {
padding_collections.uname = uname_len.max(padding_collections.uname); continue;
padding_collections.group = group_len.max(padding_collections.group); };
if config.context { padding_collections.inode = inode_len.max(padding_collections.inode);
padding_collections.context = context_len.max(padding_collections.context);
} }
if items.len() == 1usize {
padding_collections.size = 0usize; if config.alloc_size {
padding_collections.major = 0usize; if let Some(md) = item.md(out) {
padding_collections.minor = 0usize; let block_size_len = display_size(get_block_size(md, config), config).len();
} else { padding_collections.block_size = block_size_len.max(padding_collections.block_size);
padding_collections.major = major_len.max(padding_collections.major); }
padding_collections.minor = minor_len.max(padding_collections.minor); }
padding_collections.size = size_len
.max(padding_collections.size) if config.format == Format::Long {
.max(padding_collections.major + padding_collections.minor + 2usize); let context_len = item.security_context.len();
let (link_count_len, uname_len, group_len, size_len, major_len, minor_len) =
display_dir_entry_size(item, config, out);
padding_collections.link_count = link_count_len.max(padding_collections.link_count);
padding_collections.uname = uname_len.max(padding_collections.uname);
padding_collections.group = group_len.max(padding_collections.group);
if config.context {
padding_collections.context = context_len.max(padding_collections.context);
}
if items.len() == 1usize {
padding_collections.size = 0usize;
padding_collections.major = 0usize;
padding_collections.minor = 0usize;
} else {
padding_collections.major = major_len.max(padding_collections.major);
padding_collections.minor = minor_len.max(padding_collections.minor);
padding_collections.size = size_len
.max(padding_collections.size)
.max(padding_collections.major + padding_collections.minor + 2usize);
}
} }
} }
@ -2572,11 +2719,19 @@ fn calculate_padding_collection(
group: 1, group: 1,
context: 1, context: 1,
size: 1, size: 1,
block_size: 1,
}; };
for item in items { for item in items {
if config.alloc_size {
if let Some(md) = item.md(out) {
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);
}
}
let context_len = item.security_context.len(); let context_len = item.security_context.len();
let (link_count_len, uname_len, group_len, size_len, _major_len, _minor_len, _inode_len) = let (link_count_len, uname_len, group_len, size_len, _major_len, _minor_len) =
display_dir_entry_size(item, config, out); display_dir_entry_size(item, config, out);
padding_collections.link_count = link_count_len.max(padding_collections.link_count); padding_collections.link_count = link_count_len.max(padding_collections.link_count);
padding_collections.uname = uname_len.max(padding_collections.uname); padding_collections.uname = uname_len.max(padding_collections.uname);

View file

@ -79,7 +79,224 @@ fn test_ls_ordering() {
.stdout_matches(&Regex::new("some-dir1:\\ntotal 0").unwrap()); .stdout_matches(&Regex::new("some-dir1:\\ntotal 0").unwrap());
} }
//#[cfg(all(feature = "mknod"))] #[cfg(all(feature = "truncate", feature = "dd"))]
#[test]
fn test_ls_allocation_size() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("some-dir1");
at.touch("some-dir1/empty-file");
#[cfg(unix)]
{
scene
.ccmd("truncate")
.arg("-s")
.arg("4M")
.arg("some-dir1/file-with-holes")
.succeeds();
// fill empty file with zeros
scene
.ccmd("dd")
.arg("--if=/dev/zero")
.arg("--of=some-dir1/zero-file")
.arg("bs=1024")
.arg("count=4096")
.succeeds();
scene
.ccmd("dd")
.arg("--if=/dev/zero")
.arg("--of=irregular-file")
.arg("bs=1")
.arg("count=777")
.succeeds();
scene
.ucmd()
.arg("-l")
.arg("--block-size=512")
.arg("irregular-file")
.succeeds()
.stdout_matches(&Regex::new("[^ ] 2 [^ ]").unwrap());
scene
.ucmd()
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_is("total 4096\n 0 empty-file\n 0 file-with-holes\n4096 zero-file\n");
scene
.ucmd()
.arg("-sl")
.arg("some-dir1")
.succeeds()
// block size is 0 whereas size/len is 4194304
.stdout_contains("4194304");
scene
.ucmd()
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_contains("0 empty-file")
.stdout_contains("4096 zero-file");
// Test alignment of different block sized files
let res = scene.ucmd().arg("-si1").arg("some-dir1").succeeds();
let empty_file_len = String::from_utf8(res.stdout().to_owned())
.ok()
.unwrap()
.lines()
.nth(1)
.unwrap()
.strip_suffix("empty-file")
.unwrap()
.len();
let file_with_holes_len = String::from_utf8(res.stdout().to_owned())
.ok()
.unwrap()
.lines()
.nth(2)
.unwrap()
.strip_suffix("file-with-holes")
.unwrap()
.len();
assert_eq!(empty_file_len, file_with_holes_len);
scene
.ucmd()
.env("LS_BLOCK_SIZE", "8K")
.env("BLOCK_SIZE", "4K")
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 512")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("512 zero-file");
scene
.ucmd()
.env("BLOCK_SIZE", "4K")
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 1024")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("1024 zero-file");
scene
.ucmd()
.env("BLOCK_SIZE", "4K")
.arg("-s1")
.arg("--si")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 4.2M")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("4.2M zero-file");
scene
.ucmd()
.env("BLOCK_SIZE", "4096")
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 1024")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("1024 zero-file");
scene
.ucmd()
.env("POSIXLY_CORRECT", "true")
.arg("-s1")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 8192")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("8192 zero-file");
// -k should make 'ls' ignore the env var
scene
.ucmd()
.env("BLOCK_SIZE", "4K")
.arg("-s1k")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 4096")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("4096 zero-file");
// but manually specified blocksize overrides -k
scene
.ucmd()
.arg("-s1k")
.arg("--block-size=4K")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 1024")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("1024 zero-file");
scene
.ucmd()
.arg("-s1")
.arg("--block-size=4K")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 1024")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("1024 zero-file");
// si option should always trump the human-readable option
scene
.ucmd()
.arg("-s1h")
.arg("--si")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 4.2M")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("4.2M zero-file");
scene
.ucmd()
.arg("-s1")
.arg("--block-size=human-readable")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 4.0M")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("4.0M zero-file");
scene
.ucmd()
.arg("-s1")
.arg("--block-size=si")
.arg("some-dir1")
.succeeds()
.stdout_contains("total 4.2M")
.stdout_contains("0 empty-file")
.stdout_contains("0 file-with-holes")
.stdout_contains("4.2M zero-file");
}
}
#[test] #[test]
fn test_ls_devices() { fn test_ls_devices() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
@ -107,7 +324,7 @@ fn test_ls_devices() {
.stdout_matches(&Regex::new("[^ ] 1, 3 [^ ]").unwrap()); .stdout_matches(&Regex::new("[^ ] 1, 3 [^ ]").unwrap());
} }
// Regex tests alignment against a file (stdout is a link to a tty) // Tests display alignment against a file (stdout is a link to a tty)
#[cfg(unix)] #[cfg(unix)]
{ {
let res = scene let res = scene
@ -2542,22 +2759,14 @@ fn test_ls_dangling_symlinks() {
.succeeds() .succeeds()
.stdout_contains("dangle"); .stdout_contains("dangle");
#[cfg(not(windows))]
scene scene
.ucmd() .ucmd()
.arg("-Li") .arg("-Li")
.arg("temp_dir") .arg("temp_dir")
.fails() .fails()
.stderr_contains("cannot access") .stderr_contains("cannot access")
.stdout_contains("? dangle"); .stderr_contains("No such file or directory")
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
#[cfg(windows)]
scene
.ucmd()
.arg("-Li")
.arg("temp_dir")
.succeeds()
.stdout_contains("dangle");
scene scene
.ucmd() .ucmd()