#[cfg(all(feature = "fs", target_os = "linux"))]
use std::path::PathBuf;
use std::os::fd::OwnedFd;
#[cfg(all(feature = "fs", target_os = "linux"))]
use nix::{
errno::Errno,
fcntl::{FcntlArg, fcntl},
libc,
unistd::Uid,
};
#[cfg(all(feature = "fs", target_os = "linux"))]
use crate::FileId;
use crate::{FilesystemId, block::FileId as StoreFileId};
#[cfg(all(feature = "fs", target_os = "linux"))]
#[cfg_attr(coverage_nightly, coverage(off))]
fn runtime_dir() -> PathBuf {
std::env::var_os("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(format!("/run/user/{}", Uid::current())))
}
#[derive(Debug)]
pub enum LockResult {
#[cfg_attr(not(all(feature = "fs", target_os = "linux")), allow(dead_code))]
Acquired(LockHandle),
Locked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Read,
Write,
}
#[derive(Debug, Clone)]
pub struct LockHandler {
#[cfg(all(feature = "fs", target_os = "linux"))]
lock_file_path: PathBuf,
}
#[derive(Debug)]
pub struct LockHandle {
_fd: OwnedFd,
}
impl LockHandle {
#[cfg_attr(not(all(feature = "fs", target_os = "linux")), allow(dead_code))]
fn new(fd: OwnedFd) -> Self {
Self { _fd: fd }
}
}
impl LockHandler {
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn new(fs: FilesystemId) -> Self {
Self {
lock_file_path: runtime_dir()
.join("liteboxfs")
.join("fs")
.join(fs.to_string())
.join("handles.lock"),
}
}
#[cfg(not(all(feature = "fs", target_os = "linux")))]
pub fn new(_fs: FilesystemId) -> Self {
Self {}
}
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn acquire_lock(&self, file: StoreFileId, kind: LockType) -> crate::Result<LockResult> {
use std::{fs, io};
fs::create_dir_all(
self.lock_file_path
.parent()
.expect("Expected lock file path to have a parent directory."),
)?;
let lock_fd = fs::File::options()
.create(true)
.read(true)
.append(true)
.open(&self.lock_file_path)?
.into();
let result = fcntl(
&lock_fd,
FcntlArg::F_OFD_SETLK(&libc::flock {
l_type: match kind {
LockType::Read => libc::F_RDLCK as i16,
LockType::Write => libc::F_WRLCK as i16,
},
l_whence: libc::SEEK_SET as i16,
l_start: file.into(),
l_len: 1,
l_pid: 0,
}),
);
match result {
Ok(_) => Ok(LockResult::Acquired(LockHandle::new(lock_fd))),
Err(Errno::EAGAIN) | Err(Errno::EACCES) => Ok(LockResult::Locked),
Err(e) => Err(crate::Error::from(io::Error::from_raw_os_error(e as i32))),
}
}
#[cfg(not(all(feature = "fs", target_os = "linux")))]
pub fn acquire_lock(&self, _file: StoreFileId, _kind: LockType) -> crate::Result<LockResult> {
Ok(LockResult::Locked)
}
}
#[cfg(all(feature = "fs", target_os = "linux"))]
pub struct RawFd {
_handle: LockHandle,
file_id: StoreFileId,
filesystem_id: FilesystemId,
}
#[cfg(all(feature = "fs", target_os = "linux"))]
impl RawFd {
pub(crate) fn new(
handle: LockHandle,
file_id: StoreFileId,
filesystem_id: FilesystemId,
) -> Self {
Self {
_handle: handle,
file_id,
filesystem_id,
}
}
pub(crate) fn into_file_id(self) -> StoreFileId {
self.file_id
}
pub fn file_id(&self) -> FileId {
FileId::new(self.file_id, self.filesystem_id)
}
}
#[cfg(all(feature = "fs", target_os = "linux"))]
#[cfg_attr(coverage_nightly, coverage(off))]
impl std::fmt::Debug for RawFd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RawFd").field(&self.file_id).finish()
}
}