From 48b61231658a65473e52d144be43f42a7390735f Mon Sep 17 00:00:00 2001 From: nwin Date: Sun, 22 Feb 2015 13:26:52 +0100 Subject: [PATCH] Implement display of extended attributes --- src/attr/attr_darwin.rs | 124 ++++++++++++++++++++++++++++++++++++++++ src/attr/attr_other.rs | 31 ++++++++++ src/attr/mod.rs | 10 ++++ src/file.rs | 15 +++++ src/main.rs | 3 +- src/options.rs | 16 ++++++ src/output/details.rs | 20 ++++++- 7 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/attr/attr_darwin.rs create mode 100644 src/attr/attr_other.rs create mode 100644 src/attr/mod.rs diff --git a/src/attr/attr_darwin.rs b/src/attr/attr_darwin.rs new file mode 100644 index 0000000..f2c1d15 --- /dev/null +++ b/src/attr/attr_darwin.rs @@ -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> { + 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> { + Attribute::list(path, &[]) +} +/// Lists the extended attributes. +/// Does not follow symlinks like `lstat` +pub fn llist(path: &Path) -> io::IoResult> { + 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 } \ No newline at end of file diff --git a/src/attr/attr_other.rs b/src/attr/attr_other.rs new file mode 100644 index 0000000..aa9db27 --- /dev/null +++ b/src/attr/attr_other.rs @@ -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::new() +} +/// Lists the extended attributes. Does not follow symlinks like `lstat` +pub fn llist(path: &Path) -> io::IoResult> { + Vec::new() +} + +/// Returns true if the extended attribute feature is implemented on this platform. +#[inline(always)] +pub fn feature_implemented() -> bool { false } \ No newline at end of file diff --git a/src/attr/mod.rs b/src/attr/mod.rs new file mode 100644 index 0000000..b9fa34a --- /dev/null +++ b/src/attr/mod.rs @@ -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::*; + diff --git a/src/file.rs b/src/file.rs index 32a6e53..c8936d2 100644 --- a/src/file.rs +++ b/src/file.rs @@ -21,6 +21,8 @@ use column::Column::*; use dir::Dir; use filetype::HasType; use options::{SizeFormat, TimeType}; +use attr; +use attr::Attribute; /// This grey value is directly in between white and black, so it's guaranteed /// to show up on either backgrounded terminal. @@ -39,6 +41,7 @@ pub struct File<'a> { pub ext: Option, pub path: Path, pub stat: io::FileStat, + pub attrs: Vec, pub this: Option, } @@ -80,6 +83,7 @@ impl<'a> File<'a> { path: path.clone(), dir: parent, stat: stat, + attrs: attr::llist(path).unwrap_or(Vec::new()), name: filename.to_string(), ext: ext(filename.as_slice()), this: this, @@ -192,6 +196,7 @@ impl<'a> File<'a> { path: target_path.clone(), dir: self.dir, stat: stat, + attrs: attr::list(target_path).unwrap_or(Vec::new()), name: filename.to_string(), ext: ext(filename.as_slice()), 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. /// /// 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_WRITE, "w", Red.normal()), File::permission_bit(&bits, io::OTHER_EXECUTE, "x", Green.normal()), + self.attribute_marker() ]).to_string(); Cell { text: string, length: 10 } diff --git a/src/main.rs b/src/main.rs index 5c3dc08..bb40e66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 datetime; @@ -27,6 +27,7 @@ pub mod filetype; pub mod options; pub mod output; pub mod term; +pub mod attr; struct Exa<'a> { count: usize, diff --git a/src/options.rs b/src/options.rs index f5166b0..f239de9 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,6 +4,7 @@ use column::Column; use column::Column::*; use output::{Grid, Details}; use term::dimensions; +use attr; use std::ascii::AsciiExt; use std::cmp::Ordering; @@ -44,6 +45,11 @@ impl Options { /// Call getopts on the given slice of command-line strings. pub fn getopts(args: &[String]) -> Result<(Options, Vec), Misfire> { 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("a", "all", "show dot-files"); opts.optflag("b", "binary", "use binary prefixes in file sizes"); @@ -220,6 +226,7 @@ impl View { columns: try!(Columns::deduce(matches)), header: matches.opt_present("header"), tree: matches.opt_present("recurse"), + ext_attr: matches.opt_present("extended"), filter: filter, }; @@ -250,6 +257,9 @@ impl View { else if matches.opt_present("tree") { 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") { if matches.opt_present("across") { Err(Misfire::Useless("across", true, "oneline")) @@ -552,6 +562,12 @@ mod test { 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] fn tree_without_recurse() { let opts = Options::getopts(&[ "--tree".to_string() ]); diff --git a/src/output/details.rs b/src/output/details.rs index 8009a2a..4fa5f5a 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -1,4 +1,5 @@ -use column::{Column, Cell}; +use column::{Alignment, Column, Cell}; +use attr::Attribute; use dir::Dir; use file::{File, GREY}; use options::{Columns, FileFilter}; @@ -12,6 +13,7 @@ pub struct Details { pub columns: Columns, pub header: bool, pub tree: bool, + pub ext_attr: bool, pub filter: FileFilter, } @@ -36,6 +38,7 @@ impl Details { cells: columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(), name: Plain.underline().paint("Name").to_string(), last: false, + attrs: Vec::new(), children: false, }; @@ -72,6 +75,17 @@ impl Details { } 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(), name: file.file_name_view(), last: index == src.len() - 1, + attrs: file.attrs.clone(), children: file.this.is_some(), }; @@ -92,7 +107,7 @@ impl Details { if let Some(ref dir) = file.this { let mut files = dir.files(true); 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, pub name: String, pub last: bool, + pub attrs: Vec, pub children: bool, }