Add native file locks for unix and windows systems

This commit implements file locks for unix-based systems and windows
systems. This is done by using platform-specific `__sys` modules for
locking, trying to lock and unlocking files.

A build script was added for unix-systems that make use of the
flock-posix.c file
next
Sayan Nandan 3 years ago
parent aca9888109
commit 6e680b33e5

2
Cargo.lock generated

@ -686,6 +686,7 @@ version = "0.5.1"
dependencies = [
"bincode",
"bytes",
"cc",
"chrono",
"clap",
"env_logger",
@ -704,6 +705,7 @@ dependencies = [
"tokio",
"tokio-openssl",
"toml",
"winapi",
]
[[package]]

@ -3,6 +3,8 @@ name = "skyd"
version = "0.5.1"
authors = ["Sayan Nandan <ohsayan@outlook.com>"]
edition = "2018"
build = "build.rs"
[dependencies]
tokio = { version = "1.5.0", features = ["full"] }
bytes = "1.0.1"
@ -22,12 +24,16 @@ regex = "1.4.5"
sky_macros = {path="../sky-macros"}
tokio-openssl = "0.6.1"
openssl = { version = "0.10.33", features = ["vendored"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
jemallocator = "0.3.2"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = {version="0.3.9", features=["fileapi"]}
[target.'cfg(unix)'.build-dependencies]
cc = "1.0.67"
[dev-dependencies]
tokio = { version = "1.5.0", features = ["test-util"] }

@ -0,0 +1,9 @@
fn main() {
#[cfg(unix)]
{
println!("cargo:rerun-if-changed=native/flock-posix.c");
cc::Build::new()
.file("native/flock-posix.c")
.compile("libflock-posix.a");
}
}

@ -25,46 +25,36 @@
*/
#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
/* Lock a file */
int lock_file(int descriptor) {
/* Acquire an exclusive lock for a file with the given descriptor */
int lock_exclusive(int descriptor) {
if (descriptor < 0) {
return EBADF;
}
struct flock fl;
/* Lock the whole file - not just a part of it! */
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
if (fcntl(descriptor, F_SETLKW, &fl) == -1) {
if (flock(descriptor, LOCK_EX) == -1) {
return errno;
}
return 0;
}
/* Unlock a file */
int unlock_file(int descriptor) {
struct flock fl;
int try_lock_exclusive(int descriptor) {
if (descriptor < 0) {
return EBADF;
}
/* Unlock the whole file - not just a part of it! */
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
if (fcntl(descriptor, F_SETLKW, &fl) == -1) {
if (flock(descriptor, LOCK_EX | LOCK_NB) == -1) {
return errno;
}
return 0;
}
/* Unlock a file with the given descriptor */
int unlock(int descriptor) {
if (descriptor < 0) {
return EBADF;
}
if (flock(descriptor, LOCK_UN) == -1) {
return errno;
}
return 0;
}

@ -0,0 +1,174 @@
/*
* Created on Fri Apr 16 2021
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
//! # File Locking
//!
//! This module provides the `FileLock` struct that can be used for locking and/or unlocking files on
//! unix-based systems and Windows systems
// TODO(@ohsayan): Add support for solaris
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Result;
use std::io::Write;
#[derive(Debug)]
/// # File Lock
/// A file lock object holds a `std::fs::File` that is used to `lock()` and `unlock()` a file with a given
/// `filename` passed into the `lock()` method. The file lock is configured to drop the file lock when the
/// object is dropped. The `file` field is essentially used to get the raw file descriptor for passing to
/// the C function `lock_file` or `unlock_file` provided by the `native/fscposix.c` file (or `libflock-posix.a`)
///
/// **Note:** You need to lock a file first using this object before unlocking it!
///
/// ## Suggestions
///
/// It is always a good idea to attempt a lock release (unlock) explicitly than letting the `Drop` implementation
/// run it for you as that may cause some Wild West panic if the lock release fails (haha!)
///
pub struct FileLock {
file: File,
}
impl FileLock {
pub fn lock(filename: &str) -> Result<Self> {
let file = OpenOptions::new()
.create(true)
.read(false)
.write(true)
.open(filename)?;
Self::_lock(&file)?;
Ok(Self { file })
}
fn _lock(file: &File) -> Result<()> {
__sys::try_lock_ex(file)
}
pub fn unlock(&self) -> Result<()> {
__sys::unlock_file(&self.file)
}
pub fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.file.write_all(bytes)
}
}
impl Drop for FileLock {
fn drop(&mut self) {
if self.unlock().is_err() {
// This is wild; uh, oh
panic!("Failed to unlock file when dropping value");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_file_lock() {
let mut file = FileLock::lock("data.bin").unwrap();
file.write(&[1, 2, 3]).unwrap();
file.unlock().unwrap();
}
}
#[cfg(windows)]
mod __sys {
use std::io::{Error, Result};
use std::mem;
use std::os::windows::io::AsRawHandle;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::um::fileapi::{LockFileEx, UnlockFile};
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
pub fn lock_ex(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
pub fn try_lock_ex(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
fn lock_file(file: &File, flags: DWORD) -> Result<()> {
unsafe {
let mut overlapped = mem::zeroed();
let ret = LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
pub fn unlock_file(file: &File) -> Result<()> {
let ret = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
#[cfg(unix)]
mod __sys {
use libc::c_int;
use std::fs::File;
use std::io::Error;
use std::io::Result;
use std::os::unix::io::AsRawFd;
extern "C" {
fn lock_exclusive(fd: i32) -> c_int;
fn try_lock_exclusive(fd: i32) -> c_int;
fn unlock(fd: i32) -> c_int;
}
pub fn lock_ex(file: &File) -> Result<()> {
let errno = unsafe {
// UNSAFE(@ohsayan): This is completely fine to do as we've already written the function
// ourselves and are very much aware that it is safe
lock_exclusive(file.as_raw_fd())
};
match errno {
0 => Ok(()),
x @ _ => Err(Error::from_raw_os_error(x)),
}
}
pub fn try_lock_ex(file: &File) -> Result<()> {
let errno = unsafe {
// UNSAFE(@ohsayan): Again, we've written the function ourselves and know what is going on!
try_lock_exclusive(file.as_raw_fd())
};
match errno {
0 => Ok(()),
x @ _ => Err(Error::from_raw_os_error(x)),
}
}
pub fn unlock_file(file: &File) -> Result<()> {
let errno = unsafe { unlock(file.as_raw_fd()) };
match errno {
0 => Ok(()),
x @ _ => Err(Error::from_raw_os_error(x)),
}
}
}

@ -39,7 +39,7 @@ use std::path::PathBuf;
use std::time::Duration;
use tokio::time;
pub mod snapshot;
pub mod recordlock;
pub mod flock;
mod snapstore;
/// This type alias is to be used when deserializing binary data from disk

@ -1,77 +0,0 @@
/*
* Created on Fri Apr 16 2021
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
//! # POSIX Advisory locking
//!
//! This module provides the `FileLock` struct that can be used for locking and/or unlocking files on
//! POSIX-compliant systems
use std::fs::File;
#[derive(Debug)]
/// # File Lock
/// A file lock object holds a `std::fs::File` that is used to `lock()` and `unlock()` a file with a given
/// `filename` passed into the `lock()` method. The file lock is configured to drop the file lock when the
/// object is dropped. The `file` field is essentially used to get the raw file descriptor for passing to
/// the C function `lock_file` or `unlock_file` provided by the `native/fscposix.c` file (or `libflock-posix.a`)
///
/// **Note:** You need to lock a file first using this object before unlocking it!
///
/// ## Suggestions
///
/// It is always a good idea to attempt a lock release (unlock) explicitly than letting the `Drop` implementation
/// run it for you as that may cause some Wild West panic if the lock release fails (haha!)
///
pub struct FileLock {
file: File,
}
#[cfg(unix)]
mod __sys {
use libc;
use std::fs::File;
use std::io::Error;
use std::io::Result;
use std::os::unix::io::AsRawFd;
// TODO(@ohsayan): Support SOLARIS
#[cfg(not(target_os = "solaris"))]
fn flock(file: &File, flag: libc::c_int) -> Result<()> {
let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
if ret < 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
fn lock_exclusive(file: &File) -> Result<()> {
flock(file, libc::LOCK_EX)
}
fn try_lock_exclusive(file: &File) -> Result<()> {
flock(file, libc::LOCK_EX | libc::LOCK_NB)
}
}
Loading…
Cancel
Save