Implement `StartStop`

next
Sayan Nandan 1 year ago
parent 2756682729
commit 17b07897e5
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -26,3 +26,49 @@
mod header_impl;
mod rw;
mod start_stop;
use std::io::Error as IoError;
pub type SDSSResult<T> = Result<T, SDSSError>;
pub trait SDSSErrorContext {
type ExtraData;
fn with_extra(self, extra: Self::ExtraData) -> SDSSError;
}
impl SDSSErrorContext for IoError {
type ExtraData = &'static str;
fn with_extra(self, extra: Self::ExtraData) -> SDSSError {
SDSSError::IoErrorExtra(self, extra)
}
}
#[derive(Debug)]
pub enum SDSSError {
IoError(IoError),
IoErrorExtra(IoError, &'static str),
CorruptedFile(&'static str),
StartupError(&'static str),
}
impl SDSSError {
pub const fn corrupted_file(fname: &'static str) -> Self {
Self::CorruptedFile(fname)
}
pub const fn ioerror_extra(error: IoError, extra: &'static str) -> Self {
Self::IoErrorExtra(error, extra)
}
pub fn with_ioerror_extra(self, extra: &'static str) -> Self {
match self {
Self::IoError(ioe) => Self::IoErrorExtra(ioe, extra),
x => x,
}
}
}
impl From<IoError> for SDSSError {
fn from(e: IoError) -> Self {
Self::IoError(e)
}
}

@ -0,0 +1,150 @@
/*
* Created on Mon May 29 2023
*
* 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) 2023, 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/>.
*
*/
use {
super::{SDSSError, SDSSErrorContext, SDSSResult},
crate::util::os,
std::{
fs::File,
io::{ErrorKind, Read, Write},
},
};
#[cfg(not(test))]
const START_FILE: &'static str = ".start";
#[cfg(test)]
const START_FILE: &'static str = ".start_testmode";
#[cfg(not(test))]
const STOP_FILE: &'static str = ".stop";
#[cfg(test)]
const STOP_FILE: &'static str = ".stop_testmode";
const EMSG_FAILED_WRITE_START_FILE: &str =
concat_str_to_str!("failed to write to `", START_FILE, "` file");
const EMSG_FAILED_WRITE_STOP_FILE: &str =
concat_str_to_str!("failed to write to `", STOP_FILE, "` file");
const EMSG_FAILED_OPEN_START_FILE: &str =
concat_str_to_str!("failed to open `", START_FILE, "` file");
const EMSG_FAILED_OPEN_STOP_FILE: &str =
concat_str_to_str!("failed to open `", STOP_FILE, "` file");
const EMSG_FAILED_VERIFY: &str = concat_str_to_str!(
"failed to verify `",
START_FILE,
concat_str_to_str!("` and `", STOP_FILE, "` timestamps")
);
#[derive(Debug)]
pub struct StartStop {
begin: u128,
stop_file: File,
}
#[derive(Debug)]
enum ReadNX {
Created(File),
Read(File, u128),
}
impl ReadNX {
const fn created(&self) -> bool {
matches!(self, Self::Created(_))
}
fn file_mut(&mut self) -> &mut File {
match self {
Self::Created(ref mut f) => f,
Self::Read(ref mut f, _) => f,
}
}
fn into_file(self) -> File {
match self {
Self::Created(f) => f,
Self::Read(f, _) => f,
}
}
}
impl StartStop {
fn read_time_file(f: &str, create_new_if_nx: bool) -> SDSSResult<ReadNX> {
let mut f = match File::options().write(true).read(true).open(f) {
Ok(f) => f,
Err(e) if e.kind() == ErrorKind::NotFound && create_new_if_nx => {
let f = File::create(f)?;
return Ok(ReadNX::Created(f));
}
Err(e) => return Err(e.into()),
};
let len = f.metadata().map(|m| m.len())?;
if len != sizeof!(u128) as u64 {
return Err(SDSSError::corrupted_file(START_FILE));
}
let mut buf = [0u8; sizeof!(u128)];
f.read_exact(&mut buf)?;
Ok(ReadNX::Read(f, u128::from_le_bytes(buf)))
}
pub fn terminate(mut self) -> SDSSResult<()> {
self.stop_file
.write_all(self.begin.to_le_bytes().as_ref())
.map_err(|e| e.with_extra(EMSG_FAILED_WRITE_STOP_FILE))
}
pub fn verify_and_start() -> SDSSResult<Self> {
// read start file
let mut start_file = Self::read_time_file(START_FILE, true)
.map_err(|e| e.with_ioerror_extra(EMSG_FAILED_OPEN_START_FILE))?;
// read stop file
let stop_file = Self::read_time_file(STOP_FILE, start_file.created())
.map_err(|e| e.with_ioerror_extra(EMSG_FAILED_OPEN_STOP_FILE))?;
// read current time
let ctime = os::get_epoch_time();
match (&start_file, &stop_file) {
(ReadNX::Read(_, time_start), ReadNX::Read(_, time_stop))
if time_start == time_stop => {}
(ReadNX::Created(_), ReadNX::Created(_)) => {}
_ => return Err(SDSSError::StartupError(EMSG_FAILED_VERIFY)),
}
start_file
.file_mut()
.write_all(&ctime.to_le_bytes())
.map_err(|e| e.with_extra(EMSG_FAILED_WRITE_START_FILE))?;
Ok(Self {
stop_file: stop_file.into_file(),
begin: ctime,
})
}
}
#[test]
fn verify_test() {
let x = || -> SDSSResult<()> {
let ss = StartStop::verify_and_start()?;
ss.terminate()?;
let ss = StartStop::verify_and_start()?;
ss.terminate()?;
std::fs::remove_file(START_FILE)?;
std::fs::remove_file(STOP_FILE)?;
Ok(())
};
x().unwrap();
}

@ -303,3 +303,50 @@ macro_rules! is_64b {
cfg!(target_pointer_width = "64")
};
}
#[macro_export]
macro_rules! concat_array_to_array {
($a:expr, $b:expr) => {{
const BUFFER_A: [u8; $a.len()] = crate::util::copy_slice_to_array($a);
const BUFFER_B: [u8; $b.len()] = crate::util::copy_slice_to_array($b);
const BUFFER: [u8; BUFFER_A.len() + BUFFER_B.len()] = unsafe {
// UNSAFE(@ohsayan): safe because align = 1
core::mem::transmute((BUFFER_A, BUFFER_B))
};
BUFFER
}};
($a:expr, $b:expr, $c:expr) => {{
const LA: usize = $a.len() + $b.len();
const LB: usize = LA + $c.len();
const S_1: [u8; LA] = concat_array_to_array!($a, $b);
const S_2: [u8; LB] = concat_array_to_array!(&S_1, $c);
S_2
}};
}
#[macro_export]
macro_rules! concat_str_to_array {
($a:expr, $b:expr) => {
concat_array_to_array!($a.as_bytes(), $b.as_bytes())
};
($a:expr, $b:expr, $c:expr) => {{
concat_array_to_array!($a.as_bytes(), $b.as_bytes(), $c.as_bytes())
}};
}
#[macro_export]
macro_rules! concat_str_to_str {
($a:expr, $b:expr) => {{
const BUFFER: [u8; ::core::primitive::str::len($a) + ::core::primitive::str::len($b)] =
concat_str_to_array!($a, $b);
const STATIC_BUFFER: &[u8] = &BUFFER;
unsafe {
// UNSAFE(@ohsayan): all good because of restriction to str
core::str::from_utf8_unchecked(&STATIC_BUFFER)
}
}};
($a:expr, $b:expr, $c:expr) => {{
const A: &str = concat_str_to_str!($a, $b);
concat_str_to_str!(A, $c)
}};
}

@ -384,15 +384,19 @@ mod hostname_impl {
}
#[cfg(target_family = "windows")]
fn get_hostname() -> (usize, [u8; 255]) {
fn get_hostname() -> Hostname {
use winapi::shared::minwindef::DWORD;
use winapi::um::sysinfoapi::GetComputerNameA;
use winapi::um::sysinfoapi::{self, GetComputerNameExA};
let mut buf: [u8; 256] = [0; 256];
let mut size: DWORD = buf.len() as u32;
unsafe {
GetComputerNameA(buf.as_mut_ptr().cast(), &mut size);
GetComputerNameExA(
sysinfoapi::ComputerNamePhysicalDnsHostname,
buf.as_mut_ptr().cast(),
&mut size,
);
Hostname::new_from_raw_buf(&buf)
}
}
@ -401,38 +405,31 @@ mod hostname_impl {
mod test {
use std::process::Command;
#[cfg(target_os = "linux")]
fn test_get_hostname() -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("sh")
.arg("-c")
.arg("hostname")
.output()?;
let hostname = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(hostname)
}
#[cfg(target_os = "windows")]
fn test_get_hostname() -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("cmd")
.args(&["/C", "hostname"])
.output()?;
let hostname = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(hostname)
fn test_get_hostname() -> String {
let x = if cfg!(target_os = "windows") {
// Windows command to get hostname
Command::new("cmd")
.args(&["/C", "hostname"])
.output()
.expect("Failed to execute command")
.stdout
} else {
// Unix command to get hostname
Command::new("uname")
.args(&["-n"])
.output()
.expect("Failed to execute command")
.stdout
};
String::from_utf8_lossy(&x).trim().to_string()
}
#[cfg(target_os = "macos")]
fn test_get_hostname() -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("sh")
.arg("-c")
.arg("scutil --get LocalHostName")
.output()?;
let hostname = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(hostname)
}
#[test]
fn t_get_hostname() {
let hostname_from_cmd = test_get_hostname().unwrap();
assert_eq!(hostname_from_cmd.as_str(), super::Hostname::get().as_str());
assert_eq!(
test_get_hostname().as_str(),
super::Hostname::get().as_str()
);
}
}
}

Loading…
Cancel
Save