From a9710587bb710a51be394f679a18728edd180bc9 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Fri, 16 Apr 2021 15:41:02 +0000 Subject: [PATCH] Add advisory locking for POSIX systems This commit adds a basic implementation of POSIX advisory record locking which sets a lock on the `data.bin` file when the database server starts and releases the lock when it terminates. This is just done for compliance to let other processes know that we don't want them to use the file. However, the result depends entirely on the process that wants to do 'something' with the file. It is the responsibility of the process to ensure that it respects the file lock. Also, exclusive locks aren't perfect on Linux, so we can't rely on them. See discussion #123 for more information. --- Cargo.lock | 10 +++-- server/Cargo.toml | 10 +++-- server/build.rs | 6 +++ server/src/dbnet.rs | 17 ++++++++ server/src/diskstore/mod.rs | 2 + server/src/diskstore/recordlock.rs | 69 ++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 server/build.rs create mode 100644 server/src/diskstore/recordlock.rs diff --git a/Cargo.lock b/Cargo.lock index f1156f65..0eb4888c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "cc" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "cfg-if" @@ -299,9 +299,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.86" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "libsky" @@ -686,12 +686,14 @@ version = "0.5.1" dependencies = [ "bincode", "bytes", + "cc", "chrono", "clap", "env_logger", "futures", "jemallocator", "lazy_static", + "libc", "libsky", "log", "openssl", diff --git a/server/Cargo.toml b/server/Cargo.toml index 61c4c612..ed1193e2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -3,9 +3,7 @@ name = "skyd" version = "0.5.1" authors = ["Sayan Nandan "] edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - +build = "build.rs" [dependencies] tokio = { version = "1.5.0", features = ["full"] } bytes = "1.0.1" @@ -30,3 +28,9 @@ jemallocator = "0.3.2" [dev-dependencies] tokio = { version = "1.5.0", features = ["test-util"] } + +[target.'cfg(unix)'.build-dependencies] +cc = "1.0.67" + +[target.'cfg(unix)'.dependencies] +libc = "0.2.93" \ No newline at end of file diff --git a/server/build.rs b/server/build.rs new file mode 100644 index 00000000..5605073a --- /dev/null +++ b/server/build.rs @@ -0,0 +1,6 @@ +use cc; +fn main() { + cc::Build::new() + .file("native/fscposix.c") + .compile("libflock.a") +} \ No newline at end of file diff --git a/server/src/dbnet.rs b/server/src/dbnet.rs index da4de582..1cf6c4c7 100644 --- a/server/src/dbnet.rs +++ b/server/src/dbnet.rs @@ -63,6 +63,9 @@ use tokio::net::TcpStream; use tokio::sync::Semaphore; use tokio::sync::{broadcast, mpsc}; use tokio::time::{self, Duration}; +#[cfg(unix)] +use crate::diskstore::recordlock; + /// Responsible for gracefully shutting down the server instead of dying randomly // Sounds very sci-fi ;) pub struct Terminator { @@ -469,6 +472,15 @@ pub async fn run( process::exit(0x100); } }; + + #[cfg(unix)] + let lock = match recordlock::FileLock::lock("data.bin") { + Ok(lock) => lock, + Err(e) => { + log::error!("Failed to acquire lock on data file with error: '{}'", e); + process::exit(0x100); + } + }; match fs::create_dir_all(&*DIR_REMOTE_SNAPSHOT) { Ok(_) => (), Err(e) => match e.kind() { @@ -549,6 +561,11 @@ pub async fn run( } } } + #[cfg(unix)] + if let Err(e) = lock.unlock() { + log::error!("Failed to release lock on data file with error: '{}'", e); + process::exit(0x100); + } terminal::write_info("Goodbye :)\n").unwrap(); } diff --git a/server/src/diskstore/mod.rs b/server/src/diskstore/mod.rs index 555d1327..810a0465 100644 --- a/server/src/diskstore/mod.rs +++ b/server/src/diskstore/mod.rs @@ -39,6 +39,8 @@ use std::path::PathBuf; use std::time::Duration; use tokio::time; pub mod snapshot; +#[cfg(unix)] +pub mod recordlock; mod snapstore; /// This type alias is to be used when deserializing binary data from disk diff --git a/server/src/diskstore/recordlock.rs b/server/src/diskstore/recordlock.rs new file mode 100644 index 00000000..7396d8ba --- /dev/null +++ b/server/src/diskstore/recordlock.rs @@ -0,0 +1,69 @@ +/* + * 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 + * + * 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 . + * +*/ + +use libc::c_int; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::Error; +use std::os::unix::io::AsRawFd; + +extern "C" { + fn lock_file(fd: i32) -> c_int; + fn unlock_file(fd: i32) -> c_int; +} + +#[derive(Debug)] +pub struct FileLock { + file: File, +} + +impl FileLock { + pub fn lock(filename: &str) -> Result { + let file = OpenOptions::new() + .read(false) + .write(true) + .create(true) + .open(&filename)?; + let raw_err = unsafe { lock_file(file.as_raw_fd()) }; + match raw_err { + 0 => Ok(FileLock { file }), + x @ _ => Err(Error::from_raw_os_error(x)), + } + } + pub fn unlock(&self) -> Result<(), Error> { + let raw_err = unsafe { unlock_file(self.file.as_raw_fd()) }; + match raw_err { + 0 => Ok(()), + x @ _ => Err(Error::from_raw_os_error(x)), + } + } +} + +impl Drop for FileLock { + fn drop(&mut self) { + assert!(self.unlock().is_ok()); + } +}