use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use uuid::Uuid;
use block::BlockSize;
use nvm::NonVolatileMemory;
use storage::{
MAGIC_NUMBER, MAJOR_VERSION, MAX_DATA_REGION_SIZE, MAX_JOURNAL_REGION_SIZE, MINOR_VERSION,
};
use {ErrorKind, Result};
const HEADER_SIZE: u16 =
2 +
2 +
2 +
16 +
8 +
8 ;
pub(crate) const FULL_HEADER_SIZE: u16 = 4 + 2 + HEADER_SIZE;
#[derive(Debug, Clone)]
pub struct StorageHeader {
pub major_version: u16,
pub minor_version: u16,
pub block_size: BlockSize,
pub instance_uuid: Uuid,
pub journal_region_size: u64,
pub data_region_size: u64,
}
impl StorageHeader {
pub fn storage_size(&self) -> u64 {
self.region_size() + self.journal_region_size + self.data_region_size
}
pub fn region_size(&self) -> u64 {
Self::calc_region_size(self.block_size)
}
pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = track_io!(File::open(path))?;
track!(Self::read_from(file))
}
pub fn read_from<R: Read>(mut reader: R) -> Result<Self> {
let mut magic_number = [0; 4];
track_io!(reader.read_exact(&mut magic_number))?;
track_assert_eq!(magic_number, MAGIC_NUMBER, ErrorKind::InvalidInput);
let header_size = track_io!(reader.read_u16::<BigEndian>())?;
let mut reader = reader.take(u64::from(header_size));
let major_version = track_io!(reader.read_u16::<BigEndian>())?;
let minor_version = track_io!(reader.read_u16::<BigEndian>())?;
track_assert_eq!(
major_version,
MAJOR_VERSION,
ErrorKind::InvalidInput,
"Unsupported major version",
);
track_assert!(
minor_version <= MINOR_VERSION,
ErrorKind::InvalidInput,
"Unsupported minor version: actual={}, supported={}",
minor_version,
MINOR_VERSION
);
let block_size = track_io!(reader.read_u16::<BigEndian>())?;
let block_size = track!(BlockSize::new(block_size), "block_size:{}", block_size)?;
let mut instance_uuid = [0; 16];
track_io!(reader.read_exact(&mut instance_uuid))?;
let instance_uuid = Uuid::from_bytes(instance_uuid);
let journal_region_size = track_io!(reader.read_u64::<BigEndian>())?;
let data_region_size = track_io!(reader.read_u64::<BigEndian>())?;
track_assert!(
journal_region_size <= MAX_JOURNAL_REGION_SIZE,
ErrorKind::InvalidInput,
"journal_region_size:{}",
journal_region_size
);
track_assert!(
data_region_size <= MAX_DATA_REGION_SIZE,
ErrorKind::InvalidInput,
"data_region_size:{}",
data_region_size
);
track_assert_eq!(reader.limit(), 0, ErrorKind::InvalidInput);
Ok(StorageHeader {
major_version,
minor_version,
instance_uuid,
block_size,
journal_region_size,
data_region_size,
})
}
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
track_io!(writer.write_all(&MAGIC_NUMBER[..]))?;
track_io!(writer.write_u16::<BigEndian>(HEADER_SIZE))?;
track_io!(writer.write_u16::<BigEndian>(self.major_version))?;
track_io!(writer.write_u16::<BigEndian>(self.minor_version))?;
track_io!(writer.write_u16::<BigEndian>(self.block_size.as_u16()))?;
track_io!(writer.write_all(self.instance_uuid.as_bytes()))?;
track_io!(writer.write_u64::<BigEndian>(self.journal_region_size))?;
track_io!(writer.write_u64::<BigEndian>(self.data_region_size))?;
Ok(())
}
pub(crate) fn write_header_region_to<W: Write>(&self, mut writer: W) -> Result<()> {
track!(self.write_to(&mut writer))?;
let padding = vec![0; self.region_size() as usize - FULL_HEADER_SIZE as usize];
track_io!(writer.write_all(&padding))?;
Ok(())
}
pub(crate) fn calc_region_size(block_size: BlockSize) -> u64 {
block_size.ceil_align(u64::from(FULL_HEADER_SIZE))
}
pub(crate) fn split_regions<N: NonVolatileMemory>(&self, nvm: N) -> Result<(N, N)> {
let header_tail = self.region_size();
let (_, body_nvm) = track!(nvm.split(header_tail))?;
let (journal_nvm, data_nvm) = track!(body_nvm.split(self.journal_region_size))?;
Ok((journal_nvm, data_nvm))
}
}
#[cfg(test)]
mod tests {
use trackable::result::TestResult;
use uuid::Uuid;
use super::*;
use block::BlockSize;
#[test]
fn it_works() -> TestResult {
let header = StorageHeader {
major_version: MAJOR_VERSION,
minor_version: MINOR_VERSION,
block_size: BlockSize::min(),
instance_uuid: Uuid::new_v4(),
journal_region_size: 1024,
data_region_size: 4096,
};
assert_eq!(header.region_size(), u64::from(BlockSize::MIN));
assert_eq!(
header.storage_size(),
u64::from(BlockSize::MIN) + 1024 + 4096
);
let mut buf = Vec::new();
track!(header.write_to(&mut buf))?;
let h = track!(StorageHeader::read_from(&buf[..]))?;
assert_eq!(h.major_version, header.major_version);
assert_eq!(h.minor_version, header.minor_version);
assert_eq!(h.block_size, header.block_size);
assert_eq!(h.instance_uuid, header.instance_uuid);
assert_eq!(h.journal_region_size, header.journal_region_size);
assert_eq!(h.data_region_size, header.data_region_size);
Ok(())
}
#[test]
fn compatibility_check_works() -> TestResult {
let h = header(MAJOR_VERSION, MINOR_VERSION - 1);
let mut buf = Vec::new();
track!(h.write_to(&mut buf))?;
let h = track!(StorageHeader::read_from(&buf[..]))?;
assert_eq!(h.major_version, MAJOR_VERSION);
assert_eq!(h.minor_version, MINOR_VERSION - 1);
let h = header(MAJOR_VERSION, MINOR_VERSION);
let mut buf = Vec::new();
track!(h.write_to(&mut buf))?;
let h = track!(StorageHeader::read_from(&buf[..]))?;
assert_eq!(h.major_version, MAJOR_VERSION);
assert_eq!(h.minor_version, MINOR_VERSION);
let h = header(MAJOR_VERSION, MINOR_VERSION + 1);
let mut buf = Vec::new();
track!(h.write_to(&mut buf))?;
assert!(StorageHeader::read_from(&buf[..]).is_err());
let h = header(MAJOR_VERSION + 1, MINOR_VERSION);
let mut buf = Vec::new();
track!(h.write_to(&mut buf))?;
let h = header(MAJOR_VERSION - 1, MINOR_VERSION);
let mut buf = Vec::new();
track!(h.write_to(&mut buf))?;
assert!(StorageHeader::read_from(&buf[..]).is_err());
Ok(())
}
fn header(major_version: u16, minor_version: u16) -> StorageHeader {
StorageHeader {
major_version,
minor_version,
block_size: BlockSize::min(),
instance_uuid: Uuid::new_v4(),
journal_region_size: 1024,
data_region_size: 4096,
}
}
}