mirror of
https://github.com/uutils/coreutils
synced 2024-11-05 14:21:32 +00:00
Merge pull request #2656 from jhscheer/ls_selinux
`ls`: add support for showing SELinux context (--context/-Z)
This commit is contained in:
commit
b2fa51ddd9
5 changed files with 272 additions and 59 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2608,6 +2608,7 @@ dependencies = [
|
|||
"lscolors",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
"selinux",
|
||||
"term_grid",
|
||||
"termsize",
|
||||
"unicode-width",
|
||||
|
|
|
@ -148,7 +148,7 @@ feat_os_unix_musl = [
|
|||
# NOTE:
|
||||
# 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.
|
||||
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`.
|
||||
# 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.
|
||||
|
|
|
@ -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" }
|
||||
once_cell = "1.7.2"
|
||||
atty = "0.2"
|
||||
selinux = { version="0.2.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
|
@ -35,6 +36,9 @@ lazy_static = "1.4.0"
|
|||
name = "ls"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
feat_selinux = ["selinux"]
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -50,6 +50,11 @@ use unicode_width::UnicodeWidthStr;
|
|||
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
|
||||
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 {
|
||||
format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
|
||||
}
|
||||
|
@ -129,6 +134,7 @@ pub mod options {
|
|||
pub static FULL_TIME: &str = "full-time";
|
||||
pub static HIDE: &str = "hide";
|
||||
pub static IGNORE: &str = "ignore";
|
||||
pub static CONTEXT: &str = "context";
|
||||
}
|
||||
|
||||
const DEFAULT_TERM_WIDTH: u16 = 80;
|
||||
|
@ -239,6 +245,8 @@ struct Config {
|
|||
quoting_style: QuotingStyle,
|
||||
indicator_style: IndicatorStyle,
|
||||
time_style: TimeStyle,
|
||||
context: bool,
|
||||
selinux_supported: bool,
|
||||
}
|
||||
|
||||
// Fields that can be removed or added to the long format
|
||||
|
@ -250,9 +258,18 @@ struct LongFormat {
|
|||
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 {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
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) {
|
||||
(
|
||||
match format_ {
|
||||
|
@ -596,6 +613,17 @@ impl Config {
|
|||
quoting_style,
|
||||
indicator_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)
|
||||
.help("like -l --time-style=full-iso"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::CONTEXT)
|
||||
.short("Z")
|
||||
.long(options::CONTEXT)
|
||||
.help(CONTEXT_HELP_TEXT),
|
||||
)
|
||||
// Positional arguments
|
||||
.arg(
|
||||
Arg::with_name(options::PATHS)
|
||||
|
@ -1181,6 +1215,7 @@ struct PathData {
|
|||
// PathBuf that all above data corresponds to
|
||||
p_buf: PathBuf,
|
||||
must_dereference: bool,
|
||||
security_context: String,
|
||||
}
|
||||
|
||||
impl PathData {
|
||||
|
@ -1224,12 +1259,19 @@ impl PathData {
|
|||
None => OnceCell::new(),
|
||||
};
|
||||
|
||||
let security_context = if config.context {
|
||||
get_security_context(config, &p_buf, must_dereference)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
Self {
|
||||
md: OnceCell::new(),
|
||||
ft,
|
||||
display_name,
|
||||
p_buf,
|
||||
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) {
|
||||
// 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() {
|
||||
(
|
||||
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)
|
||||
}
|
||||
|
||||
fn pad_right(string: String, count: usize) -> String {
|
||||
fn pad_right(string: &str, count: usize) -> String {
|
||||
format!("{:<width$}", string, width = count)
|
||||
}
|
||||
|
||||
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 {
|
||||
let (
|
||||
mut longest_link_count_len,
|
||||
mut longest_uname_len,
|
||||
mut longest_group_len,
|
||||
mut longest_context_len,
|
||||
mut longest_size_len,
|
||||
) = (1, 1, 1, 1);
|
||||
) = (1, 1, 1, 1, 1);
|
||||
let mut total_size = 0;
|
||||
|
||||
for item in items {
|
||||
let context_len = item.security_context.len();
|
||||
let (link_count_len, uname_len, group_len, size_len) =
|
||||
display_dir_entry_size(item, config);
|
||||
longest_link_count_len = link_count_len.max(longest_link_count_len);
|
||||
longest_size_len = size_len.max(longest_size_len);
|
||||
longest_uname_len = uname_len.max(longest_uname_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);
|
||||
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 {
|
||||
display_item_long(
|
||||
item,
|
||||
longest_link_count_len,
|
||||
longest_uname_len,
|
||||
longest_group_len,
|
||||
longest_size_len,
|
||||
PaddingCollection {
|
||||
longest_link_count_len,
|
||||
longest_uname_len,
|
||||
longest_group_len,
|
||||
longest_context_len,
|
||||
longest_size_len,
|
||||
},
|
||||
config,
|
||||
out,
|
||||
);
|
||||
}
|
||||
} 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 {
|
||||
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
|
||||
|
@ -1581,15 +1647,13 @@ fn display_grid(
|
|||
/// longest_link_count_len: usize,
|
||||
/// longest_uname_len: usize,
|
||||
/// longest_group_len: usize,
|
||||
/// longest_context_len: usize,
|
||||
/// longest_size_len: usize,
|
||||
/// ```
|
||||
/// that decide the maximum possible character count of each field.
|
||||
fn display_item_long(
|
||||
item: &PathData,
|
||||
longest_link_count_len: usize,
|
||||
longest_uname_len: usize,
|
||||
longest_group_len: usize,
|
||||
longest_size_len: usize,
|
||||
padding: PaddingCollection,
|
||||
config: &Config,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
) {
|
||||
|
@ -1610,16 +1674,23 @@ fn display_item_long(
|
|||
|
||||
let _ = write!(
|
||||
out,
|
||||
"{} {}",
|
||||
"{}{} {}",
|
||||
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 {
|
||||
let _ = write!(
|
||||
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!(
|
||||
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!(
|
||||
out,
|
||||
" {}",
|
||||
pad_right(display_uname(md, config), longest_uname_len)
|
||||
pad_right(&display_uname(md, config), padding.longest_uname_len)
|
||||
);
|
||||
}
|
||||
|
||||
let _ = writeln!(
|
||||
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),
|
||||
// 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, None).unwrap().contents,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1873,10 +1952,15 @@ fn classify_file(path: &PathData) -> Option<char> {
|
|||
/// * `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
|
||||
/// 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
|
||||
/// [`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.
|
||||
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
||||
|
||||
|
@ -1968,6 +2052,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 {
|
||||
contents: name,
|
||||
width,
|
||||
|
@ -1992,3 +2090,44 @@ fn display_symlink_count(_metadata: &Metadata) -> String {
|
|||
fn display_symlink_count(metadata: &Metadata) -> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -347,6 +347,7 @@ fn test_ls_long_format() {
|
|||
// A line of the output should be:
|
||||
// One of the characters -bcCdDlMnpPsStTx?
|
||||
// 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 username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
|
||||
// A number, followed by a single space.
|
||||
|
@ -356,13 +357,13 @@ fn test_ls_long_format() {
|
|||
// and followed by a single space.
|
||||
// Whatever comes after is irrelevant to this specific test.
|
||||
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());
|
||||
}
|
||||
|
||||
// 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(
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -370,6 +371,7 @@ fn test_ls_long_format() {
|
|||
/// 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.
|
||||
/// 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]
|
||||
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
|
||||
fn test_ls_long_symlink_color() {
|
||||
|
@ -636,55 +638,57 @@ fn test_ls_long_formats() {
|
|||
let at = &scene.fixtures;
|
||||
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
|
||||
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)]
|
||||
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:
|
||||
// - group and owner
|
||||
// - author and owner
|
||||
// - 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)]
|
||||
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
|
||||
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)]
|
||||
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
|
||||
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()
|
||||
.arg("-l")
|
||||
.arg("--author")
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_three.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_three);
|
||||
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l1")
|
||||
.arg("--author")
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_three.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_three);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-n")
|
||||
.arg("--author")
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_three_num.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_three_num);
|
||||
}
|
||||
|
||||
for arg in &[
|
||||
|
@ -694,22 +698,22 @@ fn test_ls_long_formats() {
|
|||
"-lG --author", // only author and owner
|
||||
"-l --no-group --author", // only author and owner
|
||||
] {
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_two.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_two);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-n")
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_two_num.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_two_num);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,22 +727,22 @@ fn test_ls_long_formats() {
|
|||
"-l --no-group", // only owner
|
||||
"-gG --author", // only author
|
||||
] {
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_one.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_one);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-n")
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_one_num.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_one_num);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -755,22 +759,22 @@ fn test_ls_long_formats() {
|
|||
"-og1",
|
||||
"-og1l",
|
||||
] {
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_zero.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_zero);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = scene
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-n")
|
||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||
.arg("test-long-formats")
|
||||
.succeeds();
|
||||
assert!(re_zero.is_match(result.stdout_str()));
|
||||
.succeeds()
|
||||
.stdout_matches(&re_zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1248,7 +1252,7 @@ fn test_ls_inode() {
|
|||
at.touch(file);
|
||||
|
||||
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();
|
||||
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
|
||||
.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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue