Преглед изворни кода

Implement display of extended attributes

nwin пре 11 година
родитељ
комит
48b6123165
7 измењених фајлова са 216 додато и 3 уклоњено
  1. 124 0
      src/attr/attr_darwin.rs
  2. 31 0
      src/attr/attr_other.rs
  3. 10 0
      src/attr/mod.rs
  4. 15 0
      src/file.rs
  5. 2 1
      src/main.rs
  6. 16 0
      src/options.rs
  7. 18 2
      src/output/details.rs

+ 124 - 0
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<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 - 0
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<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 - 0
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::*;
+

+ 15 - 0
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<String>,
     pub path:  Path,
     pub stat:  io::FileStat,
+    pub attrs: Vec<Attribute>,
     pub this:  Option<Dir>,
 }
 
@@ -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 }

+ 2 - 1
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,

+ 16 - 0
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<String>), 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() ]);

+ 18 - 2
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<Cell>,
     pub name: String,
     pub last: bool,
+    pub attrs: Vec<Attribute>,
     pub children: bool,
 }