use crate::Result;
use errno::{errno, Errno};
use std::{
ffi::{CString, OsStr, OsString},
os::unix::{ffi::OsStrExt, io::RawFd},
path::Path,
ptr::null_mut,
};
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub enum AttrNamespace {
EXTATTR_NAMESPACE_USER = libc::EXTATTR_NAMESPACE_USER,
EXTATTR_NAMESPACE_SYSTEM = libc::EXTATTR_NAMESPACE_SYSTEM,
}
pub fn extattr_delete_fd<S: AsRef<OsStr>>(
fd: RawFd,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<()> {
let namespace = attrnamespace as libc::c_int;
let attr_name = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let res =
unsafe { libc::extattr_delete_fd(fd, namespace, attr_name.as_ptr()) };
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
pub fn extattr_delete_file<P, S>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<()>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attr_name = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let res = unsafe {
libc::extattr_delete_file(path.as_ptr(), namespace, attr_name.as_ptr())
};
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
pub fn extattr_delete_link<P, S>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<()>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attr_name = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let res = unsafe {
libc::extattr_delete_link(path.as_ptr(), namespace, attr_name.as_ptr())
};
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
fn parse_ea_entries(bytes: &[u8]) -> Vec<OsString> {
let mut ret = Vec::new();
let mut idx = 0;
let len = bytes.len();
while idx < len {
let entry_len = bytes[idx] as usize;
ret.push(
OsStr::from_bytes(&bytes[idx + 1..idx + entry_len + 1]).to_owned(),
);
idx += entry_len + 1;
}
ret
}
pub fn extattr_list_fd(
fd: RawFd,
attrnamespace: AttrNamespace,
) -> Result<Vec<OsString>> {
let namespace = attrnamespace as libc::c_int;
let buffer_size =
match unsafe { libc::extattr_list_fd(fd, namespace, null_mut(), 0) } {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_list_fd(
fd,
namespace,
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(parse_ea_entries(&buffer))
}
}
}
pub fn extattr_list_file<P>(
path: P,
attrnamespace: AttrNamespace,
) -> Result<Vec<OsString>>
where
P: AsRef<Path>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let buffer_size = match unsafe {
libc::extattr_list_file(path.as_ptr(), namespace, null_mut(), 0)
} {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_list_file(
path.as_ptr(),
namespace,
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(parse_ea_entries(&buffer))
}
}
}
pub fn extattr_list_link<P>(
path: P,
attrnamespace: AttrNamespace,
) -> Result<Vec<OsString>>
where
P: AsRef<Path>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let buffer_size = match unsafe {
libc::extattr_list_link(path.as_ptr(), namespace, null_mut(), 0)
} {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_list_link(
path.as_ptr(),
namespace,
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(parse_ea_entries(&buffer))
}
}
}
pub fn extattr_get_fd<S: AsRef<OsStr>>(
fd: RawFd,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<Vec<u8>> {
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let buffer_size = match unsafe {
libc::extattr_get_fd(fd, namespace, attrname.as_ptr(), null_mut(), 0)
} {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_get_fd(
fd,
namespace,
attrname.as_ptr(),
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(buffer)
}
}
}
pub fn extattr_get_file<P, S>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<Vec<u8>>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let buffer_size = match unsafe {
libc::extattr_get_file(
path.as_ptr(),
namespace,
attrname.as_ptr(),
null_mut(),
0,
)
} {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_get_file(
path.as_ptr(),
namespace,
attrname.as_ptr(),
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(buffer)
}
}
}
pub fn extattr_get_link<P, S>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
) -> Result<Vec<u8>>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(p) => p,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let buffer_size = match unsafe {
libc::extattr_get_link(
path.as_ptr(),
namespace,
attrname.as_ptr(),
null_mut(),
0,
)
} {
-1 => return Err(errno()),
0 => return Ok(Vec::new()),
size => size as usize,
};
let mut buffer: Vec<u8> = Vec::with_capacity(buffer_size);
let res = unsafe {
libc::extattr_get_link(
path.as_ptr(),
namespace,
attrname.as_ptr(),
buffer.as_mut_ptr().cast(),
buffer_size,
)
};
match res {
-1 => Err(errno()),
len => {
unsafe { buffer.set_len(len as usize) };
Ok(buffer)
}
}
}
pub fn extattr_set_fd<S, B>(
fd: RawFd,
attrnamespace: AttrNamespace,
attrname: S,
data: B,
) -> Result<()>
where
S: AsRef<OsStr>,
B: AsRef<[u8]>,
{
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let data_ptr = data.as_ref().as_ptr().cast();
let data_len = data.as_ref().len();
let res = unsafe {
libc::extattr_set_fd(
fd,
namespace,
attrname.as_ptr(),
data_ptr,
data_len,
)
};
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
pub fn extattr_set_file<P, S, B>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
data: B,
) -> Result<()>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
B: AsRef<[u8]>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let data_ptr = data.as_ref().as_ptr().cast();
let data_len = data.as_ref().len();
let res = unsafe {
libc::extattr_set_file(
path.as_ptr(),
namespace,
attrname.as_ptr(),
data_ptr,
data_len,
)
};
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
pub fn extattr_set_link<P, S, B>(
path: P,
attrnamespace: AttrNamespace,
attrname: S,
data: B,
) -> Result<()>
where
P: AsRef<Path>,
S: AsRef<OsStr>,
B: AsRef<[u8]>,
{
let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let namespace = attrnamespace as libc::c_int;
let attrname = match CString::new(attrname.as_ref().as_bytes()) {
Ok(n) => n,
_ => return Err(Errno(libc::EINVAL)),
};
let data_ptr = data.as_ref().as_ptr().cast();
let data_len = data.as_ref().len();
let res = unsafe {
libc::extattr_set_link(
path.as_ptr(),
namespace,
attrname.as_ptr(),
data_ptr,
data_len,
)
};
match res {
-1 => Err(errno()),
_ => Ok(()),
}
}
#[cfg(test)]
mod test {
use std::ffi::OsStr;
#[test]
fn test_parse_ea_entries() {
let list = "\x08attrname\x0fanotherattrname";
let ret = super::parse_ea_entries(list.as_bytes());
assert_eq!(
vec![
OsStr::new("attrname").to_owned(),
OsStr::new("anotherattrname").to_owned(),
],
ret
);
}
}