update to proxmox-sys 0.2 crate

- imported pbs-api-types/src/common_regex.rs from old proxmox crate
- use hex crate to generate/parse hex digest
- remove all reference to proxmox crate (use proxmox-sys and
  proxmox-serde instead)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
Dietmar Maurer
2021-11-23 17:57:00 +01:00
parent bd00ff10e4
commit 25877d05ac
201 changed files with 627 additions and 1535 deletions

View File

@ -1,334 +0,0 @@
//! Implementation of the calls to handle POSIX access control lists
// see C header file <sys/acl.h> for reference
extern crate libc;
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::RawFd;
use std::path::Path;
use std::ptr;
use libc::{c_char, c_int, c_uint, c_void};
use nix::errno::Errno;
use nix::NixPath;
// from: acl/include/acl.h
pub const ACL_UNDEFINED_ID: u32 = 0xffffffff;
// acl_perm_t values
pub type ACLPerm = c_uint;
pub const ACL_READ: ACLPerm = 0x04;
pub const ACL_WRITE: ACLPerm = 0x02;
pub const ACL_EXECUTE: ACLPerm = 0x01;
// acl_tag_t values
pub type ACLTag = c_int;
pub const ACL_UNDEFINED_TAG: ACLTag = 0x00;
pub const ACL_USER_OBJ: ACLTag = 0x01;
pub const ACL_USER: ACLTag = 0x02;
pub const ACL_GROUP_OBJ: ACLTag = 0x04;
pub const ACL_GROUP: ACLTag = 0x08;
pub const ACL_MASK: ACLTag = 0x10;
pub const ACL_OTHER: ACLTag = 0x20;
// acl_type_t values
pub type ACLType = c_uint;
pub const ACL_TYPE_ACCESS: ACLType = 0x8000;
pub const ACL_TYPE_DEFAULT: ACLType = 0x4000;
// acl entry constants
pub const ACL_FIRST_ENTRY: c_int = 0;
pub const ACL_NEXT_ENTRY: c_int = 1;
// acl to extended attribute names constants
// from: acl/include/acl_ea.h
pub const ACL_EA_ACCESS: &str = "system.posix_acl_access";
pub const ACL_EA_DEFAULT: &str = "system.posix_acl_default";
pub const ACL_EA_VERSION: u32 = 0x0002;
#[link(name = "acl")]
extern "C" {
fn acl_get_file(path: *const c_char, acl_type: ACLType) -> *mut c_void;
fn acl_set_file(path: *const c_char, acl_type: ACLType, acl: *mut c_void) -> c_int;
fn acl_get_fd(fd: RawFd) -> *mut c_void;
fn acl_get_entry(acl: *const c_void, entry_id: c_int, entry: *mut *mut c_void) -> c_int;
fn acl_create_entry(acl: *mut *mut c_void, entry: *mut *mut c_void) -> c_int;
fn acl_get_tag_type(entry: *mut c_void, tag_type: *mut ACLTag) -> c_int;
fn acl_set_tag_type(entry: *mut c_void, tag_type: ACLTag) -> c_int;
fn acl_get_permset(entry: *mut c_void, permset: *mut *mut c_void) -> c_int;
fn acl_clear_perms(permset: *mut c_void) -> c_int;
fn acl_get_perm(permset: *mut c_void, perm: ACLPerm) -> c_int;
fn acl_add_perm(permset: *mut c_void, perm: ACLPerm) -> c_int;
fn acl_get_qualifier(entry: *mut c_void) -> *mut c_void;
fn acl_set_qualifier(entry: *mut c_void, qualifier: *const c_void) -> c_int;
fn acl_init(count: c_int) -> *mut c_void;
fn acl_valid(ptr: *const c_void) -> c_int;
fn acl_free(ptr: *mut c_void) -> c_int;
}
#[derive(Debug)]
pub struct ACL {
ptr: *mut c_void,
}
impl Drop for ACL {
fn drop(&mut self) {
let ret = unsafe { acl_free(self.ptr) };
if ret != 0 {
panic!("invalid pointer encountered while dropping ACL - {}", Errno::last());
}
}
}
impl ACL {
pub fn init(count: usize) -> Result<ACL, nix::errno::Errno> {
let ptr = unsafe { acl_init(count as i32 as c_int) };
if ptr.is_null() {
return Err(Errno::last());
}
Ok(ACL { ptr })
}
pub fn get_file<P: AsRef<Path>>(path: P, acl_type: ACLType) -> Result<ACL, nix::errno::Errno> {
let path_cstr = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
let ptr = unsafe { acl_get_file(path_cstr.as_ptr(), acl_type) };
if ptr.is_null() {
return Err(Errno::last());
}
Ok(ACL { ptr })
}
pub fn set_file<P: NixPath + ?Sized>(&self, path: &P, acl_type: ACLType) -> nix::Result<()> {
path.with_nix_path(|path| {
Errno::result(unsafe { acl_set_file(path.as_ptr(), acl_type, self.ptr) })
})?
.map(drop)
}
pub fn get_fd(fd: RawFd) -> Result<ACL, nix::errno::Errno> {
let ptr = unsafe { acl_get_fd(fd) };
if ptr.is_null() {
return Err(Errno::last());
}
Ok(ACL { ptr })
}
pub fn create_entry(&mut self) -> Result<ACLEntry, nix::errno::Errno> {
let mut ptr = ptr::null_mut() as *mut c_void;
let res = unsafe { acl_create_entry(&mut self.ptr, &mut ptr) };
if res < 0 {
return Err(Errno::last());
}
Ok(ACLEntry {
ptr,
_phantom: PhantomData,
})
}
pub fn is_valid(&self) -> bool {
let res = unsafe { acl_valid(self.ptr) };
if res == 0 {
return true;
}
false
}
pub fn entries(self) -> ACLEntriesIterator {
ACLEntriesIterator {
acl: self,
current: ACL_FIRST_ENTRY,
}
}
pub fn add_entry_full(&mut self, tag: ACLTag, qualifier: Option<u64>, permissions: u64)
-> Result<(), nix::errno::Errno>
{
let mut entry = self.create_entry()?;
entry.set_tag_type(tag)?;
if let Some(qualifier) = qualifier {
entry.set_qualifier(qualifier)?;
}
entry.set_permissions(permissions)?;
Ok(())
}
}
#[derive(Debug)]
pub struct ACLEntry<'a> {
ptr: *mut c_void,
_phantom: PhantomData<&'a mut ()>,
}
impl<'a> ACLEntry<'a> {
pub fn get_tag_type(&self) -> Result<ACLTag, nix::errno::Errno> {
let mut tag = ACL_UNDEFINED_TAG;
let res = unsafe { acl_get_tag_type(self.ptr, &mut tag as *mut ACLTag) };
if res < 0 {
return Err(Errno::last());
}
Ok(tag)
}
pub fn set_tag_type(&mut self, tag: ACLTag) -> Result<(), nix::errno::Errno> {
let res = unsafe { acl_set_tag_type(self.ptr, tag) };
if res < 0 {
return Err(Errno::last());
}
Ok(())
}
pub fn get_permissions(&self) -> Result<u64, nix::errno::Errno> {
let mut permissions = 0;
let mut permset = ptr::null_mut() as *mut c_void;
let mut res = unsafe { acl_get_permset(self.ptr, &mut permset) };
if res < 0 {
return Err(Errno::last());
}
for &perm in &[ACL_READ, ACL_WRITE, ACL_EXECUTE] {
res = unsafe { acl_get_perm(permset, perm) };
if res < 0 {
return Err(Errno::last());
}
if res == 1 {
permissions |= perm as u64;
}
}
Ok(permissions)
}
pub fn set_permissions(&mut self, permissions: u64) -> Result<u64, nix::errno::Errno> {
let mut permset = ptr::null_mut() as *mut c_void;
let mut res = unsafe { acl_get_permset(self.ptr, &mut permset) };
if res < 0 {
return Err(Errno::last());
}
res = unsafe { acl_clear_perms(permset) };
if res < 0 {
return Err(Errno::last());
}
for &perm in &[ACL_READ, ACL_WRITE, ACL_EXECUTE] {
if permissions & perm as u64 == perm as u64 {
res = unsafe { acl_add_perm(permset, perm) };
if res < 0 {
return Err(Errno::last());
}
}
}
Ok(permissions)
}
pub fn get_qualifier(&self) -> Result<u64, nix::errno::Errno> {
let qualifier = unsafe { acl_get_qualifier(self.ptr) };
if qualifier.is_null() {
return Err(Errno::last());
}
let result = unsafe { *(qualifier as *const u32) as u64 };
let ret = unsafe { acl_free(qualifier) };
if ret != 0 {
panic!("invalid pointer encountered while dropping ACL qualifier - {}", Errno::last());
}
Ok(result)
}
pub fn set_qualifier(&mut self, qualifier: u64) -> Result<(), nix::errno::Errno> {
let val = qualifier as u32;
let val_ptr: *const u32 = &val;
let res = unsafe { acl_set_qualifier(self.ptr, val_ptr as *const c_void) };
if res < 0 {
return Err(Errno::last());
}
Ok(())
}
}
#[derive(Debug)]
pub struct ACLEntriesIterator {
acl: ACL,
current: c_int,
}
impl<'a> Iterator for &'a mut ACLEntriesIterator {
type Item = ACLEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut entry_ptr = ptr::null_mut();
let res = unsafe { acl_get_entry(self.acl.ptr, self.current, &mut entry_ptr) };
self.current = ACL_NEXT_ENTRY;
if res == 1 {
return Some(ACLEntry { ptr: entry_ptr, _phantom: PhantomData });
}
None
}
}
/// Helper to transform `PxarEntry`s user mode to acl permissions.
pub fn mode_user_to_acl_permissions(mode: u64) -> u64 {
(mode >> 6) & 7
}
/// Helper to transform `PxarEntry`s group mode to acl permissions.
pub fn mode_group_to_acl_permissions(mode: u64) -> u64 {
(mode >> 3) & 7
}
/// Helper to transform `PxarEntry`s other mode to acl permissions.
pub fn mode_other_to_acl_permissions(mode: u64) -> u64 {
mode & 7
}
/// Buffer to compose ACLs as extended attribute.
pub struct ACLXAttrBuffer {
buffer: Vec<u8>,
}
impl ACLXAttrBuffer {
/// Create a new buffer to write ACLs as extended attribute.
///
/// `version` defines the ACL_EA_VERSION found in acl/include/acl_ea.h
pub fn new(version: u32) -> Self {
let mut buffer = Vec::new();
buffer.extend_from_slice(&version.to_le_bytes());
Self { buffer }
}
/// Add ACL entry to buffer.
pub fn add_entry(&mut self, tag: ACLTag, qualifier: Option<u64>, permissions: u64) {
self.buffer.extend_from_slice(&(tag as u16).to_le_bytes());
self.buffer.extend_from_slice(&(permissions as u16).to_le_bytes());
match qualifier {
Some(qualifier) => self.buffer.extend_from_slice(&(qualifier as u32).to_le_bytes()),
None => self.buffer.extend_from_slice(&ACL_UNDEFINED_ID.to_le_bytes()),
}
}
/// Length of the buffer in bytes.
pub fn len(&self) -> usize {
self.buffer.len()
}
/// The buffer always contains at least the version, it is never empty
pub const fn is_empty(&self) -> bool { false }
/// Borrow raw buffer as mut slice.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
self.buffer.as_mut_slice()
}
}

View File

@ -41,7 +41,7 @@ fn x509name_to_string(name: &openssl::x509::X509NameRef) -> Result<String, Error
impl CertInfo {
pub fn from_path(path: PathBuf) -> Result<Self, Error> {
Self::from_pem(&proxmox::tools::fs::file_get_contents(&path)?)
Self::from_pem(&proxmox_sys::fs::file_get_contents(&path)?)
.map_err(|err| format_err!("failed to load certificate from {:?} - {}", path, err))
}

View File

@ -1,64 +0,0 @@
use anyhow::{bail, format_err, Error};
/// Helper to check result from std::process::Command output
///
/// The exit_code_check() function should return true if the exit code
/// is considered successful.
pub fn command_output(
output: std::process::Output,
exit_code_check: Option<fn(i32) -> bool>,
) -> Result<Vec<u8>, Error> {
if !output.status.success() {
match output.status.code() {
Some(code) => {
let is_ok = match exit_code_check {
Some(check_fn) => check_fn(code),
None => code == 0,
};
if !is_ok {
let msg = String::from_utf8(output.stderr)
.map(|m| {
if m.is_empty() {
String::from("no error message")
} else {
m
}
})
.unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
bail!("status code: {} - {}", code, msg);
}
}
None => bail!("terminated by signal"),
}
}
Ok(output.stdout)
}
/// Helper to check result from std::process::Command output, returns String.
///
/// The exit_code_check() function should return true if the exit code
/// is considered successful.
pub fn command_output_as_string(
output: std::process::Output,
exit_code_check: Option<fn(i32) -> bool>,
) -> Result<String, Error> {
let output = command_output(output, exit_code_check)?;
let output = String::from_utf8(output)?;
Ok(output)
}
pub fn run_command(
mut command: std::process::Command,
exit_code_check: Option<fn(i32) -> bool>,
) -> Result<String, Error> {
let output = command
.output()
.map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
let output = command_output_as_string(output, exit_code_check)
.map_err(|err| format_err!("command {:?} failed - {}", command, err))?;
Ok(output)
}

View File

@ -1,372 +0,0 @@
//! File system helper utilities.
use std::borrow::{Borrow, BorrowMut};
use std::fs::File;
use std::io::{self, BufRead};
use std::ops::{Deref, DerefMut};
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::Path;
use anyhow::{bail, format_err, Error};
use nix::dir;
use nix::dir::Dir;
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
use regex::Regex;
use proxmox::sys::error::SysError;
use proxmox_borrow::Tied;
pub type DirLockGuard = Dir;
/// This wraps nix::dir::Entry with the parent directory's file descriptor.
pub struct ReadDirEntry {
entry: dir::Entry,
parent_fd: RawFd,
}
impl Into<dir::Entry> for ReadDirEntry {
fn into(self) -> dir::Entry {
self.entry
}
}
impl Deref for ReadDirEntry {
type Target = dir::Entry;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl DerefMut for ReadDirEntry {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entry
}
}
impl AsRef<dir::Entry> for ReadDirEntry {
fn as_ref(&self) -> &dir::Entry {
&self.entry
}
}
impl AsMut<dir::Entry> for ReadDirEntry {
fn as_mut(&mut self) -> &mut dir::Entry {
&mut self.entry
}
}
impl Borrow<dir::Entry> for ReadDirEntry {
fn borrow(&self) -> &dir::Entry {
&self.entry
}
}
impl BorrowMut<dir::Entry> for ReadDirEntry {
fn borrow_mut(&mut self) -> &mut dir::Entry {
&mut self.entry
}
}
impl ReadDirEntry {
#[inline]
pub fn parent_fd(&self) -> RawFd {
self.parent_fd
}
pub unsafe fn file_name_utf8_unchecked(&self) -> &str {
std::str::from_utf8_unchecked(self.file_name().to_bytes())
}
}
// Since Tied<T, U> implements Deref to U, a Tied<Dir, Iterator> already implements Iterator.
// This is simply a wrapper with a shorter type name mapping nix::Error to anyhow::Error.
/// Wrapper over a pair of `nix::dir::Dir` and `nix::dir::Iter`, returned by `read_subdir()`.
pub struct ReadDir {
iter: Tied<Dir, dyn Iterator<Item = nix::Result<dir::Entry>> + Send>,
dir_fd: RawFd,
}
impl Iterator for ReadDir {
type Item = Result<ReadDirEntry, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|res| {
res.map(|entry| ReadDirEntry { entry, parent_fd: self.dir_fd })
.map_err(Error::from)
})
}
}
/// Create an iterator over sub directory entries.
/// This uses `openat` on `dirfd`, so `path` can be relative to that or an absolute path.
pub fn read_subdir<P: ?Sized + nix::NixPath>(dirfd: RawFd, path: &P) -> nix::Result<ReadDir> {
let dir = Dir::openat(dirfd, path, OFlag::O_RDONLY, Mode::empty())?;
let fd = dir.as_raw_fd();
let iter = Tied::new(dir, |dir| {
Box::new(unsafe { (*dir).iter() })
as Box<dyn Iterator<Item = nix::Result<dir::Entry>> + Send>
});
Ok(ReadDir { iter, dir_fd: fd })
}
/// Scan through a directory with a regular expression. This is simply a shortcut filtering the
/// results of `read_subdir`. Non-UTF8 compatible file names are silently ignored.
pub fn scan_subdir<'a, P: ?Sized + nix::NixPath>(
dirfd: RawFd,
path: &P,
regex: &'a regex::Regex,
) -> Result<impl Iterator<Item = Result<ReadDirEntry, Error>> + 'a, nix::Error> {
Ok(read_subdir(dirfd, path)?.filter_file_name_regex(regex))
}
/// Scan directory for matching file names with a callback.
///
/// Scan through all directory entries and call `callback()` function
/// if the entry name matches the regular expression. This function
/// used unix `openat()`, so you can pass absolute or relative file
/// names. This function simply skips non-UTF8 encoded names.
pub fn scandir<P, F>(
dirfd: RawFd,
path: &P,
regex: &regex::Regex,
mut callback: F,
) -> Result<(), Error>
where
F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>,
P: ?Sized + nix::NixPath,
{
for entry in scan_subdir(dirfd, path, regex)? {
let entry = entry?;
let file_type = match entry.file_type() {
Some(file_type) => file_type,
None => bail!("unable to detect file type"),
};
callback(
entry.parent_fd(),
unsafe { entry.file_name_utf8_unchecked() },
file_type,
)?;
}
Ok(())
}
/// Helper trait to provide a combinators for directory entry iterators.
pub trait FileIterOps<T, E>
where
Self: Sized + Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
E: Into<Error> + Send + Sync,
{
/// Filter by file type. This is more convenient than using the `filter` method alone as this
/// also includes error handling and handling of files without a type (via an error).
fn filter_file_type(self, ty: dir::Type) -> FileTypeFilter<Self, T, E> {
FileTypeFilter { inner: self, ty }
}
/// Filter by file name. Note that file names which aren't valid utf-8 will be treated as if
/// they do not match the pattern.
fn filter_file_name_regex(self, regex: &Regex) -> FileNameRegexFilter<Self, T, E> {
FileNameRegexFilter { inner: self, regex }
}
}
impl<I, T, E> FileIterOps<T, E> for I
where
I: Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
E: Into<Error> + Send + Sync,
{
}
/// This filters files from its inner iterator by a file type. Files with no type produce an error.
pub struct FileTypeFilter<I, T, E>
where
I: Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
E: Into<Error> + Send + Sync,
{
inner: I,
ty: nix::dir::Type,
}
impl<I, T, E> Iterator for FileTypeFilter<I, T, E>
where
I: Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
E: Into<Error> + Send + Sync,
{
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let item = self.inner.next()?.map_err(|e| e.into());
match item {
Ok(ref entry) => match entry.borrow().file_type() {
Some(ty) => {
if ty == self.ty {
return Some(item);
} else {
continue;
}
}
None => return Some(Err(format_err!("unable to detect file type"))),
},
Err(_) => return Some(item),
}
}
}
}
/// This filters files by name via a Regex. Files whose file name aren't valid utf-8 are skipped
/// silently.
pub struct FileNameRegexFilter<'a, I, T, E>
where
I: Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
{
inner: I,
regex: &'a Regex,
}
impl<I, T, E> Iterator for FileNameRegexFilter<'_, I, T, E>
where
I: Iterator<Item = Result<T, E>>,
T: Borrow<dir::Entry>,
{
type Item = Result<T, E>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let item = self.inner.next()?;
match item {
Ok(ref entry) => {
if let Ok(name) = entry.borrow().file_name().to_str() {
if self.regex.is_match(name) {
return Some(item);
}
}
// file did not match regex or isn't valid utf-8
continue;
},
Err(_) => return Some(item),
}
}
}
}
// /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long)
// read Linux file system attributes (see man chattr)
nix::ioctl_read!(read_attr_fd, b'f', 1, libc::c_long);
nix::ioctl_write_ptr!(write_attr_fd, b'f', 2, libc::c_long);
// /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32)
// read FAT file system attributes
nix::ioctl_read!(read_fat_attr_fd, b'r', 0x10, u32);
nix::ioctl_write_ptr!(write_fat_attr_fd, b'r', 0x11, u32);
// From /usr/include/linux/fs.h
// #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr)
// #define FS_IOC_FSSETXATTR _IOW('X', 32, struct fsxattr)
nix::ioctl_read!(fs_ioc_fsgetxattr, b'X', 31, FSXAttr);
nix::ioctl_write_ptr!(fs_ioc_fssetxattr, b'X', 32, FSXAttr);
#[repr(C)]
#[derive(Debug)]
pub struct FSXAttr {
pub fsx_xflags: u32,
pub fsx_extsize: u32,
pub fsx_nextents: u32,
pub fsx_projid: u32,
pub fsx_cowextsize: u32,
pub fsx_pad: [u8; 8],
}
impl Default for FSXAttr {
fn default() -> Self {
FSXAttr {
fsx_xflags: 0u32,
fsx_extsize: 0u32,
fsx_nextents: 0u32,
fsx_projid: 0u32,
fsx_cowextsize: 0u32,
fsx_pad: [0u8; 8],
}
}
}
/// Attempt to acquire a shared flock on the given path, 'what' and
/// 'would_block_message' are used for error formatting.
pub fn lock_dir_noblock_shared(
path: &std::path::Path,
what: &str,
would_block_msg: &str,
) -> Result<DirLockGuard, Error> {
do_lock_dir_noblock(path, what, would_block_msg, false)
}
/// Attempt to acquire an exclusive flock on the given path, 'what' and
/// 'would_block_message' are used for error formatting.
pub fn lock_dir_noblock(
path: &std::path::Path,
what: &str,
would_block_msg: &str,
) -> Result<DirLockGuard, Error> {
do_lock_dir_noblock(path, what, would_block_msg, true)
}
fn do_lock_dir_noblock(
path: &std::path::Path,
what: &str,
would_block_msg: &str,
exclusive: bool,
) -> Result<DirLockGuard, Error> {
let mut handle = Dir::open(path, OFlag::O_RDONLY, Mode::empty())
.map_err(|err| {
format_err!("unable to open {} directory {:?} for locking - {}", what, path, err)
})?;
// acquire in non-blocking mode, no point in waiting here since other
// backups could still take a very long time
proxmox::tools::fs::lock_file(&mut handle, exclusive, Some(std::time::Duration::from_nanos(0)))
.map_err(|err| {
format_err!(
"unable to acquire lock on {} directory {:?} - {}", what, path,
if err.would_block() {
String::from(would_block_msg)
} else {
err.to_string()
}
)
})?;
Ok(handle)
}
/// Get an iterator over lines of a file, skipping empty lines and comments (lines starting with a
/// `#`).
pub fn file_get_non_comment_lines<P: AsRef<Path>>(
path: P,
) -> Result<impl Iterator<Item = io::Result<String>>, Error> {
let path = path.as_ref();
Ok(io::BufReader::new(
File::open(path).map_err(|err| format_err!("error opening {:?}: {}", path, err))?,
)
.lines()
.filter_map(|line| match line {
Ok(line) => {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
None
} else {
Some(Ok(line.to_string()))
}
}
Err(err) => Some(Err(err)),
}))
}

View File

@ -1,6 +1,6 @@
//! I/O utilities.
use proxmox::tools::fd::Fd;
use proxmox_sys::fd::Fd;
/// The `BufferedRead` trait provides a single function
/// `buffered_read`. It returns a reference to an internal buffer. The

View File

@ -1,9 +1,7 @@
pub mod acl;
pub mod cert;
pub mod cli;
pub mod crypt_config;
pub mod format;
pub mod fs;
pub mod io;
pub mod json;
pub mod lru_cache;
@ -14,9 +12,5 @@ pub mod str;
pub mod sync;
pub mod sys;
pub mod ticket;
pub mod xattr;
pub mod async_lru_cache;
mod command;
pub use command::{command_output, command_output_as_string, run_command};

View File

@ -18,7 +18,7 @@ impl Write for StdChannelWriter {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.0
.send(Ok(buf.to_vec()))
.map_err(proxmox::sys::error::io_err_other)
.map_err(proxmox_sys::error::io_err_other)
.and(Ok(buf.len()))
}

View File

@ -1,230 +0,0 @@
//! Wrapper functions for the libc xattr calls
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use nix::errno::Errno;
use proxmox_io::vec;
use proxmox_lang::c_str;
/// `"security.capability"` as a CStr to avoid typos.
///
/// This cannot be `const` until `const_cstr_unchecked` is stable.
#[inline]
pub fn xattr_name_fcaps() -> &'static CStr {
c_str!("security.capability")
}
/// `"system.posix_acl_access"` as a CStr to avoid typos.
///
/// This cannot be `const` until `const_cstr_unchecked` is stable.
#[inline]
pub fn xattr_acl_access() -> &'static CStr {
c_str!("system.posix_acl_access")
}
/// `"system.posix_acl_default"` as a CStr to avoid typos.
///
/// This cannot be `const` until `const_cstr_unchecked` is stable.
#[inline]
pub fn xattr_acl_default() -> &'static CStr {
c_str!("system.posix_acl_default")
}
/// 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;
let mut buffer = vec::undefined(size);
let mut bytes = unsafe {
libc::flistxattr(fd, buffer.as_mut_ptr() as *mut libc::c_char, buffer.len())
};
while bytes < 0 {
let err = Errno::last();
match err {
Errno::ERANGE => {
// Buffer was not big enough to fit the list, retry with double the size
size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
},
_ => return Err(err),
}
// Retry to read the list with new buffer
buffer.resize(size, 0);
bytes = unsafe {
libc::flistxattr(fd, buffer.as_mut_ptr() as *mut libc::c_char, buffer.len())
};
}
buffer.truncate(bytes as usize);
Ok(ListXAttr::new(buffer))
}
/// 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(), buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
};
while bytes < 0 {
let err = Errno::last();
match err {
Errno::ERANGE => {
// Buffer was not big enough to fit the value, retry with double the size
size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
},
_ => return Err(err),
}
buffer.resize(size, 0);
bytes = unsafe {
libc::fgetxattr(fd, name.as_ptr() as *const libc::c_char, buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
};
}
buffer.resize(bytes as usize, 0);
Ok(buffer)
}
/// Set an extended attribute on a file descriptor.
pub fn fsetxattr(fd: RawFd, name: &CStr, data: &[u8]) -> Result<(), nix::errno::Errno> {
let flags = 0 as libc::c_int;
let result = unsafe {
libc::fsetxattr(fd, name.as_ptr(), data.as_ptr() as *const libc::c_void, data.len(), flags)
};
if result < 0 {
return Err(Errno::last());
}
Ok(())
}
pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &[u8]) -> Result<(), nix::errno::Errno> {
// TODO casync checks and removes capabilities if they are set
fsetxattr(fd, xattr_name_fcaps(), fcaps)
}
pub fn is_security_capability(name: &CStr) -> bool {
name.to_bytes() == xattr_name_fcaps().to_bytes()
}
pub fn is_acl(name: &CStr) -> bool {
name.to_bytes() == xattr_acl_access().to_bytes()
|| name.to_bytes() == xattr_acl_default().to_bytes()
}
/// 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(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;
}
// samba saves windows ACLs there
if name == b"security.NTACL" {
return true;
}
is_security_capability(c_name)
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::fs::OpenOptions;
use std::os::unix::io::AsRawFd;
use nix::errno::Errno;
use proxmox_lang::c_str;
#[test]
fn test_fsetxattr_fgetxattr() {
let path = "./test-xattrs.txt";
let file = OpenOptions::new()
.write(true)
.create(true)
.open(&path)
.unwrap();
let fd = file.as_raw_fd();
assert!(fsetxattr(fd, c_str!("user.attribute0"), b"value0").is_ok());
assert!(fsetxattr(fd, c_str!("user.empty"), b"").is_ok());
if nix::unistd::Uid::current() != nix::unistd::ROOT {
assert_eq!(fsetxattr(fd, c_str!("trusted.attribute0"), b"value0"), Err(Errno::EPERM));
}
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, c_str!("user.attribute1")), Err(Errno::ENODATA));
std::fs::remove_file(&path).unwrap();
}
#[test]
fn test_is_valid_xattr_name() {
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(super::xattr_name_fcaps()));
}
}