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),
|
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
|
// Only extract the relevant extended attributes
|
||||||
if !xattr::is_valid_xattr_name(&name) {
|
if !xattr::is_valid_xattr_name(&name) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -307,7 +307,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> {
|
||||||
}
|
}
|
||||||
} else if self.has_features(flags::WITH_XATTRS) {
|
} else if self.has_features(flags::WITH_XATTRS) {
|
||||||
xattrs.push(PxarXAttr {
|
xattrs.push(PxarXAttr {
|
||||||
name: name.to_vec(),
|
name: name.to_bytes().to_vec(),
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! *pxar* format decoder.
|
//! *pxar* format decoder.
|
||||||
//!
|
//!
|
||||||
//! This module contain the code to decode *pxar* archive files.
|
//! 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::ffi::{OsStr, OsString};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||||
|
@ -164,9 +164,10 @@ impl<R: Read> SequentialDecoder<R> {
|
||||||
.position(|c| *c == b'\0')
|
.position(|c| *c == b'\0')
|
||||||
.ok_or_else(|| format_err!("no value found in xattr"))?;
|
.ok_or_else(|| format_err!("no value found in xattr"))?;
|
||||||
|
|
||||||
let (name, value) = buffer.split_at(separator);
|
let (name, value) = buffer.split_at(separator + 1);
|
||||||
if !xattr::is_valid_xattr_name(name) || xattr::is_security_capability(name) {
|
let c_name = unsafe { CStr::from_bytes_with_nul_unchecked(name) };
|
||||||
bail!("incorrect xattr name - {}.", String::from_utf8_lossy(name));
|
if !xattr::is_valid_xattr_name(c_name) || xattr::is_security_capability(c_name) {
|
||||||
|
bail!("incorrect xattr name - {:?}.", c_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PxarXAttr {
|
Ok(PxarXAttr {
|
||||||
|
|
|
@ -1,15 +1,59 @@
|
||||||
//! Wrapper functions for the libc xattr calls
|
//! Wrapper functions for the libc xattr calls
|
||||||
|
|
||||||
extern crate libc;
|
use std::ffi::CStr;
|
||||||
|
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
|
|
||||||
use proxmox::tools::vec;
|
use proxmox::tools::vec;
|
||||||
|
|
||||||
use crate::pxar::{PxarXAttr, PxarFCaps};
|
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
|
// Initial buffer size for the attribute list, if content does not fit
|
||||||
// it gets dynamically increased until big enough.
|
// it gets dynamically increased until big enough.
|
||||||
let mut size = 256;
|
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())
|
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 size = 256;
|
||||||
let mut buffer = vec::undefined(size);
|
let mut buffer = vec::undefined(size);
|
||||||
let mut bytes = unsafe {
|
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 {
|
while bytes < 0 {
|
||||||
let err = Errno::last();
|
let err = Errno::last();
|
||||||
|
@ -92,20 +140,21 @@ pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &PxarFCaps) -> Result<(), nix::errno::E
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_security_capability(name: &[u8]) -> bool {
|
pub fn is_security_capability(name: &CStr) -> bool {
|
||||||
name == b"security.capability"
|
name.to_bytes() == b"security.capability"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the passed name buffer starts with a valid xattr namespace prefix
|
/// Check if the passed name buffer starts with a valid xattr namespace prefix
|
||||||
/// and is within the length limit of 255 bytes
|
/// 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 {
|
if name.is_empty() || name.len() > 255 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
|
if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
is_security_capability(name)
|
is_security_capability(c_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -118,6 +167,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fsetxattr_fgetxattr() {
|
fn test_fsetxattr_fgetxattr() {
|
||||||
|
use proxmox::c_str;
|
||||||
|
|
||||||
let path = "./tests/xattrs.txt";
|
let path = "./tests/xattrs.txt";
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
|
@ -167,26 +218,28 @@ mod tests {
|
||||||
assert_eq!(fsetxattr(fd, &invalid_name_prefix), Err(Errno::EOPNOTSUPP));
|
assert_eq!(fsetxattr(fd, &invalid_name_prefix), Err(Errno::EOPNOTSUPP));
|
||||||
assert_eq!(fsetxattr(fd, &invalid_name_length), Err(Errno::ERANGE));
|
assert_eq!(fsetxattr(fd, &invalid_name_length), Err(Errno::ERANGE));
|
||||||
|
|
||||||
let v0 = fgetxattr(fd, b"user.attribute0\0".as_ref()).unwrap();
|
let v0 = fgetxattr(fd, c_str!("user.attribute0")).unwrap();
|
||||||
let v1 = fgetxattr(fd, b"user.empty\0".as_ref()).unwrap();
|
let v1 = fgetxattr(fd, c_str!("user.empty")).unwrap();
|
||||||
|
|
||||||
assert_eq!(v0, b"value0".as_ref());
|
assert_eq!(v0, b"value0".as_ref());
|
||||||
assert_eq!(v1, b"".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();
|
std::fs::remove_file(&path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_valid_xattr_name() {
|
fn test_is_valid_xattr_name() {
|
||||||
let empty = Vec::new();
|
use std::ffi::CString;
|
||||||
let too_long = vec![b'a'; 265];
|
|
||||||
|
|
||||||
assert!(!is_valid_xattr_name(empty.as_slice()));
|
use proxmox::c_str;
|
||||||
assert!(!is_valid_xattr_name(too_long.as_slice()));
|
|
||||||
assert!(!is_valid_xattr_name(b"system.attr"));
|
let too_long = CString::new(vec![b'a'; 265]).unwrap();
|
||||||
assert!(is_valid_xattr_name(b"user.attr"));
|
|
||||||
assert!(is_valid_xattr_name(b"trusted.attr"));
|
assert!(!is_valid_xattr_name(&too_long));
|
||||||
assert!(is_valid_xattr_name(b"security.capability"));
|
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