chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

28
vendor/xattr/src/error.rs vendored Normal file
View File

@@ -0,0 +1,28 @@
use std::error::Error;
use std::fmt;
/// The error type returned on unsupported platforms.
///
/// On unsupported platforms, all operations will fail with an `io::Error` with
/// a kind `io::ErrorKind::Unsupported` and an `UnsupportedPlatformError` error as the inner error.
/// While you *could* check the inner error, it's probably simpler just to check
/// `xattr::SUPPORTED_PLATFORM`.
///
/// This error mostly exists for pretty error messages.
#[derive(Copy, Clone, Debug)]
pub struct UnsupportedPlatformError;
impl Error for UnsupportedPlatformError {
fn description(&self) -> &str {
"unsupported platform"
}
}
impl fmt::Display for UnsupportedPlatformError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"unsupported platform, please file a bug at `https://github.com/Stebalien/xattr'"
)
}
}

168
vendor/xattr/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,168 @@
#![allow(clippy::comparison_chain)]
//! A pure-Rust library to manage extended attributes.
//!
//! It provides support for manipulating extended attributes
//! (`xattrs`) on modern Unix filesystems. See the `attr(5)`
//! manpage for more details.
//!
//! An extension trait [`FileExt`] is provided to directly work with
//! standard `File` objects and file descriptors.
//!
//! If the path argument is a symlink, the get/set/list/remove functions
//! operate on the symlink itself. To operate on the symlink target, use
//! the _deref variant of these functions.
//!
//! ```rust
//! let mut xattrs = xattr::list("/").unwrap().peekable();
//!
//! if xattrs.peek().is_none() {
//! println!("no xattr set on root");
//! return;
//! }
//!
//! println!("Extended attributes:");
//! for attr in xattrs {
//! println!(" - {:?}", attr);
//! }
//! ```
mod error;
mod sys;
mod util;
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::io::{AsRawFd, BorrowedFd};
use std::path::Path;
use std::{fmt, io};
pub use error::UnsupportedPlatformError;
pub use sys::{XAttrs, SUPPORTED_PLATFORM};
/// Get an extended attribute for the specified file.
pub fn get<N, P>(path: P, name: N) -> io::Result<Option<Vec<u8>>>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
util::extract_noattr(sys::get_path(path.as_ref(), name.as_ref(), false))
}
/// Get an extended attribute for the specified file (dereference symlinks).
pub fn get_deref<N, P>(path: P, name: N) -> io::Result<Option<Vec<u8>>>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
util::extract_noattr(sys::get_path(path.as_ref(), name.as_ref(), true))
}
/// Set an extended attribute on the specified file.
pub fn set<N, P>(path: P, name: N, value: &[u8]) -> io::Result<()>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
sys::set_path(path.as_ref(), name.as_ref(), value, false)
}
/// Set an extended attribute on the specified file (dereference symlinks).
pub fn set_deref<N, P>(path: P, name: N, value: &[u8]) -> io::Result<()>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
sys::set_path(path.as_ref(), name.as_ref(), value, true)
}
/// Remove an extended attribute from the specified file.
pub fn remove<N, P>(path: P, name: N) -> io::Result<()>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
sys::remove_path(path.as_ref(), name.as_ref(), false)
}
/// Remove an extended attribute from the specified file (dereference symlinks).
pub fn remove_deref<N, P>(path: P, name: N) -> io::Result<()>
where
P: AsRef<Path>,
N: AsRef<OsStr>,
{
sys::remove_path(path.as_ref(), name.as_ref(), true)
}
/// List extended attributes attached to the specified file.
///
/// Note: this may not list *all* attributes. Speficially, it definitely won't list any trusted
/// attributes unless you are root and it may not list system attributes.
pub fn list<P>(path: P) -> io::Result<XAttrs>
where
P: AsRef<Path>,
{
sys::list_path(path.as_ref(), false)
}
/// List extended attributes attached to the specified file (dereference symlinks).
pub fn list_deref<P>(path: P) -> io::Result<XAttrs>
where
P: AsRef<Path>,
{
sys::list_path(path.as_ref(), true)
}
/// Extension trait to manipulate extended attributes on `File`-like objects.
pub trait FileExt: AsRawFd {
/// Get an extended attribute for the specified file.
fn get_xattr<N>(&self, name: N) -> io::Result<Option<Vec<u8>>>
where
N: AsRef<OsStr>,
{
// SAFETY: Implement I/O safety later.
let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
util::extract_noattr(sys::get_fd(fd, name.as_ref()))
}
/// Set an extended attribute on the specified file.
fn set_xattr<N>(&self, name: N, value: &[u8]) -> io::Result<()>
where
N: AsRef<OsStr>,
{
let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
sys::set_fd(fd, name.as_ref(), value)
}
/// Remove an extended attribute from the specified file.
fn remove_xattr<N>(&self, name: N) -> io::Result<()>
where
N: AsRef<OsStr>,
{
let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
sys::remove_fd(fd, name.as_ref())
}
/// List extended attributes attached to the specified file.
///
/// Note: this may not list *all* attributes. Speficially, it definitely won't list any trusted
/// attributes unless you are root and it may not list system attributes.
fn list_xattr(&self) -> io::Result<XAttrs> {
let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
sys::list_fd(fd)
}
}
impl FileExt for File {}
impl fmt::Debug for XAttrs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Waiting on https://github.com/rust-lang/rust/issues/117729 to stabilize...
struct AsList<'a>(&'a XAttrs);
impl<'a> fmt::Debug for AsList<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.0.clone()).finish()
}
}
f.debug_tuple("XAttrs").field(&AsList(self)).finish()
}
}

328
vendor/xattr/src/sys/bsd.rs vendored Normal file
View File

@@ -0,0 +1,328 @@
//! FreeBSD and NetBSD xattr support.
use libc::{c_int, c_void, size_t, EPERM};
use std::ffi::{CString, OsStr, OsString};
use std::mem::MaybeUninit;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::os::unix::io::{AsRawFd, BorrowedFd};
use std::path::Path;
use std::ptr;
use std::{io, slice};
use libc::{
extattr_delete_fd, extattr_delete_file, extattr_delete_link, extattr_get_fd, extattr_get_file,
extattr_get_link, extattr_list_fd, extattr_list_file, extattr_list_link, extattr_set_fd,
extattr_set_file, extattr_set_link, EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER,
};
pub const ENOATTR: i32 = libc::ENOATTR;
pub const ERANGE: i32 = libc::ERANGE;
const EXTATTR_NAMESPACE_USER_STRING: &str = "user";
const EXTATTR_NAMESPACE_SYSTEM_STRING: &str = "system";
const EXTATTR_NAMESPACE_NAMES: [&str; 3] = [
"empty",
EXTATTR_NAMESPACE_USER_STRING,
EXTATTR_NAMESPACE_SYSTEM_STRING,
];
fn path_to_c(path: &Path) -> io::Result<CString> {
match CString::new(path.as_os_str().as_bytes()) {
Ok(name) => Ok(name),
Err(_) => Err(io::Error::new(io::ErrorKind::NotFound, "file not found")),
}
}
#[inline]
fn cvt(res: libc::ssize_t) -> io::Result<usize> {
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
#[inline]
fn slice_parts(buf: &mut [MaybeUninit<u8>]) -> (*mut c_void, size_t) {
if buf.is_empty() {
(ptr::null_mut(), 0)
} else {
(buf.as_mut_ptr().cast(), buf.len() as size_t)
}
}
fn allocate_loop<F: Fn(*mut c_void, size_t) -> libc::ssize_t>(f: F) -> io::Result<Vec<u8>> {
crate::util::allocate_loop(
|buf| unsafe {
let (ptr, len) = slice_parts(buf);
let new_len = cvt(f(ptr, len))?;
if new_len < len {
Ok(slice::from_raw_parts_mut(ptr.cast(), new_len))
} else {
// If the length of the value isn't strictly smaller than the length of the value
// read, there may be more to read. Fake an ERANGE error so we can try again with a
// bigger buffer.
Err(io::Error::from_raw_os_error(crate::sys::ERANGE))
}
},
// Estimate size + 1 because, on freebsd, the only way to tell if we've read the entire
// value is read a value smaller than the buffer we passed.
|| Ok(cvt(f(ptr::null_mut(), 0))? + 1),
)
}
/// An iterator over a set of extended attributes names.
#[derive(Default, Clone)]
pub struct XAttrs {
user_attrs: Box<[u8]>,
system_attrs: Box<[u8]>,
offset: usize,
}
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)
}
}
}
// This could use libc::extattr_string_to_namespace, but it's awkward because
// that requires nul-terminated strings, which Rust's std is loathe to provide.
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].as_bytes();
let attr = attr.as_bytes();
let mut v = Vec::with_capacity(nsname.len() + attr.len() + 1);
v.extend_from_slice(nsname);
v.extend_from_slice(b".");
v.extend_from_slice(attr);
OsString::from_vec(v)
}
pub fn get_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<Vec<u8>> {
let (ns, name) = name_to_ns(name)?;
unsafe { allocate_loop(|ptr, len| extattr_get_fd(fd.as_raw_fd(), ns, name.as_ptr(), ptr, len)) }
}
pub fn set_fd(fd: BorrowedFd<'_>, name: &OsStr, value: &[u8]) -> io::Result<()> {
let (ns, name) = name_to_ns(name)?;
let ret = unsafe {
extattr_set_fd(
fd.as_raw_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: BorrowedFd<'_>, name: &OsStr) -> io::Result<()> {
let (ns, name) = name_to_ns(name)?;
let ret = unsafe { extattr_delete_fd(fd.as_raw_fd(), ns, name.as_ptr()) };
if ret != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
let sysvec = unsafe {
let res = allocate_loop(|ptr, len| {
extattr_list_fd(fd.as_raw_fd(), EXTATTR_NAMESPACE_SYSTEM, ptr, len)
});
// 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 {
allocate_loop(|ptr, len| extattr_list_fd(fd.as_raw_fd(), EXTATTR_NAMESPACE_USER, ptr, len))?
};
Ok(XAttrs {
system_attrs: sysvec.into_boxed_slice(),
user_attrs: uservec.into_boxed_slice(),
offset: 0,
})
}
pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
let (ns, name) = name_to_ns(name)?;
let path = path_to_c(path)?;
let extattr_get_func = if deref {
extattr_get_file
} else {
extattr_get_link
};
unsafe {
allocate_loop(|ptr, len| extattr_get_func(path.as_ptr(), ns, name.as_ptr(), ptr, len))
}
}
pub fn set_path(path: &Path, name: &OsStr, value: &[u8], deref: bool) -> io::Result<()> {
let (ns, name) = name_to_ns(name)?;
let path = path_to_c(path)?;
let extattr_set_func = if deref {
extattr_set_file
} else {
extattr_set_link
};
let ret = unsafe {
extattr_set_func(
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, deref: bool) -> io::Result<()> {
let (ns, name) = name_to_ns(name)?;
let path = path_to_c(path)?;
let extattr_delete_func = if deref {
extattr_delete_file
} else {
extattr_delete_link
};
let ret = unsafe { extattr_delete_func(path.as_ptr(), ns, name.as_ptr()) };
if ret != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
let path = path_to_c(path)?;
let extattr_list_func = if deref {
extattr_list_file
} else {
extattr_list_link
};
let sysvec = unsafe {
let res = allocate_loop(|ptr, len| {
extattr_list_func(path.as_ptr(), EXTATTR_NAMESPACE_SYSTEM, ptr, len)
});
// 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 {
allocate_loop(|ptr, len| {
extattr_list_func(path.as_ptr(), EXTATTR_NAMESPACE_USER, ptr, len)
})?
};
Ok(XAttrs {
system_attrs: sysvec.into_boxed_slice(),
user_attrs: uservec.into_boxed_slice(),
offset: 0,
})
}

120
vendor/xattr/src/sys/linux_macos.rs vendored Normal file
View File

@@ -0,0 +1,120 @@
use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::BorrowedFd;
use std::path::Path;
use rustix::fs as rfs;
#[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();
pub const ERANGE: i32 = rustix::io::Errno::RANGE.raw_os_error();
/// An iterator over a set of extended attributes names.
#[derive(Default, Clone)]
pub struct XAttrs {
data: Box<[u8]>,
offset: usize,
}
// 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)
}
}
}
/// A macro to abstract away some of the boilerplate when calling `allocate_loop` with rustix
/// functions. Unfortunately, we can't write this as a helper function because I need to call
/// generic rustix function with two different types.
macro_rules! allocate_loop {
(|$buf:ident| $($e:tt)*) => {
crate::util::allocate_loop(
|$buf| Ok($($e)*?.0),
|| {
let $buf: &mut [u8] = &mut [];
Ok($($e)*?)
},
)
};
}
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, buf))?;
Ok(XAttrs {
data: vec.into_boxed_slice(),
offset: 0,
})
}
pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
if deref {
allocate_loop!(|buf| rfs::getxattr(path, name, buf))
} else {
allocate_loop!(|buf| rfs::lgetxattr(path, name, buf))
}
}
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<()> {
if deref {
rfs::removexattr(path, name)
} else {
rfs::lremovexattr(path, name)
}?;
Ok(())
}
pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
let vec = if deref {
allocate_loop!(|buf| rfs::listxattr(path, buf))
} else {
allocate_loop!(|buf| rfs::llistxattr(path, buf))
}?;
Ok(XAttrs {
data: vec.into_boxed_slice(),
offset: 0,
})
}

34
vendor/xattr/src/sys/mod.rs vendored Normal file
View File

@@ -0,0 +1,34 @@
macro_rules! platforms {
($($($platform:expr);* => $module:ident),*) => {
$(
#[cfg(any($(target_os = $platform),*))]
#[cfg_attr(not(any($(target_os = $platform),*)), allow(dead_code))]
mod $module;
#[cfg(any($(target_os = $platform),*))]
pub use self::$module::*;
)*
#[cfg(all(feature = "unsupported", not(any($($(target_os = $platform),*),*))))]
#[cfg_attr(any($($(target_os = $platform),*),*), allow(dead_code))]
mod unsupported;
#[cfg(all(feature = "unsupported", not(any($($(target_os = $platform),*),*))))]
pub use self::unsupported::*;
/// A constant indicating whether or not the target platform is supported.
///
/// To make programmer's lives easier, this library builds on all platforms.
/// However, all function calls on unsupported platforms will return
/// `io::Error`s.
///
/// Note: If you would like compilation to simply fail on unsupported platforms,
/// turn of the `unsupported` feature.
pub const SUPPORTED_PLATFORM: bool = cfg!(any($($(target_os = $platform),*),*));
}
}
platforms! {
"android"; "linux"; "macos"; "hurd" => linux_macos,
"freebsd"; "netbsd" => bsd
}

80
vendor/xattr/src/sys/unsupported.rs vendored Normal file
View File

@@ -0,0 +1,80 @@
use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::io::BorrowedFd;
use std::path::Path;
use crate::UnsupportedPlatformError;
pub const ENOATTR: i32 = 0;
pub const ERANGE: i32 = 0;
/// An iterator over a set of extended attributes names.
#[derive(Clone, Default)]
pub struct XAttrs;
impl Iterator for XAttrs {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(0))
}
}
pub fn get_fd(_: BorrowedFd<'_>, _: &OsStr) -> io::Result<Vec<u8>> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn set_fd(_: BorrowedFd<'_>, _: &OsStr, _: &[u8]) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn remove_fd(_: BorrowedFd<'_>, _: &OsStr) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn list_fd(_: BorrowedFd<'_>) -> io::Result<XAttrs> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn get_path(_: &Path, _: &OsStr, _: bool) -> io::Result<Vec<u8>> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn set_path(_: &Path, _: &OsStr, _: &[u8], _: bool) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn remove_path(_: &Path, _: &OsStr, _: bool) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}
pub fn list_path(_: &Path, _: bool) -> io::Result<XAttrs> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
UnsupportedPlatformError,
))
}

57
vendor/xattr/src/util.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
use std::io;
use std::mem::MaybeUninit;
pub fn extract_noattr(result: io::Result<Vec<u8>>) -> io::Result<Option<Vec<u8>>> {
result.map(Some).or_else(|e| {
if e.raw_os_error() == Some(crate::sys::ENOATTR) {
Ok(None)
} else {
Err(e)
}
})
}
/// Calls `get_value` to with a buffer and `get_size` to estimate the size of the buffer if/when
/// `get_value` returns ERANGE.
#[allow(dead_code)]
pub fn allocate_loop<F, S>(mut get_value: F, mut get_size: S) -> io::Result<Vec<u8>>
where
F: for<'a> FnMut(&'a mut [MaybeUninit<u8>]) -> io::Result<&'a mut [u8]>,
S: FnMut() -> io::Result<usize>,
{
// Start by assuming the return value is <= 4KiB. If it is, we can do this in one syscall.
const INITIAL_BUFFER_SIZE: usize = 4096;
match get_value(&mut [MaybeUninit::<u8>::uninit(); INITIAL_BUFFER_SIZE]) {
Ok(val) => return Ok(val.to_vec()),
Err(e) if e.raw_os_error() != Some(crate::sys::ERANGE) => return Err(e),
_ => {}
}
// If that fails, we ask for the size and try again with a buffer of the correct size.
let mut vec: Vec<u8> = Vec::new();
loop {
vec.reserve_exact(get_size()?);
match get_value(vec.spare_capacity_mut()) {
Ok(initialized) => {
unsafe {
let len = initialized.len();
assert_eq!(
initialized.as_ptr(),
vec.as_ptr(),
"expected the same buffer"
);
vec.set_len(len);
}
// Only shrink to fit if we've over-allocated by MORE than one byte. Unfortunately,
// on FreeBSD, we have to over-allocate by one byte to determine if we've read all
// the attributes.
if vec.capacity() > vec.len() + 1 {
vec.shrink_to_fit();
}
return Ok(vec);
}
Err(e) if e.raw_os_error() != Some(crate::sys::ERANGE) => return Err(e),
_ => {} // try again
}
}
}