Implement display of extended attributes

This commit is contained in:
nwin 2015-02-22 13:26:52 +01:00
parent 376e417c3f
commit 48b6123165
7 changed files with 216 additions and 3 deletions

124
src/attr/attr_darwin.rs Normal file
View File

@ -0,0 +1,124 @@
//! Extended attribute support for darwin
extern crate libc;
use std::ffi::CString;
use std::ptr;
use std::mem;
use std::old_io as io;
use self::libc::{c_int, size_t, ssize_t, c_char, c_void, uint32_t};
/// Don't follow symbolic links
const XATTR_NOFOLLOW: c_int = 0x0001;
/// Expose HFS Compression extended attributes
const XATTR_SHOWCOMPRESSION: c_int = 0x0020;
extern "C" {
fn listxattr(path: *const c_char, namebuf: *mut c_char,
size: size_t, options: c_int) -> ssize_t;
fn getxattr(path: *const c_char, name: *const c_char,
value: *mut c_void, size: size_t, position: uint32_t,
options: c_int) -> ssize_t;
}
/// Attributes which can be passed to `Attribute::list_with_flags`
#[derive(Copy)]
pub enum ListFlags {
/// Don't follow symbolic links
NoFollow = XATTR_NOFOLLOW as isize,
/// Expose HFS Compression extended attributes
ShowCompression = XATTR_SHOWCOMPRESSION as isize
}
/// Extended attribute
#[derive(Debug, Clone)]
pub struct Attribute {
name: String,
size: usize,
}
impl Attribute {
/// Lists the extended attribute of `path`.
/// Does follow symlinks by default.
pub fn list(path: &Path, flags: &[ListFlags]) -> io::IoResult<Vec<Attribute>> {
let mut c_flags: c_int = 0;
for &flag in flags.iter() {
c_flags |= flag as c_int
}
let c_path = try!(CString::new(path.as_vec()));
let bufsize = unsafe { listxattr(
c_path.as_ptr(),
ptr::null_mut(),
0, c_flags
)} as isize;
if bufsize > 0 {
let mut buf = vec![0u8; bufsize as usize];
let err = unsafe { listxattr(
c_path.as_ptr(),
buf.as_mut_ptr() as *mut c_char,
bufsize as size_t, c_flags
)};
if err > 0 {
// End indicies of the attribute names
// the buffer contains 0-terminates c-strings
let idx = buf.iter().enumerate().filter_map(|(i, v)|
if *v == 0 { Some(i) } else { None }
);
let mut names = Vec::new();
let mut start = 0;
for end in idx {
let size = unsafe {
getxattr(
c_path.as_ptr(),
buf[start..end+1].as_ptr() as *const c_char,
ptr::null_mut(), 0, 0, c_flags
)
};
if size > 0 {
names.push(Attribute {
name: unsafe {
// buf is guaranteed to contain valid utf8 strings
// see man listxattr
mem::transmute::<&[u8], &str>(&buf[start..end]).to_string()
},
size: size as usize
});
}
start = end + 1;
}
println!("{:?}", names);
Ok(names)
} else {
// Ignore error for now
Ok(Vec::new())
}
} else {
// Ignore error for now
Ok(Vec::new())
}
}
/// Getter for name
pub fn name(&self) -> &str {
&self.name
}
/// Getter for size
pub fn size(&self) -> usize {
self.size
}
}
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(path, &[])
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(path, &[ListFlags::NoFollow])
}
/// Returns true if the extended attribute feature is implemented on this platform.
#[inline(always)]
pub fn feature_implemented() -> bool { true }

31
src/attr/attr_other.rs Normal file
View File

@ -0,0 +1,31 @@
//! Extended attribute support for other os
use std::old_io as io;
/// Extended attribute
pub struct Attribute;
impl Attribute {
/// Getter for name
pub fn name(&self) -> &str {
unimplemented!()
}
/// Getter for size
pub fn size(&self) -> usize {
unimplemented!()
}
}
/// Lists the extended attributes. Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Vec::new()
}
/// Lists the extended attributes. Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
Vec::new()
}
/// Returns true if the extended attribute feature is implemented on this platform.
#[inline(always)]
pub fn feature_implemented() -> bool { false }

10
src/attr/mod.rs Normal file
View File

@ -0,0 +1,10 @@
//! Extended attribute support
#[cfg(target_os = "macos")]
mod attr_darwin;
#[cfg(target_os = "macos")]
pub use self::attr_darwin::*;
#[cfg(not(target_os = "macos"))]
mod attr_other;
#[cfg(not(target_os = "macos"))]
pub use self::attr_other::*;

View File

@ -21,6 +21,8 @@ use column::Column::*;
use dir::Dir; use dir::Dir;
use filetype::HasType; use filetype::HasType;
use options::{SizeFormat, TimeType}; use options::{SizeFormat, TimeType};
use attr;
use attr::Attribute;
/// This grey value is directly in between white and black, so it's guaranteed /// This grey value is directly in between white and black, so it's guaranteed
/// to show up on either backgrounded terminal. /// to show up on either backgrounded terminal.
@ -39,6 +41,7 @@ pub struct File<'a> {
pub ext: Option<String>, pub ext: Option<String>,
pub path: Path, pub path: Path,
pub stat: io::FileStat, pub stat: io::FileStat,
pub attrs: Vec<Attribute>,
pub this: Option<Dir>, pub this: Option<Dir>,
} }
@ -80,6 +83,7 @@ impl<'a> File<'a> {
path: path.clone(), path: path.clone(),
dir: parent, dir: parent,
stat: stat, stat: stat,
attrs: attr::llist(path).unwrap_or(Vec::new()),
name: filename.to_string(), name: filename.to_string(),
ext: ext(filename.as_slice()), ext: ext(filename.as_slice()),
this: this, this: this,
@ -192,6 +196,7 @@ impl<'a> File<'a> {
path: target_path.clone(), path: target_path.clone(),
dir: self.dir, dir: self.dir,
stat: stat, stat: stat,
attrs: attr::list(target_path).unwrap_or(Vec::new()),
name: filename.to_string(), name: filename.to_string(),
ext: ext(filename.as_slice()), ext: ext(filename.as_slice()),
this: None, this: None,
@ -340,6 +345,15 @@ impl<'a> File<'a> {
} }
} }
/// Marker indicating that the file contains extended attributes
///
/// Returns “@” or “ ” depending on wheter the file contains an extented
/// attribute or not. Also returns “ ” in case the attributes cannot be read
/// for some reason.
fn attribute_marker(&self) -> ANSIString {
if self.attrs.len() > 0 { Plain.paint("@") } else { Plain.paint(" ") }
}
/// Generate the "rwxrwxrwx" permissions string, like how ls does it. /// Generate the "rwxrwxrwx" permissions string, like how ls does it.
/// ///
/// Each character is given its own colour. The first three permission /// Each character is given its own colour. The first three permission
@ -363,6 +377,7 @@ impl<'a> File<'a> {
File::permission_bit(&bits, io::OTHER_READ, "r", Yellow.normal()), File::permission_bit(&bits, io::OTHER_READ, "r", Yellow.normal()),
File::permission_bit(&bits, io::OTHER_WRITE, "w", Red.normal()), File::permission_bit(&bits, io::OTHER_WRITE, "w", Red.normal()),
File::permission_bit(&bits, io::OTHER_EXECUTE, "x", Green.normal()), File::permission_bit(&bits, io::OTHER_EXECUTE, "x", Green.normal()),
self.attribute_marker()
]).to_string(); ]).to_string();
Cell { text: string, length: 10 } Cell { text: string, length: 10 }

View File

@ -1,4 +1,4 @@
#![feature(collections, core, env, libc, old_io, old_path, plugin)] #![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]
extern crate ansi_term; extern crate ansi_term;
extern crate datetime; extern crate datetime;
@ -27,6 +27,7 @@ pub mod filetype;
pub mod options; pub mod options;
pub mod output; pub mod output;
pub mod term; pub mod term;
pub mod attr;
struct Exa<'a> { struct Exa<'a> {
count: usize, count: usize,

View File

@ -4,6 +4,7 @@ use column::Column;
use column::Column::*; use column::Column::*;
use output::{Grid, Details}; use output::{Grid, Details};
use term::dimensions; use term::dimensions;
use attr;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -44,6 +45,11 @@ impl Options {
/// Call getopts on the given slice of command-line strings. /// Call getopts on the given slice of command-line strings.
pub fn getopts(args: &[String]) -> Result<(Options, Vec<String>), Misfire> { pub fn getopts(args: &[String]) -> Result<(Options, Vec<String>), Misfire> {
let mut opts = getopts::Options::new(); let mut opts = getopts::Options::new();
if attr::feature_implemented() {
opts.optflag("@", "extended",
"display extended attribute keys and sizes in long (-l) output"
);
}
opts.optflag("1", "oneline", "display one entry per line"); opts.optflag("1", "oneline", "display one entry per line");
opts.optflag("a", "all", "show dot-files"); opts.optflag("a", "all", "show dot-files");
opts.optflag("b", "binary", "use binary prefixes in file sizes"); opts.optflag("b", "binary", "use binary prefixes in file sizes");
@ -220,6 +226,7 @@ impl View {
columns: try!(Columns::deduce(matches)), columns: try!(Columns::deduce(matches)),
header: matches.opt_present("header"), header: matches.opt_present("header"),
tree: matches.opt_present("recurse"), tree: matches.opt_present("recurse"),
ext_attr: matches.opt_present("extended"),
filter: filter, filter: filter,
}; };
@ -250,6 +257,9 @@ impl View {
else if matches.opt_present("tree") { else if matches.opt_present("tree") {
Err(Misfire::Useless("tree", false, "long")) Err(Misfire::Useless("tree", false, "long"))
} }
else if attr::feature_implemented() && matches.opt_present("extended") {
Err(Misfire::Useless("extended", false, "long"))
}
else if matches.opt_present("oneline") { else if matches.opt_present("oneline") {
if matches.opt_present("across") { if matches.opt_present("across") {
Err(Misfire::Useless("across", true, "oneline")) Err(Misfire::Useless("across", true, "oneline"))
@ -552,6 +562,12 @@ mod test {
assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long")) assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long"))
} }
#[test]
fn extended_without_long() {
let opts = Options::getopts(&[ "--extended".to_string() ]);
assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
}
#[test] #[test]
fn tree_without_recurse() { fn tree_without_recurse() {
let opts = Options::getopts(&[ "--tree".to_string() ]); let opts = Options::getopts(&[ "--tree".to_string() ]);

View File

@ -1,4 +1,5 @@
use column::{Column, Cell}; use column::{Alignment, Column, Cell};
use attr::Attribute;
use dir::Dir; use dir::Dir;
use file::{File, GREY}; use file::{File, GREY};
use options::{Columns, FileFilter}; use options::{Columns, FileFilter};
@ -12,6 +13,7 @@ pub struct Details {
pub columns: Columns, pub columns: Columns,
pub header: bool, pub header: bool,
pub tree: bool, pub tree: bool,
pub ext_attr: bool,
pub filter: FileFilter, pub filter: FileFilter,
} }
@ -36,6 +38,7 @@ impl Details {
cells: columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(), cells: columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(),
name: Plain.underline().paint("Name").to_string(), name: Plain.underline().paint("Name").to_string(),
last: false, last: false,
attrs: Vec::new(),
children: false, children: false,
}; };
@ -72,6 +75,17 @@ impl Details {
} }
print!("{}\n", row.name); print!("{}\n", row.name);
if self.ext_attr {
let width = row.attrs.iter().map(|a| a.name().len()).max().unwrap_or(0);
for attr in row.attrs.iter() {
let name = attr.name();
println!("{}\t{}",
Alignment::Left.pad_string(name, width - name.len()),
attr.size()
)
}
}
} }
} }
@ -83,6 +97,7 @@ impl Details {
cells: columns.iter().map(|c| file.display(c, cache, locale)).collect(), cells: columns.iter().map(|c| file.display(c, cache, locale)).collect(),
name: file.file_name_view(), name: file.file_name_view(),
last: index == src.len() - 1, last: index == src.len() - 1,
attrs: file.attrs.clone(),
children: file.this.is_some(), children: file.this.is_some(),
}; };
@ -92,7 +107,7 @@ impl Details {
if let Some(ref dir) = file.this { if let Some(ref dir) = file.this {
let mut files = dir.files(true); let mut files = dir.files(true);
self.filter.transform_files(&mut files); self.filter.transform_files(&mut files);
self.get_files(columns, cache, locale, dest, files.as_slice(), depth + 1); self.get_files(columns, cache, locale, dest, &files, depth + 1);
} }
} }
} }
@ -104,6 +119,7 @@ struct Row {
pub cells: Vec<Cell>, pub cells: Vec<Cell>,
pub name: String, pub name: String,
pub last: bool, pub last: bool,
pub attrs: Vec<Attribute>,
pub children: bool, pub children: bool,
} }