Jelajahi Sumber

Merge branch 'nwin:add-xattr-linux'

Conflicts:
	src/file.rs
Ben S 11 tahun lalu
induk
melakukan
ce23c63d75
9 mengubah file dengan 362 tambahan dan 16 penghapusan
  1. 1 1
      src/column.rs
  2. 28 13
      src/file.rs
  3. 5 1
      src/main.rs
  4. 19 0
      src/options.rs
  5. 17 1
      src/output/details.rs
  6. 13 0
      src/xattr/mod.rs
  7. 128 0
      src/xattr/xattr_darwin.rs
  8. 119 0
      src/xattr/xattr_linux.rs
  9. 32 0
      src/xattr/xattr_other.rs

+ 1 - 1
src/column.rs

@@ -68,7 +68,7 @@ impl Alignment {
     /// of spaces to add: this is because the strings are usually full of
     /// invisible control characters, so getting the displayed width of the
     /// string is not as simple as just getting its length.
-    pub fn pad_string(&self, string: &String, padding: usize) -> String {
+    pub fn pad_string(&self, string: &str, padding: usize) -> String {
         match *self {
             Alignment::Left  => format!("{}{}", string, spaces(padding)),
             Alignment::Right => format!("{}{}", spaces(padding), string),

+ 28 - 13
src/file.rs

@@ -22,6 +22,8 @@ use column::Column::*;
 use dir::Dir;
 use filetype::HasType;
 use options::{SizeFormat, TimeType};
+use xattr;
+use xattr::Attribute;
 
 /// This grey value is directly in between white and black, so it's guaranteed
 /// to show up on either backgrounded terminal.
@@ -40,6 +42,7 @@ pub struct File<'a> {
     pub ext:   Option<String>,
     pub path:  Path,
     pub stat:  io::FileStat,
+    pub xattrs: Vec<Attribute>,
     pub this:  Option<Dir>,
 }
 
@@ -67,12 +70,13 @@ impl<'a> File<'a> {
         };
 
         File {
-            path:  path.clone(),
-            dir:   parent,
-            stat:  stat,
-            ext:   ext(&filename),
-            name:  filename,
-            this:  this,
+            path:   path.clone(),
+            dir:    parent,
+            stat:   stat,
+            ext:    ext(&filename),
+            xattrs: xattr::llist(path).unwrap_or(Vec::new()),
+            name:   filename.to_string(),
+            this:   this,
         }
     }
 
@@ -210,12 +214,13 @@ impl<'a> File<'a> {
         // Use stat instead of lstat - we *want* to follow links.
         if let Ok(stat) = fs::stat(target_path) {
             Ok(File {
-                path:  target_path.clone(),
-                dir:   self.dir,
-                stat:  stat,
-                ext:   ext(&filename),
-                name:  filename,
-                this:  None,
+                path:   target_path.clone(),
+                dir:    self.dir,
+                stat:   stat,
+                ext:    ext(&filename),
+                xattrs: xattr::list(target_path).unwrap_or(Vec::new()),
+                name:   filename.to_string(),
+                this:   None,
             })
         }
         else {
@@ -361,6 +366,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.xattrs.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
@@ -384,9 +398,10 @@ 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 }
+        Cell { text: string, length: 11 }
     }
 
     /// Helper method for the permissions string.

+ 5 - 1
src/main.rs

@@ -1,4 +1,7 @@
-#![feature(collections, core, env, libc, old_io, old_path, plugin)]
+#![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]
+// Other platforms then macos don’t need std_misc but you can’t 
+// use #[cfg] on features.
+#![allow(unused_features)] 
 
 extern crate ansi_term;
 extern crate datetime;
@@ -27,6 +30,7 @@ pub mod filetype;
 pub mod options;
 pub mod output;
 pub mod term;
+pub mod xattr;
 
 struct Exa<'a> {
     count:   usize,

+ 19 - 0
src/options.rs

@@ -4,6 +4,7 @@ use column::Column;
 use column::Column::*;
 use output::{Grid, Details};
 use term::dimensions;
+use xattr;
 
 use std::cmp::Ordering;
 use std::fmt;
@@ -43,6 +44,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 xattr::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");
@@ -215,6 +221,7 @@ impl View {
                         columns: try!(Columns::deduce(matches)),
                         header: matches.opt_present("header"),
                         tree: matches.opt_present("recurse"),
+                        xattr: xattr::feature_implemented() && matches.opt_present("extended"),
                         filter: filter,
                 };
 
@@ -245,6 +252,9 @@ impl View {
         else if matches.opt_present("tree") {
             Err(Misfire::Useless("tree", false, "long"))
         }
+        else if xattr::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"))
@@ -461,6 +471,7 @@ mod test {
     use super::Options;
     use super::Misfire;
     use super::Misfire::*;
+    use xattr;
 
     fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
         match misfire {
@@ -547,6 +558,14 @@ mod test {
         assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long"))
     }
 
+    #[test]
+    fn extended_without_long() {
+        if xattr::feature_implemented() {
+            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() ]);

+ 17 - 1
src/output/details.rs

@@ -1,4 +1,5 @@
-use column::{Column, Cell};
+use column::{Alignment, Column, Cell};
+use xattr::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 xattr: 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.xattr {
+                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.xattrs.clone(),
                 children: file.this.is_some(),
             };
 
@@ -104,6 +119,7 @@ struct Row {
     pub cells: Vec<Cell>,
     pub name: String,
     pub last: bool,
+    pub attrs: Vec<Attribute>,
     pub children: bool,
 }
 

+ 13 - 0
src/xattr/mod.rs

@@ -0,0 +1,13 @@
+//! Extended attribute support
+#[cfg(target_os = "macos")]
+mod xattr_darwin;
+#[cfg(target_os = "macos")]
+pub use self::xattr_darwin::*;
+#[cfg(target_os = "linux")]
+mod xattr_linux;
+#[cfg(target_os = "linux")]
+pub use self::xattr_linux::*;
+#[cfg(not(any(target_os = "macos", target_os = "linux")))]
+mod xattr_other;
+#[cfg(not(any(target_os = "macos", target_os = "linux")))]
+pub use self::xattr_other::*;

+ 128 - 0
src/xattr/xattr_darwin.rs

@@ -0,0 +1,128 @@
+//! 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)
+        };
+        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 c_end = end + 1; // end of the c-string (including 0)
+                    let size = unsafe {
+                        getxattr(
+                            c_path.as_ptr(),
+                            buf[start..c_end].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 = c_end;
+                }
+                Ok(names)
+            } else {
+                Err(io::IoError {
+                    kind: io::OtherIoError,
+                    desc: "could not read extended attributes",
+                    detail: None
+                })
+            }
+        } else {
+            Err(io::IoError {
+                kind: io::OtherIoError,
+                desc: "could not read extended attributes",
+                detail: None
+            })
+        }
+    }
+    
+    /// 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 }

+ 119 - 0
src/xattr/xattr_linux.rs

@@ -0,0 +1,119 @@
+//! Extended attribute support for darwin
+extern crate libc;
+
+use std::ffi::CString;
+use std::ptr;
+use std::old_io as io;
+use self::libc::{size_t, ssize_t, c_char, c_void};
+
+extern "C" {
+    fn listxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t;
+    fn llistxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t;
+    fn getxattr(path: *const c_char, name: *const c_char, 
+                value: *mut c_void, size: size_t
+    ) -> ssize_t;
+    fn lgetxattr(path: *const c_char, name: *const c_char, 
+                value: *mut c_void, size: size_t
+    ) -> ssize_t;
+}
+
+/// Attributes which can be passed to `Attribute::list_with_flags`
+#[derive(Copy)]
+pub enum FollowSymlinks {
+    Yes,
+    No
+}
+
+/// 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, do_follow: FollowSymlinks) -> io::IoResult<Vec<Attribute>> {
+        let (listxattr, getxattr) = match do_follow {
+            FollowSymlinks::Yes => (listxattr, getxattr),
+            FollowSymlinks::No => (llistxattr, lgetxattr),
+        };
+        let c_path = try!(CString::new(path.as_vec()));
+        let bufsize = unsafe {
+            listxattr(c_path.as_ptr(), ptr::null_mut(), 0)
+        };
+        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
+            )};
+            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 c_end = end + 1; // end of the c-string (including 0)
+                    let size = unsafe {
+                        getxattr(
+                            c_path.as_ptr(),
+                            buf[start..c_end].as_ptr() as *const c_char,
+                            ptr::null_mut(), 0
+                        )
+                    };
+                    if size > 0 {
+                        names.push(Attribute { 
+                            name: String::from_utf8_lossy(&buf[start..end]).into_owned(),
+                            size: size as usize
+                        });
+                    }
+                    start = c_end;
+                }
+                Ok(names)
+            } else {
+                Err(io::IoError {
+                    kind: io::OtherIoError,
+                    desc: "could not read extended attributes",
+                    detail: None
+                })
+            }
+        } else {
+            Err(io::IoError {
+                kind: io::OtherIoError,
+                desc: "could not read extended attributes",
+                detail: None
+            })
+        }
+    }
+    
+    /// 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, FollowSymlinks::Yes)
+}
+/// Lists the extended attributes.
+/// Does not follow symlinks like `lstat`
+pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
+    Attribute::list(path, FollowSymlinks::No)
+}
+
+/// Returns true if the extended attribute feature is implemented on this platform.
+#[inline(always)]
+pub fn feature_implemented() -> bool { true }

+ 32 - 0
src/xattr/xattr_other.rs

@@ -0,0 +1,32 @@
+//! Extended attribute support for other os
+use std::old_io as io;
+
+/// Extended attribute
+#[derive(Clone)]
+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) -> io::IoResult<Vec<Attribute>> {
+    Ok(Vec::new())
+}
+/// Lists the extended attributes. Does not follow symlinks like `lstat`
+pub fn llist(_: &Path) -> io::IoResult<Vec<Attribute>> {
+    Ok(Vec::new())
+}
+
+/// Returns true if the extended attribute feature is implemented on this platform.
+#[inline(always)]
+pub fn feature_implemented() -> bool { false }