use std::{
ffi::{OsStr, OsString},
io::{self, Error, ErrorKind},
path::Path,
};
#[cfg(windows)]
mod iter_windows;
pub fn get<P: AsRef<Path>, N: AsRef<OsStr>>(path: P, name: N) -> io::Result<Option<Vec<u8>>> {
#[allow(unused)]
let (path, name) = (path.as_ref(), name.as_ref());
#[cfg(unix)]
return with_namespaced_name(name, |namespaced_name| {
xattr::get_deref(path, namespaced_name)
});
#[cfg(windows)]
return with_ads_path(path, name, |ads_path| {
match std::fs::OpenOptions::new().read(true).open(ads_path) {
Err(e) if e.kind() == std::io::ErrorKind::NotFound && std::fs::exists(path)? => {
Ok(None)
}
Err(e) => Err(e),
Ok(mut file) => {
use std::io::Read;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(Some(buf))
}
}
});
#[allow(unreachable_code)]
Err(Error::new(ErrorKind::Unsupported, "unsupported OS"))
}
pub fn set<P: AsRef<Path>, N: AsRef<OsStr>, V: AsRef<[u8]>>(
path: P,
name: N,
value: V,
) -> io::Result<()> {
#[allow(unused)]
let (path, name, value) = (path.as_ref(), name.as_ref(), value.as_ref());
#[cfg(unix)]
return with_namespaced_name(name, |namespaced_name| {
xattr::set_deref(path, namespaced_name, value)
});
#[cfg(windows)]
return {
if !std::fs::exists(path)? {
return Err(Error::new(ErrorKind::NotFound, "file does not exist"));
}
with_ads_path(path, name, |ads_path| {
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(ads_path)?;
file.write_all(value)?;
file.sync_all()?;
Ok(())
})
};
#[allow(unreachable_code)]
Err(Error::new(ErrorKind::Unsupported, "unsupported OS"))
}
pub fn remove<P: AsRef<Path>, N: AsRef<OsStr>>(path: P, name: N) -> io::Result<()> {
#[allow(unused)]
let (path, name) = (path.as_ref(), name.as_ref());
#[cfg(unix)]
return with_namespaced_name(name, |namespaced_name| {
xattr::remove_deref(path, namespaced_name)
});
#[cfg(windows)]
return {
if !std::fs::exists(path)? {
return Err(Error::new(ErrorKind::NotFound, "file does not exist"));
}
with_ads_path(path, name, |ads_path| std::fs::remove_file(ads_path))
};
#[allow(unreachable_code)]
Err(Error::new(ErrorKind::Unsupported, "unsupported OS"))
}
pub struct Attributes {
#[cfg(windows)]
inner: std::iter::FilterMap<
iter_windows::AttributesImpl,
fn(io::Result<OsString>) -> Option<io::Result<OsString>>,
>,
#[cfg(unix)]
#[allow(clippy::type_complexity)]
inner: std::iter::FilterMap<xattr::XAttrs, fn(OsString) -> Option<io::Result<OsString>>>,
}
impl Iterator for Attributes {
type Item = io::Result<OsString>;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(any(windows, unix))]
return self.inner.next();
#[allow(unreachable_code)]
{
unreachable!();
}
}
}
pub fn list<P: AsRef<Path>>(path: P) -> io::Result<Attributes> {
#[cfg(any(windows, unix))]
{
let path = path.as_ref();
#[cfg(windows)]
if !std::fs::exists(path)? {
return Err(Error::new(ErrorKind::NotFound, "file does not exist"));
}
return Ok(Attributes {
#[cfg(windows)]
inner: iter_windows::AttributesImpl::new(path)?.filter_map(
|s: io::Result<OsString>| {
Some(match s {
Ok(s) => {
let name = s
.as_encoded_bytes()
.strip_prefix(b":")?
.strip_suffix(b":$DATA")?;
if name.is_empty() {
return None;
}
Ok(unsafe { OsStr::from_encoded_bytes_unchecked(name) }.to_owned())
}
Err(e) => Err(e),
})
},
),
#[cfg(unix)]
inner: xattr::list(path)?.filter_map(|s| {
const USER_NAMESPACE: &[u8] = b"user.";
let name = s.as_encoded_bytes().strip_prefix(USER_NAMESPACE)?;
Some(Ok(
unsafe { OsStr::from_encoded_bytes_unchecked(name) }.to_owned(),
))
}),
});
}
#[allow(unreachable_code)]
Err(Error::new(ErrorKind::Unsupported, "unsupported OS"))
}
#[cfg(any(unix, windows))]
fn with_buffer<R>(inner: impl Fn(&mut std::ffi::OsString) -> R) -> R {
thread_local! {
static BUFFER: std::cell::RefCell<Option<std::ffi::OsString>>
= const { std::cell::RefCell::new(None) };
}
BUFFER.with(|buffer| {
let mut buffer = buffer.borrow_mut();
if buffer.is_none() {
*buffer = Some(std::ffi::OsString::new());
}
let buffer = buffer.as_mut().unwrap();
let ret = inner(buffer);
buffer.clear();
ret
})
}
#[cfg(unix)]
fn with_namespaced_name<R>(name: &OsStr, inner: impl Fn(&OsStr) -> R) -> R {
with_buffer(|buffer| {
buffer.push("user.");
buffer.push(name);
inner(buffer)
})
}
#[cfg(windows)]
fn with_ads_path<R>(path: &Path, name: &OsStr, inner: impl Fn(&OsStr) -> R) -> R {
with_buffer(|buffer| {
buffer.push(path);
buffer.push(":");
let bytes = name.as_encoded_bytes();
buffer.push(unsafe {
OsStr::from_encoded_bytes_unchecked(bytes.strip_suffix(b":$DATA").unwrap_or(bytes))
});
inner(buffer)
})
}