xattr: api cleanup
Make `flistxattr()` return a `ListXAttr` helper which provides an iterator over `&CStr`. This exposes the property that xattr names are a zero-terminated string without simply being an opaque "byte vector". Using &[u8] as a type here is too lax. Also let `fgetxattr` take a `CStr`. While this may be a burden on the caller, we usually already have zero-terminated strings on the call site. Currently we only use this method coming from `flistxattr` after all. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
9af76ef075
commit
27a3decbfe
|
@ -287,7 +287,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> {
|
|||
Err(err) => bail!("read_xattrs failed for {:?} - {}", self.full_path(), err),
|
||||
};
|
||||
|
||||
for name in xattr_names.split(|c| *c == b'\0') {
|
||||
for name in &xattr_names {
|
||||
// Only extract the relevant extended attributes
|
||||
if !xattr::is_valid_xattr_name(&name) {
|
||||
continue;
|
||||
|
@ -307,7 +307,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> {
|
|||
}
|
||||
} else if self.has_features(flags::WITH_XATTRS) {
|
||||
xattrs.push(PxarXAttr {
|
||||
name: name.to_vec(),
|
||||
name: name.to_bytes().to_vec(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! *pxar* format decoder.
|
||||
//!
|
||||
//! This module contain the code to decode *pxar* archive files.
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
|
@ -164,9 +164,10 @@ impl<R: Read> SequentialDecoder<R> {
|
|||
.position(|c| *c == b'\0')
|
||||
.ok_or_else(|| format_err!("no value found in xattr"))?;
|
||||
|
||||
let (name, value) = buffer.split_at(separator);
|
||||
if !xattr::is_valid_xattr_name(name) || xattr::is_security_capability(name) {
|
||||
bail!("incorrect xattr name - {}.", String::from_utf8_lossy(name));
|
||||
let (name, value) = buffer.split_at(separator + 1);
|
||||
let c_name = unsafe { CStr::from_bytes_with_nul_unchecked(name) };
|
||||
if !xattr::is_valid_xattr_name(c_name) || xattr::is_security_capability(c_name) {
|
||||
bail!("incorrect xattr name - {:?}.", c_name);
|
||||
}
|
||||
|
||||
Ok(PxarXAttr {
|
||||
|
|
|
@ -1,15 +1,59 @@
|
|||
//! Wrapper functions for the libc xattr calls
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
|
||||
use proxmox::tools::vec;
|
||||
|
||||
use crate::pxar::{PxarXAttr, PxarFCaps};
|
||||
|
||||
pub fn flistxattr(fd: RawFd) -> Result<Vec<u8>, nix::errno::Errno> {
|
||||
/// Result of `flistxattr`, allows iterating over the attributes as a list of `&CStr`s.
|
||||
///
|
||||
/// Listing xattrs produces a list separated by zeroes, inherently making them available as `&CStr`
|
||||
/// already, so we make use of this fact and reflect this in the interface.
|
||||
pub struct ListXAttr {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ListXAttr {
|
||||
fn new(data: Vec<u8>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ListXAttr {
|
||||
type Item = &'a CStr;
|
||||
type IntoIter = ListXAttrIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
ListXAttrIter {
|
||||
data: &self.data,
|
||||
at: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the extended attribute entries in a `ListXAttr`.
|
||||
pub struct ListXAttrIter<'a> {
|
||||
data: &'a [u8],
|
||||
at: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ListXAttrIter<'a> {
|
||||
type Item = &'a CStr;
|
||||
|
||||
fn next(&mut self) -> Option<&'a CStr> {
|
||||
let data = &self.data[self.at..];
|
||||
let next = data.iter().position(|b| *b == 0)? + 1;
|
||||
self.at += next;
|
||||
Some(unsafe { CStr::from_bytes_with_nul_unchecked(&data[..next]) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a list of extended attributes accessible as an iterator over items of type `&CStr`.
|
||||
pub fn flistxattr(fd: RawFd) -> Result<ListXAttr, nix::errno::Errno> {
|
||||
// Initial buffer size for the attribute list, if content does not fit
|
||||
// it gets dynamically increased until big enough.
|
||||
let mut size = 256;
|
||||
|
@ -32,16 +76,20 @@ pub fn flistxattr(fd: RawFd) -> Result<Vec<u8>, nix::errno::Errno> {
|
|||
libc::flistxattr(fd, buffer.as_mut_ptr() as *mut i8, buffer.len())
|
||||
};
|
||||
}
|
||||
buffer.resize(bytes as usize, 0);
|
||||
buffer.truncate(bytes as usize);
|
||||
|
||||
Ok(buffer)
|
||||
Ok(ListXAttr::new(buffer))
|
||||
}
|
||||
|
||||
pub fn fgetxattr(fd: RawFd, name: &[u8]) -> Result<Vec<u8>, nix::errno::Errno> {
|
||||
/// Get an extended attribute by name.
|
||||
///
|
||||
/// Extended attributes may not contain zeroes, which we enforce in the API by using a `&CStr`
|
||||
/// type.
|
||||
pub fn fgetxattr(fd: RawFd, name: &CStr) -> Result<Vec<u8>, nix::errno::Errno> {
|
||||
let mut size = 256;
|
||||
let mut buffer = vec::undefined(size);
|
||||
let mut bytes = unsafe {
|
||||
libc::fgetxattr(fd, name.as_ptr() as *const i8, buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
|
||||
libc::fgetxattr(fd, name.as_ptr(), buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
|
||||
};
|
||||
while bytes < 0 {
|
||||
let err = Errno::last();
|
||||
|
@ -92,20 +140,21 @@ pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &PxarFCaps) -> Result<(), nix::errno::E
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_security_capability(name: &[u8]) -> bool {
|
||||
name == b"security.capability"
|
||||
pub fn is_security_capability(name: &CStr) -> bool {
|
||||
name.to_bytes() == b"security.capability"
|
||||
}
|
||||
|
||||
/// Check if the passed name buffer starts with a valid xattr namespace prefix
|
||||
/// and is within the length limit of 255 bytes
|
||||
pub fn is_valid_xattr_name(name: &[u8]) -> bool {
|
||||
pub fn is_valid_xattr_name(c_name: &CStr) -> bool {
|
||||
let name = c_name.to_bytes();
|
||||
if name.is_empty() || name.len() > 255 {
|
||||
return false;
|
||||
}
|
||||
if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
|
||||
return true;
|
||||
}
|
||||
is_security_capability(name)
|
||||
is_security_capability(c_name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -118,6 +167,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_fsetxattr_fgetxattr() {
|
||||
use proxmox::c_str;
|
||||
|
||||
let path = "./tests/xattrs.txt";
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
|
@ -167,26 +218,28 @@ mod tests {
|
|||
assert_eq!(fsetxattr(fd, &invalid_name_prefix), Err(Errno::EOPNOTSUPP));
|
||||
assert_eq!(fsetxattr(fd, &invalid_name_length), Err(Errno::ERANGE));
|
||||
|
||||
let v0 = fgetxattr(fd, b"user.attribute0\0".as_ref()).unwrap();
|
||||
let v1 = fgetxattr(fd, b"user.empty\0".as_ref()).unwrap();
|
||||
let v0 = fgetxattr(fd, c_str!("user.attribute0")).unwrap();
|
||||
let v1 = fgetxattr(fd, c_str!("user.empty")).unwrap();
|
||||
|
||||
assert_eq!(v0, b"value0".as_ref());
|
||||
assert_eq!(v1, b"".as_ref());
|
||||
assert_eq!(fgetxattr(fd, b"user.attribute1\0".as_ref()), Err(Errno::ENODATA));
|
||||
assert_eq!(fgetxattr(fd, c_str!("user.attribute1")), Err(Errno::ENODATA));
|
||||
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_xattr_name() {
|
||||
let empty = Vec::new();
|
||||
let too_long = vec![b'a'; 265];
|
||||
use std::ffi::CString;
|
||||
|
||||
assert!(!is_valid_xattr_name(empty.as_slice()));
|
||||
assert!(!is_valid_xattr_name(too_long.as_slice()));
|
||||
assert!(!is_valid_xattr_name(b"system.attr"));
|
||||
assert!(is_valid_xattr_name(b"user.attr"));
|
||||
assert!(is_valid_xattr_name(b"trusted.attr"));
|
||||
assert!(is_valid_xattr_name(b"security.capability"));
|
||||
use proxmox::c_str;
|
||||
|
||||
let too_long = CString::new(vec![b'a'; 265]).unwrap();
|
||||
|
||||
assert!(!is_valid_xattr_name(&too_long));
|
||||
assert!(!is_valid_xattr_name(c_str!("system.attr")));
|
||||
assert!(is_valid_xattr_name(c_str!("user.attr")));
|
||||
assert!(is_valid_xattr_name(c_str!("trusted.attr")));
|
||||
assert!(is_valid_xattr_name(c_str!("security.capability")));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue