xattr 0.1.10

unix extended filesystem attributes
Documentation
//! FreeBSD and NetBSD xattr support.

use std::io;
use std::ffi::{CString, OsStr, OsString};
use std::os::unix::io::RawFd;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::Path;
use std::mem;

use libc::{c_char, ssize_t, size_t, c_int, c_void, EPERM};
use util::{path_to_c, allocate_loop};

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

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<OsString> {
        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<usize>) {
        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, try!(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<Vec<u8>> {
    let (ns, name) = try!(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) = try!(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) = try!(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<XAttrs> {
    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<Vec<u8>> {
    let (ns, name) = try!(name_to_ns(name));
    let path = try!(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) = try!(name_to_ns(name));
    let path = try!(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) = try!(name_to_ns(name));
    let path = try!(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<XAttrs> {
    let path = try!(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,
    })
}