#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(unix)]
mod unix;
#[cfg(unix)]
use crate::unix as sys;
#[cfg(all(unix, feature = "multilock"))]
mod unix_fileid;
#[cfg(all(unix, feature = "multilock"))]
use unix_fileid as fileid;
#[cfg(not(all(unix, feature = "multilock")))]
mod nil_fileid;
#[cfg(not(all(unix, feature = "multilock")))]
use nil_fileid as fileid;
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
enum Exclusivity {
#[cfg(any(not(unix), feature = "multilock"))]
PerFileDesc,
OsDependent,
}
#[cfg(windows)]
mod windows;
#[cfg(windows)]
use crate::windows as sys;
pub use crate::sys::{Error, OsStr, OsString};
#[cfg(feature = "std")]
use std::{
ffi,
path::{Path, PathBuf},
};
use core::{fmt, ops::Deref};
impl Clone for OsString {
fn clone(&self) -> Self {
self.to_os_str()
.and_then(|str| str.into_os_string())
.expect("Allocation error")
}
}
impl fmt::Debug for OsString {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{:?}", self.as_ref())
}
}
impl fmt::Display for OsString {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.as_ref())
}
}
impl Deref for OsString {
type Target = OsStr;
fn deref(&self) -> &OsStr {
self.as_ref()
}
}
#[derive(Debug)]
pub enum EitherOsStr<'str> {
Borrowed(&'str OsStr),
Owned(OsString),
}
impl<'str> AsRef<OsStr> for EitherOsStr<'str> {
fn as_ref(&self) -> &OsStr {
match self {
Self::Borrowed(str) => str,
Self::Owned(string) => string.as_ref(),
}
}
}
impl<'str> Deref for EitherOsStr<'str> {
type Target = OsStr;
fn deref(&self) -> &OsStr {
self.as_ref()
}
}
pub trait IntoOsString {
fn into_os_string(self) -> Result<OsString, Error>;
}
impl IntoOsString for OsString {
fn into_os_string(self) -> Result<OsString, Error> {
Ok(self)
}
}
impl<'str> IntoOsString for EitherOsStr<'str> {
fn into_os_string(self) -> Result<OsString, Error> {
match self {
Self::Borrowed(str) => str.into_os_string(),
Self::Owned(string) => Ok(string),
}
}
}
#[cfg(feature = "std")]
impl<'str> IntoOsString for &'str ffi::OsStr {
fn into_os_string(self) -> Result<OsString, Error> {
self.to_os_str()?.into_os_string()
}
}
#[cfg(feature = "std")]
impl IntoOsString for PathBuf {
fn into_os_string(self) -> Result<OsString, Error> {
(*self).into_os_string()
}
}
#[cfg(feature = "std")]
impl<'str> IntoOsString for &'str Path {
fn into_os_string(self) -> Result<OsString, Error> {
AsRef::<ffi::OsStr>::as_ref(self).to_os_str()?.into_os_string()
}
}
#[cfg(feature = "std")]
impl IntoOsString for ffi::OsString {
fn into_os_string(self) -> Result<OsString, Error> {
(*self).into_os_string()
}
}
impl<'str> IntoOsString for &'str str {
fn into_os_string(self) -> Result<OsString, Error> {
self.to_os_str()?.into_os_string()
}
}
#[cfg(feature = "std")]
impl IntoOsString for String {
fn into_os_string(self) -> Result<OsString, Error> {
self.to_os_str()?.into_os_string()
}
}
#[cfg(feature = "std")]
impl ToOsStr for String {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
(**self).to_os_str()
}
}
pub trait ToOsStr {
fn to_os_str(&self) -> Result<EitherOsStr, Error>;
}
impl<'str> ToOsStr for EitherOsStr<'str> {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
Ok(match self {
EitherOsStr::Owned(string) => {
EitherOsStr::Owned(string.to_os_str()?.into_os_string()?)
},
EitherOsStr::Borrowed(str) => EitherOsStr::Borrowed(str),
})
}
}
impl ToOsStr for OsStr {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
Ok(EitherOsStr::Borrowed(self))
}
}
impl ToOsStr for OsString {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
Ok(EitherOsStr::Borrowed(self.as_ref()))
}
}
#[cfg(feature = "std")]
impl ToOsStr for ffi::OsString {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
(**self).to_os_str()
}
}
#[cfg(feature = "std")]
impl ToOsStr for PathBuf {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
(**self).to_os_str()
}
}
#[cfg(feature = "std")]
impl ToOsStr for Path {
fn to_os_str(&self) -> Result<EitherOsStr, Error> {
AsRef::<ffi::OsStr>::as_ref(self).to_os_str()
}
}
#[derive(Debug)]
pub struct LockFile {
locked: bool,
id: fileid::FileId,
desc: sys::FileDesc,
}
impl LockFile {
#[cfg(any(not(unix), feature = "multilock"))]
pub fn open_excl<P>(path: &P) -> Result<Self, Error>
where
P: ToOsStr + ?Sized,
{
Self::open_internal(path, Exclusivity::PerFileDesc)
}
pub fn open<P>(path: &P) -> Result<Self, Error>
where
P: ToOsStr + ?Sized,
{
Self::open_internal(path, Exclusivity::OsDependent)
}
fn open_internal<P>(path: &P, ex: Exclusivity) -> Result<Self, Error>
where
P: ToOsStr + ?Sized,
{
let path = path.to_os_str()?;
let desc = sys::open(path.as_ref())?;
let id = fileid::FileId::get_id(desc, ex)?;
Ok(Self { locked: false, id, desc })
}
pub fn lock(&mut self) -> Result<(), Error> {
if self.locked {
panic!("Cannot lock if already owning a lock");
}
self.id.take_lock();
if let Err(error) = sys::lock(self.desc) {
self.id.release_lock();
return Err(error);
}
self.locked = true;
Ok(())
}
pub fn try_lock(&mut self) -> Result<bool, Error> {
if self.locked {
panic!("Cannot lock if already owning a lock");
}
if self.id.try_take_lock() {
let lock_result = sys::try_lock(self.desc);
match lock_result {
Ok(true) => self.locked = true,
_ => self.id.release_lock(),
}
lock_result
} else {
Ok(false)
}
}
pub fn owns_lock(&self) -> bool {
self.locked
}
pub fn unlock(&mut self) -> Result<(), Error> {
if !self.locked {
panic!("Attempted to unlock already locked lockfile");
}
self.locked = false;
sys::unlock(self.desc)?;
self.id.release_lock();
Ok(())
}
}
impl Drop for LockFile {
fn drop(&mut self) {
if self.locked {
let _ = sys::unlock(self.desc);
self.id.release_lock();
self.locked = false;
}
sys::close(self.desc);
}
}
#[cfg(windows)]
unsafe impl Send for LockFile {}
#[cfg(windows)]
unsafe impl Sync for LockFile {}
#[cfg(test)]
mod test {
#[cfg(all(feature = "std", any(not(unix), feature = "multilock")))]
#[test]
fn exclusive_lock_cases() -> Result<(), crate::Error> {
let mut f1 = crate::LockFile::open_excl("lock2.test")?;
let mut f2 = crate::LockFile::open_excl("lock2.test")?;
assert!(f1.try_lock()?);
assert!(!f2.try_lock()?);
let thr = std::thread::spawn(move || {
f2.lock().unwrap();
f2
});
std::thread::sleep(std::time::Duration::from_millis(100));
drop(f1);
let f2 = thr.join().unwrap();
assert!(f2.owns_lock());
Ok(())
}
}