use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
use super::user::{ApiToken, User};
use crate::api2::types::{Authid, Userid};
+use crate::tools::Memcom;
/// Cache User/Group/Token/Acl configuration data for fast permission tests
pub struct CachedUserInfo {
struct ConfigCache {
data: Option<Arc<CachedUserInfo>>,
last_update: i64,
+ last_user_cache_generation: usize,
}
lazy_static! {
static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
- ConfigCache { data: None, last_update: 0 }
+ ConfigCache { data: None, last_update: 0, last_user_cache_generation: 0 }
);
}
/// Returns a cached instance (up to 5 seconds old).
pub fn new() -> Result<Arc<Self>, Error> {
let now = epoch_i64();
+
+ let memcom = Memcom::new()?;
+ let user_cache_generation = memcom.user_cache_generation();
+
{ // limit scope
let cache = CACHED_CONFIG.read().unwrap();
- if (now - cache.last_update) < 5 {
+ if (user_cache_generation == cache.last_user_cache_generation) &&
+ ((now - cache.last_update) < 5)
+ {
if let Some(ref config) = cache.data {
return Ok(config.clone());
}
let mut cache = CACHED_CONFIG.write().unwrap();
cache.last_update = now;
+ cache.last_user_cache_generation = user_cache_generation;
cache.data = Some(config.clone());
Ok(config)
--- /dev/null
+//! Memory based communication channel between proxy & daemon for things such as cache
+//! invalidation.
+
+use std::ffi::CString;
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use anyhow::{bail, format_err, Error};
+use nix::errno::Errno;
+use nix::fcntl::OFlag;
+use nix::sys::mman::{MapFlags, ProtFlags};
+use nix::sys::stat::Mode;
+use once_cell::sync::OnceCell;
+
+use proxmox::sys::error::SysError;
+use proxmox::tools::fd::Fd;
+use proxmox::tools::mmap::Mmap;
+
+/// In-memory communication channel.
+pub struct Memcom {
+ mmap: Mmap<u8>,
+}
+
+#[repr(C)]
+struct Head {
+ // User (user.cfg) cache generation/version.
+ user_cache_generation: AtomicUsize,
+}
+
+static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new();
+
+const MEMCOM_FILE_PATH: &str = rundir!("/proxmox-backup-memcom");
+
+impl Memcom {
+
+ /// Open the memory based communication channel singleton.
+ pub fn new() -> Result<Arc<Self>, Error> {
+ INSTANCE.get_or_try_init(Self::open).map(Arc::clone)
+ }
+
+ // Actual work of `new`:
+ fn open() -> Result<Arc<Self>, Error> {
+ let fd = match open_existing() {
+ Ok(fd) => fd,
+ Err(err) if err.not_found() => create_new()?,
+ Err(err) => bail!("failed to open {} - {}", MEMCOM_FILE_PATH, err),
+ };
+
+ let mmap = unsafe {
+ Mmap::<u8>::map_fd(
+ fd.as_raw_fd(),
+ 0,
+ 4096,
+ ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
+ MapFlags::MAP_SHARED | MapFlags::MAP_NORESERVE | MapFlags::MAP_POPULATE,
+ )?
+ };
+
+ Ok(Arc::new(Self { mmap }))
+ }
+
+ // Shortcut to get the mapped `Head` as a `Head`.
+ fn head(&self) -> &Head {
+ unsafe { &*(self.mmap.as_ptr() as *const u8 as *const Head) }
+ }
+
+ /// Returns the user cache generation number.
+ pub fn user_cache_generation(&self) -> usize {
+ self.head().user_cache_generation.load(Ordering::Acquire)
+ }
+
+ /// Increase the user cache generation number.
+ pub fn increase_user_cache_generation(&self) {
+ self.head()
+ .user_cache_generation
+ .fetch_add(1, Ordering::AcqRel);
+ }
+}
+
+/// The fast path opens an existing file.
+fn open_existing() -> Result<Fd, nix::Error> {
+ Fd::open(MEMCOM_FILE_PATH, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty())
+}
+
+/// Since we need to initialize the file, we also need a solid slow path where we create the file.
+/// In order to make sure the next user's `open()` vs `mmap()` race against our `truncate()` call,
+/// we create it in a temporary location and rotate it in place.
+fn create_new() -> Result<Fd, Error> {
+ // create a temporary file:
+ let temp_file_name = format!("{}.{}", MEMCOM_FILE_PATH, unsafe { libc::getpid() });
+ let fd = Fd::open(
+ temp_file_name.as_str(),
+ OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR | OFlag::O_CLOEXEC,
+ Mode::from_bits_truncate(0o660),
+ ).map_err(|err| {
+ format_err!(
+ "failed to create new in-memory communication file at {} - {}",
+ temp_file_name,
+ err
+ )
+ })?;
+
+ // let it be a page in size, it'll be initialized to zero by the kernel
+ nix::unistd::ftruncate(fd.as_raw_fd(), 4096)
+ .map_err(|err| format_err!("failed to set size of {} - {}", temp_file_name, err))?;
+
+ // if this is the pbs-daemon (running as root) rather than the proxy (running as backup user),
+ // make sure the backup user can access the file:
+ if let Ok(backup_user) = crate::backup::backup_user() {
+ match nix::unistd::fchown(fd.as_raw_fd(), None, Some(backup_user.gid)) {
+ Ok(()) => (),
+ Err(err) if err.is_errno(Errno::EPERM) => {
+ // we're not the daemon (root), so the file is already owned by the backup user
+ }
+ Err(err) => bail!(
+ "failed to set group to 'backup' for {} - {}",
+ temp_file_name,
+ err
+ ),
+ }
+ }
+
+ // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against
+ // the initialization, the first one wins!
+ // TODO: nicer `renameat2()` wrapper in `proxmox::sys`?
+ let c_file_name = CString::new(temp_file_name.as_bytes()).unwrap();
+ let new_path = CString::new(MEMCOM_FILE_PATH).unwrap();
+ let rc = unsafe {
+ libc::renameat2(
+ -1,
+ c_file_name.as_ptr(),
+ -1,
+ new_path.as_ptr(),
+ libc::RENAME_NOREPLACE,
+ )
+ };
+ if rc == 0 {
+ return Ok(fd);
+ }
+ let err = io::Error::last_os_error();
+
+ // if another process has already raced ahead and created the file, let's just open theirs
+ // instead:
+ if err.kind() == io::ErrorKind::AlreadyExists {
+ // someone beat us to it:
+ drop(fd);
+ return open_existing().map_err(Error::from);
+ }
+
+ // for any other errors, just bail out
+ bail!(
+ "failed to move file at {} into place at {} - {}",
+ temp_file_name,
+ MEMCOM_FILE_PATH,
+ err
+ );
+}