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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/uu/ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ name = "ls"
path = "src/main.rs"

[features]
feat_selinux = ["selinux"]
feat_selinux = ["selinux", "uucore/selinux"]
11 changes: 5 additions & 6 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1900,11 +1900,7 @@ impl PathData {
None => OnceCell::new(),
};

let security_context = if config.context {
get_security_context(config, &p_buf, must_dereference)
} else {
String::new()
};
let security_context = get_security_context(config, &p_buf, must_dereference);

Self {
md: OnceCell::new(),
Expand Down Expand Up @@ -3339,7 +3335,10 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -
Err(err) => {
// The Path couldn't be dereferenced, so return early and set exit code 1
// to indicate a minor error
show!(LsError::IOErrorContext(p_buf.to_path_buf(), err, false));
// Only show error when context display is requested to avoid duplicate messages
if config.context {
show!(LsError::IOErrorContext(p_buf.to_path_buf(), err, false));
}
return substitute_string;
}
Ok(_md) => (),
Expand Down
110 changes: 108 additions & 2 deletions tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4263,8 +4263,7 @@ fn test_ls_context_long() {
let line: Vec<_> = result.stdout_str().split(' ').collect();
assert!(line[0].ends_with('.'));
assert!(line[4].starts_with("unconfined_u"));
let s: Vec<_> = line[4].split(':').collect();
assert!(s.len() == 4);
validate_selinux_context(line[4]);
}
}

Expand Down Expand Up @@ -4298,6 +4297,113 @@ fn test_ls_context_format() {
}
}

/// Helper function to validate `SELinux` context format
#[cfg(feature = "feat_selinux")]
fn validate_selinux_context(context: &str) {
assert!(
context.contains(':'),
"Expected SELinux context format (user:role:type:level), got: {}",
context
);

assert_eq!(
context.split(':').count(),
4,
"SELinux context should have 4 components separated by colons, got: {}",
context
);
}

#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_selinux_context_format() {
if !uucore::selinux::is_selinux_enabled() {
println!("test skipped: Kernel has no support for SElinux context");
return;
}

let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.touch("file");
at.symlink_file("file", "link");

// Test that ls -lnZ properly shows the context
for file in ["file", "link"] {
let result = scene.ucmd().args(&["-lnZ", file]).succeeds();
let output = result.stdout_str();

let lines: Vec<&str> = output.lines().collect();
assert!(!lines.is_empty(), "Output should contain at least one line");

let first_line = lines[0];
let parts: Vec<&str> = first_line.split_whitespace().collect();
assert!(parts.len() >= 6, "Line should have at least 6 fields");

// The 5th field (0-indexed position 4) should contain the SELinux context
// Format: permissions links owner group context size date time name
let context = parts[4];
validate_selinux_context(context);
}
}

#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_selinux_context_indicator() {
if !uucore::selinux::is_selinux_enabled() {
println!("test skipped: Kernel has no support for SElinux context");
return;
}

let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.touch("file");
at.symlink_file("file", "link");

// Test that ls -l shows "." indicator for files with SELinux contexts
for file in ["file", "link"] {
let result = scene.ucmd().args(&["-l", file]).succeeds();
let output = result.stdout_str();

// The 11th character should be "." indicating SELinux context
// -rw-rw-r--. (permissions + context indicator)
let lines: Vec<&str> = output.lines().collect();
assert!(!lines.is_empty(), "Output should contain at least one line");

let first_line = lines[0];
let chars: Vec<char> = first_line.chars().collect();
assert!(
chars.len() >= 11,
"Line should be at least 11 characters long"
);

// The 11th character (0-indexed position 10) should be "." for SELinux context
assert_eq!(
chars[10], '.',
"Expected '.' indicator for SELinux context in position 11, got '{}' in line: {}",
chars[10], first_line
);
}

// Test that ls -lnZ properly shows the context
for file in ["file", "link"] {
let result = scene.ucmd().args(&["-lnZ", file]).succeeds();
let output = result.stdout_str();

let lines: Vec<&str> = output.lines().collect();
assert!(!lines.is_empty(), "Output should contain at least one line");

let first_line = lines[0];
let parts: Vec<&str> = first_line.split_whitespace().collect();
assert!(parts.len() >= 6, "Line should have at least 6 fields");

// The 5th field (0-indexed position 4) should contain the SELinux context
// Format: permissions links owner group context size date time name
validate_selinux_context(parts[4]);
}
}

#[test]
#[allow(non_snake_case)]
fn test_ls_a_A() {
Expand Down
Loading