use std::fs::{remove_file, File, OpenOptions};
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
use std::os::unix::io::AsRawFd;
use std::path::Path;
use anyhow::{bail, Context, Ok, Result};
use nix::fcntl::{fcntl, FcntlArg};
use nix::unistd::getpid;
pub const MAX_FILE_ALIGN: u32 = 4096;
const FILE_LOCK_READ: u64 = 0x01;
const FILE_LOCK_WRITE: u64 = 0x02;
const FILE_LOCK_ALL: [u64; 2] = [FILE_LOCK_READ, FILE_LOCK_WRITE];
const LOCK_PERM_BASE: u64 = 100;
const LOCK_SHARED_BASE: u64 = 200;
pub fn open_file(path: &str, read_only: bool, direct: bool) -> Result<File> {
let mut options = OpenOptions::new();
options.read(true).write(!read_only);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let file = options.open(path).with_context(|| {
format!(
"failed to open the file for block {}. Error: {}",
path,
std::io::Error::last_os_error(),
)
})?;
Ok(file)
}
pub fn get_file_alignment(file: &File, direct: bool) -> Result<(u32, u32)> {
if !direct {
return Ok((1, 1));
}
let metadata = file
.metadata()
.with_context(|| "Failed to get file metadata.")?;
let blk_size = metadata.blksize() as u32;
Ok((blk_size, blk_size))
}
fn do_fcntl_lock(
file: &File,
path: &str,
lockname: &str,
flock: libc::flock,
is_lock: bool,
) -> Result<()> {
let err = match fcntl(file.as_raw_fd(), FcntlArg::F_SETLK(&flock)) {
Err(e) => e,
_ => return Ok(()),
};
if is_lock {
bail!(
"Failed to get {} on file: {}. Is it used more than once or \
another process using the same file? Error: {}",
lockname,
path,
err as i32,
);
} else {
bail!(
"Failed to release lock on file: {}. Error: {}",
path,
err as i32,
);
}
}
fn lock_or_unlock_file(
file: &File,
path: &str,
lock_op: i16,
lock_name: &str,
is_lock: bool,
) -> Result<()> {
let pid = getpid().as_raw();
let mut flock = libc::flock {
l_whence: libc::SEEK_SET as i16,
l_len: 1,
l_pid: pid,
l_type: lock_op,
l_start: 0,
};
for lock in FILE_LOCK_ALL {
flock.l_start = (LOCK_PERM_BASE + lock) as i64;
do_fcntl_lock(file, path, lock_name, flock, is_lock)?;
}
flock.l_start = (LOCK_SHARED_BASE + FILE_LOCK_WRITE) as i64;
do_fcntl_lock(file, path, lock_name, flock, is_lock)?;
Ok(())
}
pub fn lock_file(file: &File, path: &str, read_only: bool) -> Result<()> {
let (lock_op, lock_name) = if read_only {
(libc::F_RDLCK, "read lock")
} else {
(libc::F_WRLCK, "write lock")
};
lock_or_unlock_file(file, path, lock_op as i16, lock_name, true)
}
pub fn unlock_file(file: &File, path: &str) -> Result<()> {
lock_or_unlock_file(file, path, libc::F_UNLCK as i16, "", false)
}
pub fn clear_file(path: String) -> Result<()> {
if Path::new(&path).exists() {
remove_file(&path)
.with_context(|| format!("File {} exists, but failed to remove it.", &path))?;
}
Ok(())
}