Merge branch 'nwin:add-xattr-linux'

Conflicts:
	src/file.rs
This commit is contained in:
Ben S 2015-02-23 14:52:07 +00:00
commit ce23c63d75
9 changed files with 362 additions and 16 deletions

View File

@ -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),

View File

@ -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.

View File

@ -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 dont need std_misc but you cant
// 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,

View File

@ -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() ]);

View File

@ -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
src/xattr/mod.rs Normal file
View File

@ -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
src/xattr/xattr_darwin.rs Normal file
View File

@ -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
src/xattr/xattr_linux.rs Normal file
View File

@ -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
src/xattr/xattr_other.rs Normal file
View File

@ -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 }