Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(exa PR) 855: Add option to show security attribute and improve extended support #61

Merged
merged 3 commits into from Jul 29, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -74,6 +74,7 @@ These options are available when running with `--long` (`-l`):
- **-t**, **--time=(field)**: which timestamp field to use
- **-u**, **--accessed**: use the accessed timestamp field
- **-U**, **--created**: use the created timestamp field
- **-Z**, **--context**: list each file’s security context
- **-@**, **--extended**: list each file’s extended attributes and sizes
- **--changed**: use the changed timestamp field
- **--git**: list each file’s Git status, if tracked or ignored
Expand Down
1 change: 1 addition & 0 deletions completions/fish/exa.fish
Expand Up @@ -89,3 +89,4 @@ complete -c exa -l 'no-time' -d "Suppress the time field"
# Optional extras
complete -c exa -l 'git' -d "List each file's Git status, if tracked"
complete -c exa -s '@' -l 'extended' -d "List each file's extended attributes and sizes"
complete -c exa -s 'Z' -l 'context' -d "List each file's security context"
1 change: 1 addition & 0 deletions completions/zsh/_exa
Expand Up @@ -53,6 +53,7 @@ __exa() {
{-U,--created}"[Use the created timestamp field]" \
--git"[List each file's Git status, if tracked]" \
{-@,--extended}"[List each file's extended attributes and sizes]" \
{-Z,--context}"[List each file's security context]" \
'*:filename:_files'
}

Expand Down
3 changes: 3 additions & 0 deletions man/exa.1.md
Expand Up @@ -180,6 +180,9 @@ These options are available when running with `--long` (`-l`):
`-@`, `--extended`
: List each file’s extended attributes and sizes.

`-Z`, `--context`
: List each file's security context.

`--git` [if exa was built with git support]
: List each file’s Git status, if tracked.

Expand Down
172 changes: 121 additions & 51 deletions src/fs/feature/xattr.rs
Expand Up @@ -3,6 +3,7 @@
#![allow(trivial_casts)] // for ARM

use std::cmp::Ordering;
use std::ffi::CString;
use std::io;
use std::path::Path;

Expand Down Expand Up @@ -50,58 +51,98 @@ pub enum FollowSymlinks {
#[derive(Debug, Clone)]
pub struct Attribute {
pub name: String,
pub size: usize,
pub value: String,
}


#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
use std::ffi::CString;
fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Result<Vec<Attribute>> {
const SELINUX_XATTR_NAME: &str = "security.selinux";
const ENODATA: i32 = 61;

let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
Some(cstring) => cstring,
None => {
return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?"));
}
let c_attr_name = CString::new(SELINUX_XATTR_NAME).map_err(|e| {
io::Error::new(io::ErrorKind::Other, e)
})?;
let size = lister.getxattr_first(c_path, &c_attr_name);

let size = match size.cmp(&0) {
Ordering::Less => {
let e = io::Error::last_os_error();

if e.kind() == io::ErrorKind::Other && e.raw_os_error() == Some(ENODATA) {
return Ok(Vec::new())
}

return Err(e)
},
Ordering::Equal => return Err(io::Error::from(io::ErrorKind::InvalidData)),
Ordering::Greater => size as usize,
};

let mut buf_value = vec![0_u8; size];
let size = lister.getxattr_second(c_path, &c_attr_name, &mut buf_value, size);

match size.cmp(&0) {
Ordering::Less => return Err(io::Error::last_os_error()),
Ordering::Equal => return Err(io::Error::from(io::ErrorKind::InvalidData)),
Ordering::Greater => (),
}

Ok(vec![Attribute {
name: String::from(SELINUX_XATTR_NAME),
value: lister.translate_attribute_data(&buf_value),
}])
}

pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
let c_path = CString::new(path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, "Error: path not convertible to string"))?).map_err(|e| {
io::Error::new(io::ErrorKind::Other, e)
})?;

let bufsize = lister.listxattr_first(&c_path);
match bufsize.cmp(&0) {
let bufsize = match bufsize.cmp(&0) {
Ordering::Less => return Err(io::Error::last_os_error()),
Ordering::Equal => return Ok(Vec::new()),
Ordering::Greater => {},
}
// Some filesystems, like sysfs, return nothing on listxattr, even though the security
// attribute is set.
Ordering::Equal => return get_secattr(lister, &c_path),
Ordering::Greater => bufsize as usize,
};

let mut buf = vec![0_u8; bufsize as usize];
let err = lister.listxattr_second(&c_path, &mut buf, bufsize);
let mut buf = vec![0_u8; bufsize];

match err.cmp(&0) {
match lister.listxattr_second(&c_path, &mut buf, bufsize).cmp(&0) {
Ordering::Less => return Err(io::Error::last_os_error()),
Ordering::Equal => return Ok(Vec::new()),
Ordering::Greater => {},
}

let mut names = Vec::new();
if err > 0 {
// End indices of the attribute names
// the buffer contains 0-terminated c-strings
let idx = buf.iter().enumerate().filter_map(|(i, v)|
if *v == 0 { Some(i) } else { None }
);
let mut start = 0;

for end in idx {
let c_end = end + 1; // end of the c-string (including 0)
let size = lister.getxattr(&c_path, &buf[start..c_end]);

if size > 0 {
names.push(Attribute {
name: lister.translate_attribute_name(&buf[start..end]),
size: size as usize,
});

for attr_name in buf.split(|c| c == &0) {
if attr_name.is_empty() {
continue;
}

let c_attr_name = CString::new(attr_name).map_err(|e| {
io::Error::new(io::ErrorKind::Other, e)
})?;
let size = lister.getxattr_first(&c_path, &c_attr_name);

if size > 0 {
let mut buf_value = vec![0_u8; size as usize];
if lister.getxattr_second(&c_path, &c_attr_name, &mut buf_value, size as usize) < 0 {
return Err(io::Error::last_os_error());
}

start = c_end;
names.push(Attribute {
name: lister.translate_attribute_data(attr_name),
value: lister.translate_attribute_data(&buf_value),
});
} else {
names.push(Attribute {
name: lister.translate_attribute_data(attr_name),
value: String::new(),
});
}
}

Expand Down Expand Up @@ -148,8 +189,8 @@ mod lister {
Self { c_flags }
}

pub fn translate_attribute_name(&self, input: &[u8]) -> String {
unsafe { std::str::from_utf8_unchecked(input).into() }
pub fn translate_attribute_data(&self, input: &[u8]) -> String {
unsafe { std::str::from_utf8_unchecked(input).trim_end_matches('\0').into() }
}

pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
Expand All @@ -163,29 +204,42 @@ mod lister {
}
}

pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
unsafe {
listxattr(
c_path.as_ptr(),
buf.as_mut_ptr().cast::<c_char>(),
bufsize as size_t,
buf.as_mut_ptr().cast(),
bufsize,
self.c_flags,
)
}
}

pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
pub fn getxattr_first(&self, c_path: &CString, c_name: &CString) -> ssize_t {
unsafe {
getxattr(
c_path.as_ptr(),
buf.as_ptr().cast::<c_char>(),
c_name.as_ptr().cast(),
ptr::null_mut(),
0,
0,
self.c_flags,
)
}
}

pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
unsafe {
getxattr(
c_path.as_ptr(),
c_name.as_ptr().cast(),
buf.as_mut_ptr().cast::<libc::c_void>(),
bufsize,
0,
self.c_flags,
)
}
}
}
}

Expand Down Expand Up @@ -234,8 +288,8 @@ mod lister {
Lister { follow_symlinks }
}

pub fn translate_attribute_name(&self, input: &[u8]) -> String {
String::from_utf8_lossy(input).into_owned()
pub fn translate_attribute_data(&self, input: &[u8]) -> String {
String::from_utf8_lossy(input).trim_end_matches('\0').into()
}

pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
Expand All @@ -246,42 +300,58 @@ mod lister {

unsafe {
listxattr(
c_path.as_ptr().cast(),
c_path.as_ptr(),
ptr::null_mut(),
0,
)
}
}

pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
let listxattr = match self.follow_symlinks {
FollowSymlinks::Yes => listxattr,
FollowSymlinks::No => llistxattr,
};

unsafe {
listxattr(
c_path.as_ptr().cast(),
c_path.as_ptr(),
buf.as_mut_ptr().cast(),
bufsize as size_t,
bufsize,
)
}
}

pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
pub fn getxattr_first(&self, c_path: &CString, c_name: &CString) -> ssize_t {
let getxattr = match self.follow_symlinks {
FollowSymlinks::Yes => getxattr,
FollowSymlinks::No => lgetxattr,
FollowSymlinks::Yes => getxattr,
FollowSymlinks::No => lgetxattr,
};

unsafe {
getxattr(
c_path.as_ptr().cast(),
buf.as_ptr().cast(),
c_path.as_ptr(),
c_name.as_ptr().cast(),
ptr::null_mut(),
0,
)
}
}

pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
let getxattr = match self.follow_symlinks {
FollowSymlinks::Yes => getxattr,
FollowSymlinks::No => lgetxattr,
};

unsafe {
getxattr(
c_path.as_ptr(),
c_name.as_ptr().cast(),
buf.as_mut_ptr().cast::<libc::c_void>(),
bufsize,
)
}
}
}
}
9 changes: 9 additions & 0 deletions src/fs/fields.rs
Expand Up @@ -260,6 +260,15 @@ impl Default for Git {
}
}

pub enum SecurityContextType<'a> {
SELinux(&'a str),
None
}

pub struct SecurityContext<'a> {
pub context: SecurityContextType<'a>,
}

#[allow(dead_code)]
#[derive(PartialEq, Copy, Clone)]
pub enum SubdirGitRepoStatus{
Expand Down