//! FreeBSD and NetBSD xattr support. use std::ffi::{CString, OsStr, OsString}; use std::io; use std::mem; use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::os::unix::io::RawFd; use std::path::Path; use libc::{c_char, c_int, c_void, size_t, ssize_t, EPERM}; use util::{allocate_loop, path_to_c}; const EXTATTR_NAMESPACE_USER_STRING: &'static str = "user"; const EXTATTR_NAMESPACE_SYSTEM_STRING: &'static str = "system"; const EXTATTR_NAMESPACE_NAMES: [&'static str; 3] = [ "empty", EXTATTR_NAMESPACE_USER_STRING, EXTATTR_NAMESPACE_SYSTEM_STRING, ]; const EXTATTR_NAMESPACE_USER: c_int = 1; const EXTATTR_NAMESPACE_SYSTEM: c_int = 2; extern "C" { pub fn extattr_list_fd( fd: c_int, attrnamespace: c_int, data: *mut c_void, nbytes: size_t, ) -> ssize_t; pub fn extattr_get_fd( fd: c_int, attrnamespace: c_int, attrname: *const c_char, data: *mut c_void, nbytes: size_t, ) -> ssize_t; pub fn extattr_delete_fd(fd: c_int, attrname: c_int, attrname: *const c_char) -> c_int; pub fn extattr_set_fd( fd: c_int, attrname: c_int, attrname: *const c_char, data: *const c_void, nbytes: size_t, ) -> ssize_t; pub fn extattr_list_link( path: *const c_char, attrnamespace: c_int, data: *mut c_void, nbytes: size_t, ) -> ssize_t; pub fn extattr_get_link( path: *const c_char, attrnamespace: c_int, attrname: *const c_char, data: *mut c_void, nbytes: size_t, ) -> ssize_t; pub fn extattr_delete_link( path: *const c_char, attrname: c_int, attrname: *const c_char, ) -> c_int; pub fn extattr_set_link( path: *const c_char, attrname: c_int, attrname: *const c_char, data: *const c_void, nbytes: size_t, ) -> ssize_t; } /// An iterator over a set of extended attributes names. pub struct XAttrs { user_attrs: Box<[u8]>, system_attrs: Box<[u8]>, offset: usize, } impl Clone for XAttrs { fn clone(&self) -> Self { XAttrs { user_attrs: Vec::from(&*self.user_attrs).into_boxed_slice(), system_attrs: Vec::from(&*self.system_attrs).into_boxed_slice(), offset: self.offset, } } fn clone_from(&mut self, other: &XAttrs) { self.offset = other.offset; let mut data = mem::replace(&mut self.user_attrs, Box::new([])).into_vec(); data.extend(other.user_attrs.iter().cloned()); self.user_attrs = data.into_boxed_slice(); data = mem::replace(&mut self.system_attrs, Box::new([])).into_vec(); data.extend(other.system_attrs.iter().cloned()); self.system_attrs = data.into_boxed_slice(); } } impl Iterator for XAttrs { type Item = OsString; fn next(&mut self) -> Option { if self.user_attrs.is_empty() && self.system_attrs.is_empty() { return None; } if self.offset == self.user_attrs.len() + self.system_attrs.len() { return None; } let data = if self.offset < self.system_attrs.len() { &self.system_attrs[self.offset..] } else { &self.user_attrs[self.offset - self.system_attrs.len()..] }; let siz = data[0] as usize; self.offset += siz + 1; if self.offset < self.system_attrs.len() { Some(prefix_namespace( OsStr::from_bytes(&data[1..siz + 1]), EXTATTR_NAMESPACE_SYSTEM, )) } else { Some(prefix_namespace( OsStr::from_bytes(&data[1..siz + 1]), EXTATTR_NAMESPACE_USER, )) } } fn size_hint(&self) -> (usize, Option) { if self.user_attrs.len() + self.system_attrs.len() == self.offset { (0, Some(0)) } else { (1, None) } } } fn name_to_ns(name: &OsStr) -> io::Result<(c_int, CString)> { let mut groups = name.as_bytes().splitn(2, |&b| b == b'.').take(2); let nsname = match groups.next() { Some(s) => s, None => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "couldn't find namespace", )) } }; let propname = match groups.next() { Some(s) => s, None => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "couldn't find attribute", )) } }; let ns_int = match EXTATTR_NAMESPACE_NAMES .iter() .position(|&s| s.as_bytes() == nsname) { Some(i) => i, None => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "no matching namespace", )) } }; Ok((ns_int as c_int, CString::new(propname)?)) } fn prefix_namespace(attr: &OsStr, ns: c_int) -> OsString { let nsname = EXTATTR_NAMESPACE_NAMES[ns as usize]; let mut v = Vec::with_capacity(nsname.as_bytes().len() + attr.as_bytes().len() + 1); v.extend(nsname.as_bytes()); v.extend(".".as_bytes()); v.extend(attr.as_bytes()); OsString::from_vec(v) } pub fn get_fd(fd: RawFd, name: &OsStr) -> io::Result> { let (ns, name) = name_to_ns(name)?; unsafe { allocate_loop(|buf, len| { extattr_get_fd(fd, ns, name.as_ptr(), buf as *mut c_void, len as size_t) }) } } pub fn set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()> { let (ns, name) = name_to_ns(name)?; let ret = unsafe { extattr_set_fd( fd, ns, name.as_ptr(), value.as_ptr() as *const c_void, value.len() as size_t, ) }; if ret == -1 { Err(io::Error::last_os_error()) } else { Ok(()) } } pub fn remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()> { let (ns, name) = name_to_ns(name)?; let ret = unsafe { extattr_delete_fd(fd, ns, name.as_ptr()) }; if ret != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } pub fn list_fd(fd: RawFd) -> io::Result { let sysvec = unsafe { let res = allocate_loop(|buf, len| { extattr_list_fd( fd, EXTATTR_NAMESPACE_SYSTEM, buf as *mut c_void, len as size_t, ) }); // On FreeBSD, system attributes require root privileges to view. However, // to mimic the behavior of listxattr in linux and osx, we need to query // them anyway and return empty results if we get EPERM match res { Ok(v) => v, Err(err) => { if err.raw_os_error() == Some(EPERM) { Vec::new() } else { return Err(err); } } } }; let uservec = unsafe { let res = allocate_loop(|buf, len| { extattr_list_fd( fd, EXTATTR_NAMESPACE_USER, buf as *mut c_void, len as size_t, ) }); match res { Ok(v) => v, Err(err) => return Err(err), } }; Ok(XAttrs { system_attrs: sysvec.into_boxed_slice(), user_attrs: uservec.into_boxed_slice(), offset: 0, }) } pub fn get_path(path: &Path, name: &OsStr) -> io::Result> { let (ns, name) = name_to_ns(name)?; let path = path_to_c(path)?; unsafe { allocate_loop(|buf, len| { extattr_get_link( path.as_ptr(), ns, name.as_ptr(), buf as *mut c_void, len as size_t, ) }) } } pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> { let (ns, name) = name_to_ns(name)?; let path = path_to_c(path)?; let ret = unsafe { extattr_set_link( path.as_ptr(), ns, name.as_ptr(), value.as_ptr() as *const c_void, value.len() as size_t, ) }; if ret == -1 { Err(io::Error::last_os_error()) } else { Ok(()) } } pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> { let (ns, name) = name_to_ns(name)?; let path = path_to_c(path)?; let ret = unsafe { extattr_delete_link(path.as_ptr(), ns, name.as_ptr()) }; if ret != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } pub fn list_path(path: &Path) -> io::Result { let path = path_to_c(path)?; let sysvec = unsafe { let res = allocate_loop(|buf, len| { extattr_list_link( path.as_ptr(), EXTATTR_NAMESPACE_SYSTEM, buf as *mut c_void, len as size_t, ) }); // On FreeBSD, system attributes require root privileges to view. However, // to mimic the behavior of listxattr in linux and osx, we need to query // them anyway and return empty results if we get EPERM match res { Ok(v) => v, Err(err) => { if err.raw_os_error() == Some(EPERM) { Vec::new() } else { return Err(err); } } } }; let uservec = unsafe { let res = allocate_loop(|buf, len| { extattr_list_link( path.as_ptr(), EXTATTR_NAMESPACE_USER, buf as *mut c_void, len as size_t, ) }); match res { Ok(v) => v, Err(err) => return Err(err), } }; Ok(XAttrs { system_attrs: sysvec.into_boxed_slice(), user_attrs: uservec.into_boxed_slice(), offset: 0, }) }