1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-03 22:47:46 +00:00

Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion

Signed-off-by: Hanif Bin Ariffin <hanif.ariffin.4326@gmail.com>
This commit is contained in:
Hanif Bin Ariffin 2021-10-05 16:31:56 +08:00
commit c04a0185aa
16 changed files with 394 additions and 196 deletions

1
Cargo.lock generated
View file

@ -2629,6 +2629,7 @@ dependencies = [
"lscolors", "lscolors",
"number_prefix", "number_prefix",
"once_cell", "once_cell",
"selinux",
"term_grid", "term_grid",
"termsize", "termsize",
"unicode-width", "unicode-width",

View file

@ -148,7 +148,7 @@ feat_os_unix_musl = [
# NOTE: # NOTE:
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. # The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. # Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
feat_selinux = ["cp/selinux", "id/selinux", "selinux", "feat_require_selinux"] feat_selinux = ["cp/selinux", "id/selinux", "ls/selinux", "selinux", "feat_require_selinux"]
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`. # "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
# NOTE: # NOTE:
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time. # On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.

View file

@ -11,7 +11,7 @@ extern crate uucore;
use std::io::{stdin, Read}; use std::io::{stdin, Read};
use clap::App; use clap::App;
use uucore::encoding::Format; use uucore::{encoding::Format, error::UResult};
pub mod base_common; pub mod base_common;
@ -24,27 +24,22 @@ static ABOUT: &str = "
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format = Format::Base32; let format = Format::Base32;
let usage = usage(); let usage = usage();
let name = uucore::util_name();
let config_result: Result<base_common::Config, String> = let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args
let stdin_raw = stdin(); let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw); let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input( base_common::handle_input(
&mut input, &mut input,
@ -52,12 +47,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols, config.wrap_cols,
config.ignore_garbage, config.ignore_garbage,
config.decode, config.decode,
name, )
);
0
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(uucore::util_name(), VERSION, ABOUT) base_common::base_app(ABOUT)
} }

View file

@ -11,13 +11,16 @@ use std::io::{stdout, Read, Write};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::encoding::{wrap_print, Data, Format}; use uucore::encoding::{wrap_print, Data, Format};
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
use std::fs::File; use std::fs::File;
use std::io::{BufReader, Stdin}; use std::io::{BufReader, Stdin};
use std::path::Path; use std::path::Path;
use clap::{App, Arg}; use clap::{crate_version, App, Arg};
pub static BASE_CMD_PARSE_ERROR: i32 = 1;
// Config. // Config.
pub struct Config { pub struct Config {
@ -35,15 +38,14 @@ pub mod options {
} }
impl Config { impl Config {
pub fn from(app_name: &str, options: &clap::ArgMatches) -> Result<Config, String> { pub fn from(options: &clap::ArgMatches) -> UResult<Config> {
let file: Option<String> = match options.values_of(options::FILE) { let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => { Some(mut values) => {
let name = values.next().unwrap(); let name = values.next().unwrap();
if let Some(extra_op) = values.next() { if let Some(extra_op) = values.next() {
return Err(format!( return Err(UUsageError::new(
"extra operand {}\nTry '{} --help' for more information.", BASE_CMD_PARSE_ERROR,
extra_op.quote(), format!("extra operand {}", extra_op.quote(),),
app_name
)); ));
} }
@ -51,7 +53,10 @@ impl Config {
None None
} else { } else {
if !Path::exists(Path::new(name)) { if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name.maybe_quote())); return Err(USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("{}: No such file or directory", name.maybe_quote()),
));
} }
Some(name.to_owned()) Some(name.to_owned())
} }
@ -62,8 +67,12 @@ impl Config {
let cols = options let cols = options
.value_of(options::WRAP) .value_of(options::WRAP)
.map(|num| { .map(|num| {
num.parse::<usize>() num.parse::<usize>().map_err(|_| {
.map_err(|_| format!("invalid wrap size: {}", num.quote())) USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("invalid wrap size: {}", num.quote()),
)
})
}) })
.transpose()?; .transpose()?;
@ -76,23 +85,17 @@ impl Config {
} }
} }
pub fn parse_base_cmd_args( pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult<Config> {
args: impl uucore::Args, let app = base_app(about).usage(usage);
name: &str,
version: &str,
about: &str,
usage: &str,
) -> Result<Config, String> {
let app = base_app(name, version, about).usage(usage);
let arg_list = args let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
Config::from(name, &app.get_matches_from(arg_list)) Config::from(&app.get_matches_from(arg_list))
} }
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> { pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> {
App::new(name) App::new(uucore::util_name())
.version(version) .version(crate_version!())
.about(about) .about(about)
// Format arguments. // Format arguments.
.arg( .arg(
@ -121,14 +124,15 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static
.arg(Arg::with_name(options::FILE).index(1).multiple(true)) .arg(Arg::with_name(options::FILE).index(1).multiple(true))
} }
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> { pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> {
match &config.to_read { match &config.to_read {
Some(name) => { Some(name) => {
let file_buf = crash_if_err!(1, File::open(Path::new(name))); let file_buf =
Box::new(BufReader::new(file_buf)) // as Box<dyn Read> File::open(Path::new(name)).map_err_context(|| name.maybe_quote().to_string())?;
Ok(Box::new(BufReader::new(file_buf))) // as Box<dyn Read>
} }
None => { None => {
Box::new(stdin_ref.lock()) // as Box<dyn Read> Ok(Box::new(stdin_ref.lock())) // as Box<dyn Read>
} }
} }
} }
@ -139,8 +143,7 @@ pub fn handle_input<R: Read>(
line_wrap: Option<usize>, line_wrap: Option<usize>,
ignore_garbage: bool, ignore_garbage: bool,
decode: bool, decode: bool,
name: &str, ) -> UResult<()> {
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap { if let Some(wrap) = line_wrap {
data = data.line_wrap(wrap); data = data.line_wrap(wrap);
@ -150,28 +153,23 @@ pub fn handle_input<R: Read>(
match data.encode() { match data.encode() {
Ok(s) => { Ok(s) => {
wrap_print(&data, s); wrap_print(&data, s);
Ok(())
} }
Err(_) => { Err(_) => Err(USimpleError::new(
eprintln!( 1,
"{}: error: invalid input (length must be multiple of 4 characters)", "error: invalid input (length must be multiple of 4 characters)",
name )),
);
exit!(1)
}
} }
} else { } else {
match data.decode() { match data.decode() {
Ok(s) => { Ok(s) => {
if stdout().write_all(&s).is_err() { if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error // on windows console, writing invalid utf8 returns an error
eprintln!("{}: error: Cannot write non-utf8 data", name); return Err(USimpleError::new(1, "error: cannot write non-utf8 data"));
exit!(1)
} }
Ok(())
} }
Err(_) => { Err(_) => Err(USimpleError::new(1, "error: invalid input")),
eprintln!("{}: error: invalid input", name);
exit!(1)
}
} }
} }
} }

View file

@ -12,7 +12,7 @@ extern crate uucore;
use uu_base32::base_common; use uu_base32::base_common;
pub use uu_base32::uu_app; pub use uu_base32::uu_app;
use uucore::encoding::Format; use uucore::{encoding::Format, error::UResult};
use std::io::{stdin, Read}; use std::io::{stdin, Read};
@ -25,26 +25,22 @@ static ABOUT: &str = "
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format = Format::Base64; let format = Format::Base64;
let usage = usage(); let usage = usage();
let name = uucore::util_name();
let config_result: Result<base_common::Config, String> = let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args
let stdin_raw = stdin(); let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw); let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input( base_common::handle_input(
&mut input, &mut input,
@ -52,8 +48,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols, config.wrap_cols,
config.ignore_garbage, config.ignore_garbage,
config.decode, config.decode,
name, )
);
0
} }

View file

@ -11,10 +11,14 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{App, Arg};
use uu_base32::base_common::{self, Config}; use uu_base32::base_common::{self, Config, BASE_CMD_PARSE_ERROR};
use uucore::{encoding::Format, InvalidEncodingHandling}; use uucore::{
encoding::Format,
error::{UResult, UUsageError},
InvalidEncodingHandling,
};
use std::io::{stdin, Read}; use std::io::{stdin, Read};
@ -26,8 +30,6 @@ static ABOUT: &str = "
from any other non-alphabet bytes in the encoded stream. from any other non-alphabet bytes in the encoded stream.
"; ";
static BASE_CMD_PARSE_ERROR: i32 = 1;
const ENCODINGS: &[(&str, Format)] = &[ const ENCODINGS: &[(&str, Format)] = &[
("base64", Format::Base64), ("base64", Format::Base64),
("base64url", Format::Base64Url), ("base64url", Format::Base64Url),
@ -47,14 +49,14 @@ fn usage() -> String {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
let mut app = base_common::base_app(uucore::util_name(), crate_version!(), ABOUT); let mut app = base_common::base_app(ABOUT);
for encoding in ENCODINGS { for encoding in ENCODINGS {
app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
} }
app app
} }
fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) { fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> {
let usage = usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from( let matches = uu_app().usage(&usage[..]).get_matches_from(
args.collect_str(InvalidEncodingHandling::ConvertLossy) args.collect_str(InvalidEncodingHandling::ConvertLossy)
@ -63,24 +65,19 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
let format = ENCODINGS let format = ENCODINGS
.iter() .iter()
.find(|encoding| matches.is_present(encoding.0)) .find(|encoding| matches.is_present(encoding.0))
.unwrap_or_else(|| { .ok_or_else(|| UUsageError::new(BASE_CMD_PARSE_ERROR, "missing encoding type"))?
show_usage_error!("missing encoding type");
std::process::exit(1)
})
.1; .1;
( let config = Config::from(&matches)?;
Config::from("basenc", &matches).unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)), Ok((config, format))
format,
)
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
let name = uucore::util_name(); pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let (config, format) = parse_cmd_args(args); let (config, format) = parse_cmd_args(args)?;
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args
let stdin_raw = stdin(); let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw); let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input( base_common::handle_input(
&mut input, &mut input,
@ -88,8 +85,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols, config.wrap_cols,
config.ignore_garbage, config.ignore_garbage,
config.decode, config.decode,
name, )
);
0
} }

View file

@ -320,18 +320,19 @@ impl<'a> SplitWriter<'a> {
let l = line?; let l = line?;
match n.cmp(&(&ln + 1)) { match n.cmp(&(&ln + 1)) {
Ordering::Less => { Ordering::Less => {
if input_iter.add_line_to_buffer(ln, l).is_some() { assert!(
panic!("the buffer is big enough to contain 1 line"); input_iter.add_line_to_buffer(ln, l).is_none(),
} "the buffer is big enough to contain 1 line"
);
ret = Ok(()); ret = Ok(());
break; break;
} }
Ordering::Equal => { Ordering::Equal => {
if !self.options.suppress_matched assert!(
&& input_iter.add_line_to_buffer(ln, l).is_some() self.options.suppress_matched
{ || input_iter.add_line_to_buffer(ln, l).is_none(),
panic!("the buffer is big enough to contain 1 line"); "the buffer is big enough to contain 1 line"
} );
ret = Ok(()); ret = Ok(());
break; break;
} }
@ -378,9 +379,10 @@ impl<'a> SplitWriter<'a> {
match (self.options.suppress_matched, offset) { match (self.options.suppress_matched, offset) {
// no offset, add the line to the next split // no offset, add the line to the next split
(false, 0) => { (false, 0) => {
if input_iter.add_line_to_buffer(ln, l).is_some() { assert!(
panic!("the buffer is big enough to contain 1 line"); input_iter.add_line_to_buffer(ln, l).is_none(),
} "the buffer is big enough to contain 1 line"
);
} }
// a positive offset, some more lines need to be added to the current split // a positive offset, some more lines need to be added to the current split
(false, _) => self.writeln(l)?, (false, _) => self.writeln(l)?,
@ -425,9 +427,10 @@ impl<'a> SplitWriter<'a> {
if !self.options.suppress_matched { if !self.options.suppress_matched {
// add 1 to the buffer size to make place for the matched line // add 1 to the buffer size to make place for the matched line
input_iter.set_size_of_buffer(offset_usize + 1); input_iter.set_size_of_buffer(offset_usize + 1);
if input_iter.add_line_to_buffer(ln, l).is_some() { assert!(
panic!("should be big enough to hold every lines"); input_iter.add_line_to_buffer(ln, l).is_none(),
} "should be big enough to hold every lines"
);
} }
self.finish_split(); self.finish_split();
if input_iter.buffer_len() < offset_usize { if input_iter.buffer_len() < offset_usize {

View file

@ -35,13 +35,12 @@ fn unimplemented_flags_should_error_non_linux() {
} }
} }
if !succeeded.is_empty() { assert!(
panic!( succeeded.is_empty(),
"The following flags did not panic as expected: {:?}", "The following flags did not panic as expected: {:?}",
succeeded succeeded
); );
} }
}
#[test] #[test]
fn unimplemented_flags_should_error() { fn unimplemented_flags_should_error() {
@ -64,13 +63,12 @@ fn unimplemented_flags_should_error() {
} }
} }
if !succeeded.is_empty() { assert!(
panic!( succeeded.is_empty(),
"The following flags did not panic as expected: {:?}", "The following flags did not panic as expected: {:?}",
succeeded succeeded
); );
} }
}
#[test] #[test]
fn test_status_level_absent() { fn test_status_level_absent() {

View file

@ -27,6 +27,7 @@ uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", featu
uucore_procs = { version=">=0.0.6", package = "uucore_procs", path = "../../uucore_procs" } uucore_procs = { version=">=0.0.6", package = "uucore_procs", path = "../../uucore_procs" }
once_cell = "1.7.2" once_cell = "1.7.2"
atty = "0.2" atty = "0.2"
selinux = { version="0.2.1", optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
lazy_static = "1.4.0" lazy_static = "1.4.0"
@ -35,6 +36,9 @@ lazy_static = "1.4.0"
name = "ls" name = "ls"
path = "src/main.rs" path = "src/main.rs"
[features]
feat_selinux = ["selinux"]
[package.metadata.cargo-udeps.ignore] [package.metadata.cargo-udeps.ignore]
# Necessary for "make all" # Necessary for "make all"
normal = ["uucore_procs"] normal = ["uucore_procs"]

View file

@ -50,6 +50,11 @@ use unicode_width::UnicodeWidthStr;
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::{fs::display_permissions, version_cmp::version_cmp}; use uucore::{fs::display_permissions, version_cmp::version_cmp};
#[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
#[cfg(feature = "selinux")]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
fn usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
@ -129,6 +134,7 @@ pub mod options {
pub static FULL_TIME: &str = "full-time"; pub static FULL_TIME: &str = "full-time";
pub static HIDE: &str = "hide"; pub static HIDE: &str = "hide";
pub static IGNORE: &str = "ignore"; pub static IGNORE: &str = "ignore";
pub static CONTEXT: &str = "context";
} }
const DEFAULT_TERM_WIDTH: u16 = 80; const DEFAULT_TERM_WIDTH: u16 = 80;
@ -239,6 +245,8 @@ struct Config {
quoting_style: QuotingStyle, quoting_style: QuotingStyle,
indicator_style: IndicatorStyle, indicator_style: IndicatorStyle,
time_style: TimeStyle, time_style: TimeStyle,
context: bool,
selinux_supported: bool,
} }
// Fields that can be removed or added to the long format // Fields that can be removed or added to the long format
@ -250,9 +258,18 @@ struct LongFormat {
numeric_uid_gid: bool, numeric_uid_gid: bool,
} }
struct PaddingCollection {
longest_link_count_len: usize,
longest_uname_len: usize,
longest_group_len: usize,
longest_context_len: usize,
longest_size_len: usize,
}
impl Config { impl Config {
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn from(options: &clap::ArgMatches) -> UResult<Config> { fn from(options: &clap::ArgMatches) -> UResult<Config> {
let context = options.is_present(options::CONTEXT);
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) { let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
( (
match format_ { match format_ {
@ -596,6 +613,17 @@ impl Config {
quoting_style, quoting_style,
indicator_style, indicator_style,
time_style, time_style,
context,
selinux_supported: {
#[cfg(feature = "selinux")]
{
selinux::kernel_support() != selinux::KernelSupport::Unsupported
}
#[cfg(not(feature = "selinux"))]
{
false
}
},
}) })
} }
} }
@ -1157,6 +1185,12 @@ only ignore '.' and '..'.",
.overrides_with(options::FULL_TIME) .overrides_with(options::FULL_TIME)
.help("like -l --time-style=full-iso"), .help("like -l --time-style=full-iso"),
) )
.arg(
Arg::with_name(options::CONTEXT)
.short("Z")
.long(options::CONTEXT)
.help(CONTEXT_HELP_TEXT),
)
// Positional arguments // Positional arguments
.arg( .arg(
Arg::with_name(options::PATHS) Arg::with_name(options::PATHS)
@ -1181,6 +1215,7 @@ struct PathData {
// PathBuf that all above data corresponds to // PathBuf that all above data corresponds to
p_buf: PathBuf, p_buf: PathBuf,
must_dereference: bool, must_dereference: bool,
security_context: String,
} }
impl PathData { impl PathData {
@ -1224,12 +1259,19 @@ impl PathData {
None => OnceCell::new(), None => OnceCell::new(),
}; };
let security_context = if config.context {
get_security_context(config, &p_buf, must_dereference)
} else {
String::new()
};
Self { Self {
md: OnceCell::new(), md: OnceCell::new(),
ft, ft,
display_name, display_name,
p_buf, p_buf,
must_dereference, must_dereference,
security_context,
} }
} }
@ -1398,7 +1440,7 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
} }
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) { fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) {
// TODO: Cache/memoize 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() { if let Some(md) = entry.md() {
( (
display_symlink_count(md).len(), display_symlink_count(md).len(),
@ -1411,31 +1453,40 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, u
} }
} }
fn pad_left(string: String, count: usize) -> String { fn pad_left(string: &str, count: usize) -> String {
format!("{:>width$}", string, width = count) format!("{:>width$}", string, width = count)
} }
fn pad_right(string: String, count: usize) -> String { fn pad_right(string: &str, count: usize) -> String {
format!("{:<width$}", string, width = count) format!("{:<width$}", string, width = count)
} }
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) { fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
// `-Z`, `--context`:
// Display the SELinux security context or '?' if none is found. When used with the `-l`
// option, print the security context to the left of the size column.
if config.format == Format::Long { if config.format == Format::Long {
let ( let (
mut longest_link_count_len, mut longest_link_count_len,
mut longest_uname_len, mut longest_uname_len,
mut longest_group_len, mut longest_group_len,
mut longest_context_len,
mut longest_size_len, mut longest_size_len,
) = (1, 1, 1, 1); ) = (1, 1, 1, 1, 1);
let mut total_size = 0; let mut total_size = 0;
for item in items { for item in items {
let context_len = item.security_context.len();
let (link_count_len, uname_len, group_len, size_len) = let (link_count_len, uname_len, group_len, size_len) =
display_dir_entry_size(item, config); display_dir_entry_size(item, config);
longest_link_count_len = link_count_len.max(longest_link_count_len); longest_link_count_len = link_count_len.max(longest_link_count_len);
longest_size_len = size_len.max(longest_size_len); longest_size_len = size_len.max(longest_size_len);
longest_uname_len = uname_len.max(longest_uname_len); longest_uname_len = uname_len.max(longest_uname_len);
longest_group_len = group_len.max(longest_group_len); longest_group_len = group_len.max(longest_group_len);
if config.context {
longest_context_len = context_len.max(longest_context_len);
}
longest_size_len = size_len.max(longest_size_len); longest_size_len = size_len.max(longest_size_len);
total_size += item.md().map_or(0, |md| get_block_size(md, config)); total_size += item.md().map_or(0, |md| get_block_size(md, config));
} }
@ -1447,16 +1498,31 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
for item in items { for item in items {
display_item_long( display_item_long(
item, item,
PaddingCollection {
longest_link_count_len, longest_link_count_len,
longest_uname_len, longest_uname_len,
longest_group_len, longest_group_len,
longest_context_len,
longest_size_len, longest_size_len,
},
config, config,
out, out,
); );
} }
} else { } else {
let names = items.iter().filter_map(|i| display_file_name(i, config)); let mut longest_context_len = 1;
let prefix_context = if config.context {
for item in items {
let context_len = item.security_context.len();
longest_context_len = context_len.max(longest_context_len);
}
Some(longest_context_len)
} else {
None
};
let names = items
.iter()
.filter_map(|i| display_file_name(i, config, prefix_context));
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),
@ -1581,15 +1647,13 @@ fn display_grid(
/// longest_link_count_len: usize, /// longest_link_count_len: usize,
/// longest_uname_len: usize, /// longest_uname_len: usize,
/// longest_group_len: usize, /// longest_group_len: usize,
/// longest_context_len: usize,
/// longest_size_len: usize, /// longest_size_len: usize,
/// ``` /// ```
/// that decide the maximum possible character count of each field. /// that decide the maximum possible character count of each field.
fn display_item_long( fn display_item_long(
item: &PathData, item: &PathData,
longest_link_count_len: usize, padding: PaddingCollection,
longest_uname_len: usize,
longest_group_len: usize,
longest_size_len: usize,
config: &Config, config: &Config,
out: &mut BufWriter<Stdout>, out: &mut BufWriter<Stdout>,
) { ) {
@ -1610,16 +1674,23 @@ fn display_item_long(
let _ = write!( let _ = write!(
out, out,
"{} {}", "{}{} {}",
display_permissions(md, true), display_permissions(md, true),
pad_left(display_symlink_count(md), longest_link_count_len), if item.security_context.len() > 1 {
// GNU `ls` uses a "." character to indicate a file with a security context,
// but not other alternate access method.
"."
} else {
""
},
pad_left(&display_symlink_count(md), padding.longest_link_count_len),
); );
if config.long.owner { if config.long.owner {
let _ = write!( let _ = write!(
out, out,
" {}", " {}",
pad_right(display_uname(md, config), longest_uname_len) pad_right(&display_uname(md, config), padding.longest_uname_len)
); );
} }
@ -1627,7 +1698,15 @@ fn display_item_long(
let _ = write!( let _ = write!(
out, out,
" {}", " {}",
pad_right(display_group(md, config), longest_group_len) pad_right(&display_group(md, config), padding.longest_group_len)
);
}
if config.context {
let _ = write!(
out,
" {}",
pad_right(&item.security_context, padding.longest_context_len)
); );
} }
@ -1637,19 +1716,19 @@ fn display_item_long(
let _ = write!( let _ = write!(
out, out,
" {}", " {}",
pad_right(display_uname(md, config), longest_uname_len) pad_right(&display_uname(md, config), padding.longest_uname_len)
); );
} }
let _ = writeln!( let _ = writeln!(
out, out,
" {} {} {}", " {} {} {}",
pad_left(display_size_or_rdev(md, config), longest_size_len), pad_left(&display_size_or_rdev(md, config), padding.longest_size_len),
display_date(md, config), display_date(md, config),
// unwrap is fine because it fails when metadata is not available // unwrap is fine because it fails when metadata is not available
// but we already know that it is because it's checked at the // but we already know that it is because it's checked at the
// start of the function. // start of the function.
display_file_name(item, config).unwrap().contents, display_file_name(item, config, None).unwrap().contents,
); );
} }
@ -1873,21 +1952,22 @@ fn classify_file(path: &PathData) -> Option<char> {
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`]. /// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
/// * `config.format` to display symlink targets if `Format::Long`. This function is also /// * `config.format` to display symlink targets if `Format::Long`. This function is also
/// responsible for coloring symlink target names if `config.color` is specified. /// responsible for coloring symlink target names if `config.color` is specified.
/// * `config.context` to prepend security context to `name` if compiled with `feat_selinux`.
/// ///
/// Note that non-unicode sequences in symlink targets are dealt with using /// Note that non-unicode sequences in symlink targets are dealt with using
/// [`std::path::Path::to_string_lossy`]. /// [`std::path::Path::to_string_lossy`].
fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> { fn display_file_name(
path: &PathData,
config: &Config,
prefix_context: Option<usize>,
) -> Option<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.
let mut name = escape_name(&path.display_name, &config.quoting_style); let mut name = escape_name(&path.display_name, &config.quoting_style);
#[cfg(unix)] #[cfg(unix)]
{ {
if config.format != Format::Long && config.inode { if config.format != Format::Long && config.inode {
name = path name = path.md().map_or_else(|| "?".to_string(), get_inode) + " " + &name;
.md()
.map_or_else(|| "?".to_string(), |md| get_inode(md))
+ " "
+ &name;
} }
} }
@ -1968,6 +2048,20 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
} }
} }
// Prepend the security context to the `name` and adjust `width` in order
// to get correct alignment from later calls to`display_grid()`.
if config.context {
if let Some(pad_count) = prefix_context {
let security_context = if !matches!(config.format, Format::Commas) {
pad_left(&path.security_context, pad_count)
} else {
path.security_context.to_owned()
};
name = format!("{} {}", security_context, name);
width += security_context.len() + 1;
}
}
Some(Cell { Some(Cell {
contents: name, contents: name,
width, width,
@ -1992,3 +2086,44 @@ fn display_symlink_count(_metadata: &Metadata) -> String {
fn display_symlink_count(metadata: &Metadata) -> String { fn display_symlink_count(metadata: &Metadata) -> String {
metadata.nlink().to_string() metadata.nlink().to_string()
} }
// This returns the SELinux security context as UTF8 `String`.
// In the long term this should be changed to `OsStr`, see discussions at #2621/#2656
#[allow(unused_variables)]
fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> String {
let substitute_string = "?".to_string();
if config.selinux_supported {
#[cfg(feature = "selinux")]
{
match selinux::SecurityContext::of_path(p_buf, must_dereference, false) {
Err(_r) => {
// TODO: show the actual reason why it failed
show_warning!("failed to get security context of: {}", p_buf.quote());
substitute_string
}
Ok(None) => substitute_string,
Ok(Some(context)) => {
let mut context = context.as_bytes();
if context.ends_with(&[0]) {
// TODO: replace with `strip_prefix()` when MSRV >= 1.51
context = &context[..context.len() - 1]
};
String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
show_warning!(
"getting security context of: {}: {}",
p_buf.quote(),
e.to_string()
);
String::from_utf8_lossy(context).into_owned()
})
}
}
}
#[cfg(not(feature = "selinux"))]
{
substitute_string
}
} else {
substitute_string
}
}

View file

@ -210,7 +210,7 @@ fn reset_term(stdout: &mut std::io::Stdout) {
#[inline(always)] #[inline(always)]
fn reset_term(_: &mut usize) {} fn reset_term(_: &mut usize) {}
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) { fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap(); let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols)); let lines = break_buff(buff, usize::from(cols));
@ -232,7 +232,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo
code: KeyCode::Char('c'), code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL, modifiers: KeyModifiers::CONTROL,
}) => { }) => {
reset_term(&mut stdout); reset_term(stdout);
std::process::exit(0); std::process::exit(0);
} }
Event::Key(KeyEvent { Event::Key(KeyEvent {

View file

@ -145,13 +145,12 @@ impl OutputInfo {
byte_size_block: usize, byte_size_block: usize,
print_width_block: usize, print_width_block: usize,
) -> [usize; MAX_BYTES_PER_UNIT] { ) -> [usize; MAX_BYTES_PER_UNIT] {
if byte_size_block > MAX_BYTES_PER_UNIT { assert!(
panic!( byte_size_block <= MAX_BYTES_PER_UNIT,
"{}-bits types are unsupported. Current max={}-bits.", "{}-bits types are unsupported. Current max={}-bits.",
8 * byte_size_block, 8 * byte_size_block,
8 * MAX_BYTES_PER_UNIT 8 * MAX_BYTES_PER_UNIT
); );
}
let mut spacing = [0; MAX_BYTES_PER_UNIT]; let mut spacing = [0; MAX_BYTES_PER_UNIT];
let mut byte_size = sf.byte_size(); let mut byte_size = sf.byte_size();

View file

@ -825,7 +825,7 @@ impl FieldSelector {
fn parse(key: &str, global_settings: &GlobalSettings) -> UResult<Self> { fn parse(key: &str, global_settings: &GlobalSettings) -> UResult<Self> {
let mut from_to = key.split(','); let mut from_to = key.split(',');
let (from, from_options) = Self::split_key_options(from_to.next().unwrap()); let (from, from_options) = Self::split_key_options(from_to.next().unwrap());
let to = from_to.next().map(|to| Self::split_key_options(to)); let to = from_to.next().map(Self::split_key_options);
let options_are_empty = from_options.is_empty() && matches!(to, None | Some((_, ""))); let options_are_empty = from_options.is_empty() && matches!(to, None | Some((_, "")));
if options_are_empty { if options_are_empty {

View file

@ -113,12 +113,18 @@ fn test_wrap_bad_arg() {
#[test] #[test]
fn test_base32_extra_operand() { fn test_base32_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified. // Expect a failure when multiple files are specified.
new_ucmd!() ts.ucmd()
.arg("a.txt") .arg("a.txt")
.arg("b.txt") .arg("b.txt")
.fails() .fails()
.stderr_only("base32: extra operand 'b.txt'\nTry 'base32 --help' for more information."); .stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
} }
#[test] #[test]

View file

@ -95,12 +95,18 @@ fn test_wrap_bad_arg() {
#[test] #[test]
fn test_base64_extra_operand() { fn test_base64_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified. // Expect a failure when multiple files are specified.
new_ucmd!() ts.ucmd()
.arg("a.txt") .arg("a.txt")
.arg("b.txt") .arg("b.txt")
.fails() .fails()
.stderr_only("base64: extra operand 'b.txt'\nTry 'base64 --help' for more information."); .stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
} }
#[test] #[test]

View file

@ -347,6 +347,7 @@ fn test_ls_long_format() {
// A line of the output should be: // A line of the output should be:
// One of the characters -bcCdDlMnpPsStTx? // One of the characters -bcCdDlMnpPsStTx?
// rwx, with - for missing permissions, thrice. // rwx, with - for missing permissions, thrice.
// Zero or one "." for indicating a file with security context
// A number, preceded by column whitespace, and followed by a single space. // A number, preceded by column whitespace, and followed by a single space.
// A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd). // A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
// A number, followed by a single space. // A number, followed by a single space.
@ -356,13 +357,13 @@ fn test_ls_long_format() {
// and followed by a single space. // and followed by a single space.
// Whatever comes after is irrelevant to this specific test. // Whatever comes after is irrelevant to this specific test.
scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new( scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3} +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ " r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}\.? +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
).unwrap()); ).unwrap());
} }
// This checks for the line with the .. entry. The uname and group should be digits. // This checks for the line with the .. entry. The uname and group should be digits.
scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new( scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\nd([r-][w-][xt-]){3} +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\." r"\nd([r-][w-][xt-]){3}\.? +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
).unwrap()); ).unwrap());
} }
@ -370,6 +371,7 @@ fn test_ls_long_format() {
/// This test is mainly about coloring, but, the recursion, symlink `->` processing, /// This test is mainly about coloring, but, the recursion, symlink `->` processing,
/// and `.` and `..` being present in `-a` all need to work for the test to pass. /// and `.` and `..` being present in `-a` all need to work for the test to pass.
/// This test does not really test anything provided by `-l` but the file names and symlinks. /// This test does not really test anything provided by `-l` but the file names and symlinks.
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
#[test] #[test]
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))] #[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
fn test_ls_long_symlink_color() { fn test_ls_long_symlink_color() {
@ -636,55 +638,57 @@ fn test_ls_long_formats() {
let at = &scene.fixtures; let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long-formats")); at.touch(&at.plus_as_string("test-long-formats"));
// Zero or one "." for indicating a file with security context
// Regex for three names, so all of author, group and owner // Regex for three names, so all of author, group and owner
let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap(); let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
#[cfg(unix)] #[cfg(unix)]
let re_three_num = Regex::new(r"[xrw-]{9} \d (\d+ ){3}0").unwrap(); let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
// Regex for two names, either: // Regex for two names, either:
// - group and owner // - group and owner
// - author and owner // - author and owner
// - author and group // - author and group
let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap(); let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
#[cfg(unix)] #[cfg(unix)]
let re_two_num = Regex::new(r"[xrw-]{9} \d (\d+ ){2}0").unwrap(); let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
// Regex for one name: author, group or owner // Regex for one name: author, group or owner
let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap(); let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
#[cfg(unix)] #[cfg(unix)]
let re_one_num = Regex::new(r"[xrw-]{9} \d \d+ 0").unwrap(); let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
// Regex for no names // Regex for no names
let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap(); let re_zero = Regex::new(r"[xrw-]{9}\.? \d 0").unwrap();
let result = scene scene
.ucmd() .ucmd()
.arg("-l") .arg("-l")
.arg("--author") .arg("--author")
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_three.is_match(result.stdout_str())); .stdout_matches(&re_three);
let result = scene scene
.ucmd() .ucmd()
.arg("-l1") .arg("-l1")
.arg("--author") .arg("--author")
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_three.is_match(result.stdout_str())); .stdout_matches(&re_three);
#[cfg(unix)] #[cfg(unix)]
{ {
let result = scene scene
.ucmd() .ucmd()
.arg("-n") .arg("-n")
.arg("--author") .arg("--author")
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_three_num.is_match(result.stdout_str())); .stdout_matches(&re_three_num);
} }
for arg in &[ for arg in &[
@ -694,22 +698,22 @@ fn test_ls_long_formats() {
"-lG --author", // only author and owner "-lG --author", // only author and owner
"-l --no-group --author", // only author and owner "-l --no-group --author", // only author and owner
] { ] {
let result = scene scene
.ucmd() .ucmd()
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_two.is_match(result.stdout_str())); .stdout_matches(&re_two);
#[cfg(unix)] #[cfg(unix)]
{ {
let result = scene scene
.ucmd() .ucmd()
.arg("-n") .arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_two_num.is_match(result.stdout_str())); .stdout_matches(&re_two_num);
} }
} }
@ -723,22 +727,22 @@ fn test_ls_long_formats() {
"-l --no-group", // only owner "-l --no-group", // only owner
"-gG --author", // only author "-gG --author", // only author
] { ] {
let result = scene scene
.ucmd() .ucmd()
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_one.is_match(result.stdout_str())); .stdout_matches(&re_one);
#[cfg(unix)] #[cfg(unix)]
{ {
let result = scene scene
.ucmd() .ucmd()
.arg("-n") .arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_one_num.is_match(result.stdout_str())); .stdout_matches(&re_one_num);
} }
} }
@ -755,22 +759,22 @@ fn test_ls_long_formats() {
"-og1", "-og1",
"-og1l", "-og1l",
] { ] {
let result = scene scene
.ucmd() .ucmd()
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_zero.is_match(result.stdout_str())); .stdout_matches(&re_zero);
#[cfg(unix)] #[cfg(unix)]
{ {
let result = scene scene
.ucmd() .ucmd()
.arg("-n") .arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>()) .args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats") .arg("test-long-formats")
.succeeds(); .succeeds()
assert!(re_zero.is_match(result.stdout_str())); .stdout_matches(&re_zero);
} }
} }
} }
@ -1248,7 +1252,7 @@ fn test_ls_inode() {
at.touch(file); at.touch(file);
let re_short = Regex::new(r" *(\d+) test_inode").unwrap(); let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap(); let re_long = Regex::new(r" *(\d+) [xrw-]{10}\.? \d .+ test_inode").unwrap();
let result = scene.ucmd().arg("test_inode").arg("-i").succeeds(); let result = scene.ucmd().arg("test_inode").arg("-i").succeeds();
assert!(re_short.is_match(result.stdout_str())); assert!(re_short.is_match(result.stdout_str()));
@ -2272,3 +2276,68 @@ fn test_ls_dangling_symlinks() {
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display .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" }); .stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
} }
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context1() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let file = "test_ls_context_file";
let expected = format!("unconfined_u:object_r:user_tmp_t:s0 {}\n", file);
let (at, mut ucmd) = at_and_ucmd!();
at.touch(file);
ucmd.args(&["-Z", file]).succeeds().stdout_is(expected);
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context2() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
for c_flag in &["-Z", "--context"] {
ts.ucmd()
.args(&[c_flag, &"/"])
.succeeds()
.stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag, &"/"])).stdout_str());
}
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context_format() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
// NOTE:
// --format=long/verbose matches the output of GNU's ls for --context
// except for the size count which may differ to the size count reported by GNU's ls.
for word in &[
"across",
"commas",
"horizontal",
// "long",
"single-column",
// "verbose",
"vertical",
] {
let format = format!("--format={}", word);
ts.ucmd()
.args(&[&"-Z", &format.as_str(), &"/"])
.succeeds()
.stdout_only(
unwrap_or_return!(expected_result(&ts, &[&"-Z", &format.as_str(), &"/"]))
.stdout_str(),
);
}
}