#![doc(html_root_url = "https://docs.rs/fcntl/0.1.0")]
use libc::{
__errno_location,
fcntl as libc_fcntl,
};
use std::{
convert::TryInto,
error::Error,
fmt::{self, Display},
os::unix::io::AsRawFd,
};
pub use libc::{
c_int,
c_short,
flock,
};
#[derive(Copy, Clone)]
pub enum FcntlArg {
Flock(flock),
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum FcntlCmd {
SetLock,
SetLockWait,
GetLock,
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum FcntlError {
CommandNotImplemented(FcntlCmd),
Errno(Option<c_int>),
Internal,
InvalidArgForCmd,
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum FcntlLockType {
Read,
Write,
}
pub trait FlockOperations {
fn default() -> Self;
fn with_l_type(self, l_type: c_short) -> Self;
fn with_locktype(self, locktype: FcntlLockType) -> Self;
}
pub fn fcntl<'a, RF>(fd: &'a RF, cmd: FcntlCmd, arg: FcntlArg) -> Result<FcntlArg, FcntlError>
where RF: AsRawFd
{
let fd = fd.as_raw_fd();
match cmd {
FcntlCmd::GetLock | FcntlCmd::SetLock => {
match arg {
FcntlArg::Flock(flock) => {
let mut flock = flock;
let rv = unsafe { libc_fcntl(fd, cmd.into(), &mut flock) };
if rv == 0 {
Ok(FcntlArg::Flock(flock))
} else {
let errno_ptr = unsafe { __errno_location() };
let errno = if errno_ptr.is_null() {
None
} else {
Some(unsafe {*errno_ptr})
};
Err(FcntlError::Errno(errno))
}
}
_ => Err(FcntlError::InvalidArgForCmd),
}
}
FcntlCmd::SetLockWait => Err(FcntlError::CommandNotImplemented(FcntlCmd::SetLockWait)),
}
}
pub fn is_file_locked<'a, RF>(fd: &'a RF, flock: Option<flock>) -> Result<bool, FcntlError>
where RF: AsRawFd
{
let arg = match flock {
Some(flock) => FcntlArg::Flock(flock),
None => FcntlArg::Flock(libc::flock::default())
};
match fcntl(fd, FcntlCmd::GetLock, arg) {
Ok(FcntlArg::Flock(result)) => {
Ok(result.l_type != libc::F_UNLCK.try_into().unwrap())
}
Ok(_) => Err(FcntlError::Internal),
Err(err) => Err(err),
}
}
pub fn lock_file<'a, RF>(fd: &'a RF, flock: Option<flock>, locktype: Option<FcntlLockType>) -> Result<bool, FcntlError>
where RF: AsRawFd
{
let locktype = locktype.unwrap_or(FcntlLockType::Read);
let arg = match flock {
Some(flock) => FcntlArg::Flock(flock.with_locktype(locktype)),
None => FcntlArg::Flock(libc::flock::default().with_locktype(locktype)),
};
match fcntl(fd, FcntlCmd::SetLock, arg) {
Ok(FcntlArg::Flock(_result)) => Ok(true),
Ok(_) => Err(FcntlError::Internal),
Err(FcntlError::Errno(Some(libc::EACCES))) | Err(FcntlError::Errno(Some(libc::EAGAIN))) => Ok(false),
Err(err) => Err(err),
}
}
pub fn unlock_file<'a, RF>(fd: &'a RF, flock: Option<flock>) -> Result<bool, FcntlError>
where RF: AsRawFd
{
let arg = match flock {
Some(flock) => FcntlArg::Flock(flock.with_l_type(libc::F_UNLCK.try_into().unwrap())),
None => FcntlArg::Flock(libc::flock::default().with_l_type(libc::F_UNLCK.try_into().unwrap(),)),
};
match fcntl(fd, FcntlCmd::SetLock, arg) {
Ok(FcntlArg::Flock(_result)) => Ok(true),
Ok(_) => Err(FcntlError::Internal),
Err(err) => Err(err),
}
}
impl FlockOperations for flock {
fn default() -> Self {
flock {
l_type: 0,
l_whence: 0,
l_start: 0,
l_len: 0,
l_pid: 0,
}
}
fn with_l_type(mut self, l_type: c_short) -> Self {
self.l_type = l_type;
self
}
fn with_locktype(mut self, locktype: FcntlLockType) -> Self {
self.l_type = locktype.into();
self
}
}
impl Display for FcntlError {
fn fmt(&self, ff: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CommandNotImplemented(cmd) => write!(ff, "{:?} is not implemented for this operation", cmd),
Self::Errno(Some(errno)) => write!(ff, "syscall returned unknown or unexpected value: {}", errno),
Self::Errno(None) => write!(ff, "syscall returned error but we could not retrieve errno"),
Self::Internal => write!(ff, "we encountered an internal error. Please report this as a bug (fcntl)!"),
Self::InvalidArgForCmd => write!(ff, "the provided arg parameter is invalid for the requested cmd"),
}
}
}
impl Error for FcntlError {}
impl From<FcntlCmd> for c_int {
fn from(cmd: FcntlCmd) -> c_int {
match cmd {
FcntlCmd::GetLock => libc::F_GETLK,
FcntlCmd::SetLock => libc::F_SETLK,
FcntlCmd::SetLockWait => libc::F_SETLKW,
}
}
}
impl From<FcntlLockType> for c_short {
fn from(locktype: FcntlLockType) -> c_short {
match locktype {
FcntlLockType::Read => libc::F_RDLCK.try_into().unwrap(),
FcntlLockType::Write => libc::F_WRLCK.try_into().unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{OpenOptions, remove_file};
const LOCK_FILE_NAME: &str = "./test-work-dir/lock-test";
#[test]
fn check_cmd_conversion() {
let pairs = vec![
(FcntlCmd::GetLock, libc::F_GETLK),
(FcntlCmd::SetLock, libc::F_SETLK),
(FcntlCmd::SetLockWait, libc::F_SETLKW),
];
for (cmd, check) in pairs.into_iter() {
assert_eq!(libc::c_int::from(cmd), check);
}
}
#[test]
fn ensure_conversions_dont_panic() {
let _: libc::c_short = libc::F_UNLCK.try_into().unwrap();
}
#[test]
fn check_file_locking_simple() {
let result = {
let file = OpenOptions::new().write(true).create(true).open(LOCK_FILE_NAME).unwrap();
is_file_locked(&file, None)
};
let _ = remove_file(LOCK_FILE_NAME);
assert_eq!(result, Ok(false));
}
#[test]
fn lock_file_simple() {
{
let file = OpenOptions::new().write(true).create(true).open(LOCK_FILE_NAME).unwrap();
}
let file = OpenOptions::new().read(true).open(LOCK_FILE_NAME).unwrap();
let result_is_file_locked_before = is_file_locked(&file, None);
let result_lock_file_read = lock_file(&file, None, None);
let result_unlock_after = unlock_file(&file, None);
let _ = remove_file(LOCK_FILE_NAME);
assert_eq!(result_is_file_locked_before, Ok(false), "Verify that file was unlocked");
assert_eq!(result_lock_file_read, Ok(true), "Lock file");
assert_eq!(result_unlock_after, Ok(true), "Unlock file");
}
}