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:
@ -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()
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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: ®ex::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)),
|
||||
}))
|
||||
}
|
@ -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
|
||||
|
@ -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};
|
||||
|
@ -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()))
|
||||
}
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user