use std::{
cell::UnsafeCell,
collections::HashSet,
fs::{self, File, OpenOptions},
io::{self, Read, Write},
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use bytemuck::{Pod, Zeroable};
use memmap2::MmapMut;
use parking_lot::Mutex;
pub struct GuardedLandfill {
guarded: Landfill,
}
impl GuardedLandfill {
pub fn inner(self) -> Landfill {
self.guarded
}
}
impl Deref for GuardedLandfill {
type Target = Landfill;
fn deref(&self) -> &Landfill {
&self.guarded
}
}
pub trait Substructure: Sized {
fn init(landfill: GuardedLandfill) -> io::Result<Self>;
fn flush(&self) -> io::Result<()>;
}
#[derive(Debug)]
struct LandfillInner {
dir_path: Option<PathBuf>,
reserved_names: Mutex<HashSet<String>>,
self_destruct_sequence_initiated: Mutex<bool>,
}
#[derive(Clone, Debug)]
pub struct Landfill {
inner: Arc<LandfillInner>,
name_prefix: String,
}
impl Landfill {
pub fn open<P: AsRef<Path>>(dir_path: P) -> io::Result<Landfill> {
let dir_path: PathBuf = dir_path.as_ref().into();
if !dir_path.exists() {
fs::create_dir(&dir_path)?;
}
let mut lock_file_path = dir_path.clone();
lock_file_path.push("_lock");
let _lock = OpenOptions::new()
.create_new(true)
.write(true)
.open(&lock_file_path)?;
Ok(Landfill {
inner: Arc::new(LandfillInner {
dir_path: Some(dir_path),
self_destruct_sequence_initiated: Mutex::new(false),
reserved_names: Mutex::new(HashSet::new()),
}),
name_prefix: String::new(),
})
}
pub fn ephemeral() -> io::Result<Landfill> {
Ok(Landfill {
inner: Arc::new(LandfillInner {
dir_path: None,
self_destruct_sequence_initiated: Mutex::new(false),
reserved_names: Mutex::new(HashSet::new()),
}),
name_prefix: String::new(),
})
}
pub fn substructure<S, N>(&self, name: N) -> io::Result<S>
where
S: Substructure,
N: Into<String>,
{
let branch = self.branch(name.into());
if !self.register_name(branch.full_name()) {
return Err(io::Error::new(
io::ErrorKind::Other,
"Attempt at mapping the same substructure twice",
));
}
let guarded = GuardedLandfill { guarded: branch };
S::init(guarded)
}
pub(crate) fn branch(&self, mut name: String) -> Self {
if !self.name_prefix.is_empty() {
name = format!("{}_{name}", self.name_prefix);
}
Landfill {
inner: self.inner.clone(),
name_prefix: name,
}
}
pub fn initiate_self_destruct_sequence(&self) {
*self.inner.self_destruct_sequence_initiated.lock() = true;
}
fn full_name(&self) -> String {
self.name_prefix.clone()
}
fn active_path(&self) -> Option<PathBuf> {
self.inner.dir_path.as_ref().map(|path| {
let name = self.full_name();
let mut path = path.clone();
path.push(name);
path
})
}
pub fn get_static_or_init<Init, T>(&self, init: Init) -> io::Result<T>
where
Init: Fn() -> T,
T: Zeroable + Pod,
{
if let Some(path) = self.active_path() {
if path.exists() {
let t = T::zeroed();
let t_slice = &mut [t];
let byte_slice: &mut [u8] = bytemuck::cast_slice_mut(t_slice);
let mut file = OpenOptions::new().read(true).open(&path)?;
file.read_exact(byte_slice)?;
Ok(t)
} else {
let t = init();
let t_slice = &[t];
let byte_slice: &[u8] = bytemuck::cast_slice(t_slice);
let mut file =
OpenOptions::new().write(true).create(true).open(&path)?;
file.write_all(byte_slice)?;
file.flush()?;
Ok(t)
}
} else {
Ok(init())
}
}
fn register_name(&self, name: String) -> bool {
let mut names = self.inner.reserved_names.lock();
names.insert(name)
}
pub fn map_file_create(&self, size: u64) -> io::Result<Option<MappedFile>> {
if !self.register_name(self.full_name()) {
if let Some(path) = self.active_path() {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
file.set_len(size)?;
let map = UnsafeCell::new(unsafe { MmapMut::map_mut(&file)? });
Ok(Some(MappedFile {
_file: Some(file),
map,
_fill: self.clone(),
}))
} else {
let map = UnsafeCell::new(MmapMut::map_anon(size as usize)?);
Ok(Some(MappedFile {
_file: None,
map,
_fill: self.clone(),
}))
}
} else {
Ok(None)
}
}
pub fn map_file_existing(
&self,
size: u64,
) -> io::Result<Option<MappedFile>> {
let full_name = self.full_name();
if !self.register_name(full_name) {
return Ok(None);
}
if let Some(path) = self.active_path() {
if path.exists() {
match OpenOptions::new().read(true).write(true).open(&path) {
Ok(file) => {
file.set_len(size)?;
let map = UnsafeCell::new(unsafe {
MmapMut::map_mut(&file)?
});
Ok(Some(MappedFile {
_file: Some(file),
map,
_fill: self.clone(),
}))
}
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(e),
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub fn self_destruct(&self) {
*self.inner.self_destruct_sequence_initiated.lock() = true
}
}
impl Drop for LandfillInner {
fn drop(&mut self) {
if let Some(dir_path) = self.dir_path.as_ref() {
let mut lock_file_path = dir_path.clone();
lock_file_path.push("_lock");
let _ = fs::remove_file(lock_file_path);
if *self.self_destruct_sequence_initiated.lock() {
let _ = fs::remove_dir_all(dir_path);
}
}
}
}
pub struct MappedFile {
map: UnsafeCell<MmapMut>,
_file: Option<File>,
_fill: Landfill,
}
impl AsRef<[u8]> for MappedFile {
fn as_ref(&self) -> &[u8] {
unsafe { &(*self.map.get())[..] }
}
}
impl MappedFile {
#[allow(clippy::mut_from_ref)]
pub unsafe fn bytes_mut(&self) -> &mut [u8] {
unsafe { &mut *self.map.get() }
}
pub fn flush(&self) -> io::Result<()> {
unsafe { (*self.map.get()).flush() }
}
}