#![allow(unsafe_code)]
use std::collections::BTreeMap;
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::io::AsRawHandle;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use parking_lot::Mutex;
use crate::Result;
use crate::errors::PagedbError;
use super::file::IocpFile;
use super::port::Port;
use crate::vfs::blocking::offload;
use crate::vfs::traits::{Vfs, canonical_native_path, resolve_native_path};
use crate::vfs::types::OpenMode;
use windows_sys::Win32::Foundation::{
ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, GetLastError, HANDLE,
};
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_OVERLAPPED, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx,
UnlockFileEx,
};
use windows_sys::Win32::System::IO::OVERLAPPED;
#[derive(Debug, Clone, Copy)]
enum LockState {
Free,
Exclusive,
Shared(u32),
}
#[derive(Debug, Clone, Copy)]
enum LockKind {
Exclusive,
Shared,
}
struct InProcLockEntry {
state: Mutex<LockState>,
}
struct OsLockFileExHandle {
file: std::fs::File,
}
impl OsLockFileExHandle {
fn try_acquire(path: &std::path::Path, kind: LockKind) -> Result<Self> {
const FILE_SHARE_READ_WRITE: u32 = 0x0000_0003;
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.share_mode(FILE_SHARE_READ_WRITE)
.open(path)
.map_err(PagedbError::Io)?;
let handle = file.as_raw_handle() as HANDLE;
let flags = match kind {
LockKind::Exclusive => LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
LockKind::Shared => LOCKFILE_FAIL_IMMEDIATELY,
};
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
let rc = unsafe { LockFileEx(handle, flags, 0, u32::MAX, u32::MAX, &mut overlapped) };
if rc == 0 {
let err = unsafe { GetLastError() };
if err == ERROR_LOCK_VIOLATION || err == ERROR_IO_PENDING {
return Err(PagedbError::AlreadyLocked);
}
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(Self { file })
}
}
impl Drop for OsLockFileExHandle {
fn drop(&mut self) {
let handle = self.file.as_raw_handle() as HANDLE;
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
let _ = unsafe { UnlockFileEx(handle, 0, u32::MAX, u32::MAX, &mut overlapped) };
}
}
unsafe impl Send for OsLockFileExHandle {}
pub struct IocpLockHandle {
lock_ref: Arc<InProcLockEntry>,
kind: LockKind,
_os_lock: OsLockFileExHandle,
}
impl Drop for IocpLockHandle {
fn drop(&mut self) {
let mut s = self.lock_ref.state.lock();
match (self.kind, *s) {
(LockKind::Exclusive, LockState::Exclusive)
| (LockKind::Shared, LockState::Shared(1)) => *s = LockState::Free,
(LockKind::Shared, LockState::Shared(n)) if n > 1 => {
*s = LockState::Shared(n - 1);
}
_ => {}
}
}
}
struct IocpInner {
root: PathBuf,
port: Port,
next_key: AtomicUsize,
locks: Mutex<BTreeMap<String, Arc<InProcLockEntry>>>,
}
#[derive(Clone)]
pub struct IocpVfs {
inner: Arc<IocpInner>,
}
impl IocpVfs {
pub fn new(root: impl Into<PathBuf>) -> Result<Self> {
let port = Port::new()?;
Ok(Self {
inner: Arc::new(IocpInner {
root: root.into(),
port,
next_key: AtomicUsize::new(1),
locks: Mutex::new(BTreeMap::new()),
}),
})
}
fn resolve(&self, path: &str) -> Result<PathBuf> {
resolve_native_path(&self.inner.root, path)
}
fn lookup_or_create_entry(&self, path: &str) -> Arc<InProcLockEntry> {
let mut locks = self.inner.locks.lock();
locks
.entry(path.to_string())
.or_insert_with(|| {
Arc::new(InProcLockEntry {
state: Mutex::new(LockState::Free),
})
})
.clone()
}
async fn do_lock(&self, path: &str, kind: LockKind) -> Result<IocpLockHandle> {
let logical_path = canonical_native_path(path)?;
let entry = self.lookup_or_create_entry(&logical_path);
{
let mut s = entry.state.lock();
match (kind, *s) {
(LockKind::Exclusive, LockState::Free) => *s = LockState::Exclusive,
(LockKind::Shared, LockState::Free) => *s = LockState::Shared(1),
(LockKind::Shared, LockState::Shared(n)) => *s = LockState::Shared(n + 1),
_ => return Err(PagedbError::AlreadyLocked),
}
}
let lock_path = self.resolve(&logical_path)?;
let acquired = offload(move || {
if let Some(parent) = lock_path.parent() {
std::fs::create_dir_all(parent).map_err(PagedbError::Io)?;
}
OsLockFileExHandle::try_acquire(&lock_path, kind)
})
.await;
match acquired {
Ok(os_lock) => Ok(IocpLockHandle {
lock_ref: entry,
kind,
_os_lock: os_lock,
}),
Err(e) => {
let mut s = entry.state.lock();
match (kind, *s) {
(LockKind::Exclusive, LockState::Exclusive)
| (LockKind::Shared, LockState::Shared(1)) => *s = LockState::Free,
(LockKind::Shared, LockState::Shared(n)) => *s = LockState::Shared(n - 1),
_ => {}
}
Err(e)
}
}
}
}
impl Vfs for IocpVfs {
type File = IocpFile;
type LockHandle = IocpLockHandle;
async fn open(&self, path: &str, mode: OpenMode) -> Result<Self::File> {
let p = self.resolve(path)?;
let (file, writable) = offload(move || {
if matches!(mode, OpenMode::CreateNew | OpenMode::CreateOrOpen) {
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent).map_err(PagedbError::Io)?;
}
}
const FILE_SHARE_RWD: u32 = 0x0000_0007;
let opened = match mode {
OpenMode::Read => {
let f = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_RWD)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&p)
.map_err(PagedbError::Io)?;
(f, false)
}
OpenMode::ReadWrite => {
let f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.share_mode(FILE_SHARE_RWD)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&p)
.map_err(PagedbError::Io)?;
(f, true)
}
OpenMode::CreateNew => {
let f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.share_mode(FILE_SHARE_RWD)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&p)
.map_err(PagedbError::Io)?;
(f, true)
}
OpenMode::CreateOrOpen => {
let f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.share_mode(FILE_SHARE_RWD)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(&p)
.map_err(PagedbError::Io)?;
(f, true)
}
};
Ok(opened)
})
.await?;
let key = self.inner.next_key.fetch_add(1, Ordering::Relaxed);
let handle = file.as_raw_handle() as HANDLE;
self.inner.port.associate(handle, key)?;
Ok(IocpFile::new(
file,
writable,
key,
Arc::clone(&self.inner.port.inner),
))
}
async fn remove(&self, path: &str) -> Result<()> {
let p = self.resolve(path)?;
offload(move || match std::fs::remove_file(&p) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(PagedbError::Io(e)),
})
.await
}
async fn rename(&self, from: &str, to: &str) -> Result<()> {
let f = self.resolve(from)?;
let t = self.resolve(to)?;
offload(move || {
if let Some(parent) = t.parent() {
std::fs::create_dir_all(parent).map_err(PagedbError::Io)?;
}
std::fs::rename(&f, &t).map_err(PagedbError::Io)
})
.await
}
async fn list_dir(&self, path: &str) -> Result<Vec<String>> {
let p = self.resolve(path)?;
offload(move || {
let iter = match std::fs::read_dir(&p) {
Ok(it) => it,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(e) => return Err(PagedbError::Io(e)),
};
let mut out = Vec::new();
for entry in iter {
let entry = entry.map_err(PagedbError::Io)?;
if let Some(name) = entry.file_name().to_str() {
out.push(name.to_string());
}
}
out.sort();
Ok(out)
})
.await
}
async fn mkdir_all(&self, path: &str) -> Result<()> {
let p = self.resolve(path)?;
offload(move || std::fs::create_dir_all(&p).map_err(PagedbError::Io)).await
}
async fn sync_dir(&self, path: &str) -> Result<()> {
canonical_native_path(path)?;
Ok(())
}
async fn lock_exclusive(&self, path: &str) -> Result<Self::LockHandle> {
self.do_lock(path, LockKind::Exclusive).await
}
async fn lock_shared(&self, path: &str) -> Result<Self::LockHandle> {
self.do_lock(path, LockKind::Shared).await
}
fn root_path(&self) -> Option<&std::path::Path> {
Some(&self.inner.root)
}
}