use new atomic_open_or_create_file

Factor out open_backup_lockfile() method to acquire locks owned by
user backup with permission 0660.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Dietmar Maurer
2021-07-20 13:51:54 +02:00
committed by Thomas Lamprecht
parent a00888e93f
commit 7526d86419
29 changed files with 161 additions and 270 deletions

View File

@ -1,21 +1,17 @@
//! 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 anyhow::Error;
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::fs::CreateOptions;
use proxmox::tools::mmap::Mmap;
/// In-memory communication channel.
@ -32,6 +28,7 @@ struct Head {
static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new();
const MEMCOM_FILE_PATH: &str = pbs_buildcfg::rundir!("/proxmox-backup-memcom");
const EMPTY_PAGE: [u8; 4096] = [0u8; 4096];
impl Memcom {
/// Open the memory based communication channel singleton.
@ -41,15 +38,20 @@ impl Memcom {
// 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 user = crate::backup::backup_user()?;
let options = CreateOptions::new()
.perm(Mode::from_bits_truncate(0o660))
.owner(user.uid)
.group(user.gid);
let file = proxmox::tools::fs::atomic_open_or_create_file(
MEMCOM_FILE_PATH,
OFlag::O_RDWR | OFlag::O_CLOEXEC,
&EMPTY_PAGE, options)?;
let mmap = unsafe {
Mmap::<u8>::map_fd(
fd.as_raw_fd(),
file.as_raw_fd(),
0,
4096,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
@ -77,87 +79,3 @@ impl Memcom {
.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
);
}