#[macro_use]
extern crate log;
use std::fs::{self, File, OpenOptions};
use std::io;
use std::mem::{self, ManuallyDrop};
use std::path::{Path, PathBuf};
use std::ops::Deref;
#[derive(Debug)]
pub struct Lockfile {
handle: ManuallyDrop<File>,
path: PathBuf,
}
impl Lockfile {
pub fn create(path: impl AsRef<Path>) -> Result<Lockfile, io::Error> {
let path = path.as_ref();
let dir = path.parent().expect("lockfile path must have a parent");
fs::create_dir_all(dir)?;
debug!(
"lockfile parent directories created/found at {}",
dir.display()
);
let mut lockfile_opts = OpenOptions::new();
lockfile_opts.create_new(true).read(true).write(true);
let lockfile = lockfile_opts.open(path)?;
debug!("lockfile created at {}", path.display());
Ok(Lockfile {
handle: ManuallyDrop::new(lockfile),
path: path.to_owned(),
})
}
#[inline]
pub fn path(&self) -> &Path {
self.path.as_path()
}
pub fn close(mut self) -> Result<(), io::Error> {
let (handle, path) = unsafe {
let handle = mem::replace(&mut self.handle, mem::zeroed());
let path = mem::replace(&mut self.path, mem::zeroed());
mem::forget(self);
(handle, path)
};
let handle = ManuallyDrop::into_inner(handle);
drop(handle);
fs::remove_file(&path)?;
debug!("Removed lockfile at {}", path.display());
Ok(())
}
}
impl Drop for Lockfile {
fn drop(&mut self) {
unsafe {
ManuallyDrop::drop(&mut self.handle);
if let Err(e) = fs::remove_file(&self.path) {
warn!(
"could not remove lockfile at {}: {}",
self.path.display(),
e
);
}
debug!("Removed lockfile at {}", self.path.display());
}
}
}
impl AsRef<Path> for Lockfile {
#[inline]
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}
impl io::Read for Lockfile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::Read::read(&mut self.handle.deref(), buf)
}
}
impl<'a> io::Read for &'a Lockfile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::Read::read(&mut self.handle.deref(), buf)
}
}
impl io::Write for Lockfile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.handle.deref(), buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.handle.deref())
}
}
impl<'a> io::Write for &'a Lockfile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.handle.deref(), buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.handle.deref())
}
}
impl io::Seek for Lockfile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.handle.deref(), pos)
}
}
impl<'a> io::Seek for &'a Lockfile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.handle.deref(), pos)
}
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use self::tempfile::NamedTempFile;
use super::Lockfile;
use std::path::PathBuf;
use std::fs;
use std::io;
fn tmp_path() -> PathBuf {
NamedTempFile::new().unwrap().into_temp_path().to_owned()
}
#[test]
fn smoke() {
let path = tmp_path();
let lockfile = Lockfile::create(&path).unwrap();
assert_eq!(lockfile.path(), path);
lockfile.close().unwrap();
assert_eq!(fs::metadata(path).unwrap_err().kind(), io::ErrorKind::NotFound);
}
#[test]
fn lock_twice() {
let path = tmp_path();
let _lockfile = Lockfile::create(&path).unwrap();
assert_eq!(Lockfile::create(&path).unwrap_err().kind(), io::ErrorKind::AlreadyExists);
}
}