blob: acfa221b16e3f0d5b479e5c344e2ac53e2d45398 [file] [log] [blame]
use std::ffi::{OsStr, OsString};
use std::io;
use std::mem;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::BorrowedFd;
use std::path::Path;
use rustix::fs as rfs;
use rustix::path::Arg;
use crate::util::allocate_loop;
use std::os::raw::c_char;
#[cfg(not(target_os = "macos"))]
pub const ENOATTR: i32 = rustix::io::Errno::NODATA.raw_os_error();
#[cfg(target_os = "macos")]
pub const ENOATTR: i32 = rustix::io::Errno::NOATTR.raw_os_error();
// Convert an `&mut [u8]` to an `&mut [c_char]`
#[inline]
fn as_listxattr_buffer(buf: &mut [u8]) -> &mut [c_char] {
// SAFETY: u8 and i8 have the same size and alignment
unsafe {
&mut *(buf as *mut [u8] as *mut [c_char])
}
}
/// An iterator over a set of extended attributes names.
pub struct XAttrs {
data: Box<[u8]>,
offset: usize,
}
impl Clone for XAttrs {
fn clone(&self) -> Self {
XAttrs {
data: Vec::from(&*self.data).into_boxed_slice(),
offset: self.offset,
}
}
fn clone_from(&mut self, other: &XAttrs) {
self.offset = other.offset;
let mut data = mem::replace(&mut self.data, Box::new([])).into_vec();
data.extend(other.data.iter().cloned());
self.data = data.into_boxed_slice();
}
}
// Yes, I could avoid these allocations on linux/macos. However, if we ever want to be freebsd
// compatible, we need to be able to prepend the namespace to the extended attribute names.
// Furthermore, borrowing makes the API messy.
impl Iterator for XAttrs {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
let data = &self.data[self.offset..];
if data.is_empty() {
None
} else {
// always null terminated (unless empty).
let end = data.iter().position(|&b| b == 0u8).unwrap();
self.offset += end + 1;
Some(OsStr::from_bytes(&data[..end]).to_owned())
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.data.len() == self.offset {
(0, Some(0))
} else {
(1, None)
}
}
}
pub fn get_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<Vec<u8>> {
allocate_loop(|buf| {
rfs::fgetxattr(fd, name, buf)
})
}
pub fn set_fd(fd: BorrowedFd<'_>, name: &OsStr, value: &[u8]) -> io::Result<()> {
rfs::fsetxattr(fd, name, value, rfs::XattrFlags::empty())?;
Ok(())
}
pub fn remove_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<()> {
rfs::fremovexattr(fd, name)?;
Ok(())
}
pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
let vec = allocate_loop(|buf| {
rfs::flistxattr(fd, as_listxattr_buffer(buf))
})?;
Ok(XAttrs {
data: vec.into_boxed_slice(),
offset: 0,
})
}
pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
let path = path.into_c_str()?;
let name = name.into_c_str()?;
allocate_loop(|buf| {
#[cfg(target_os = "macos")]
{
// If an empty slice is passed to lgetxattr on macOS, it returns an error.
// Might be a macOS bug, so work around it here by calling the libc manually.
if buf.is_empty() {
let ret = unsafe {
libc::getxattr(
(&*path).as_ptr(),
(&*name).as_ptr(),
std::ptr::null_mut(),
0,
0,
if deref { 0 } else { libc::XATTR_NOFOLLOW },
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
} else {
return Ok(ret as usize);
}
}
}
let getxattr_func = if deref { rfs::getxattr } else { rfs::lgetxattr };
let size = getxattr_func(&*path, &*name, buf)?;
io::Result::Ok(size)
})
}
pub fn set_path(path: &Path, name: &OsStr, value: &[u8], deref: bool) -> io::Result<()> {
let setxattr_func = if deref { rfs::setxattr } else { rfs::lsetxattr };
setxattr_func(path, name, value, rfs::XattrFlags::empty())?;
Ok(())
}
pub fn remove_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<()> {
let removexattr_func = if deref { rfs::removexattr } else { rfs::lremovexattr };
removexattr_func(path, name)?;
Ok(())
}
pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
let listxattr_func = if deref { rfs::listxattr } else { rfs::llistxattr };
let path = path.as_cow_c_str()?;
let vec = allocate_loop(|buf| {
listxattr_func(&*path, as_listxattr_buffer(buf))
})?;
Ok(XAttrs {
data: vec.into_boxed_slice(),
offset: 0,
})
}