solana_accounts_db/tiered_storage/
file.rsuse {
super::{error::TieredStorageError, TieredStorageResult},
bytemuck::{AnyBitPattern, NoUninit, Zeroable},
std::{
fs::{File, OpenOptions},
io::{BufWriter, Read, Result as IoResult, Seek, SeekFrom, Write},
mem,
path::Path,
ptr,
},
};
pub const FILE_MAGIC_NUMBER: u64 = u64::from_le_bytes(*b"AnzaTech");
#[derive(Debug, PartialEq, Eq, Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)]
#[repr(C)]
pub struct TieredStorageMagicNumber(pub u64);
const _: () = assert!(std::mem::size_of::<TieredStorageMagicNumber>() == 8);
impl Default for TieredStorageMagicNumber {
fn default() -> Self {
Self(FILE_MAGIC_NUMBER)
}
}
#[derive(Debug)]
pub struct TieredReadableFile(pub File);
impl TieredReadableFile {
pub fn new(file_path: impl AsRef<Path>) -> TieredStorageResult<Self> {
let file = Self(
OpenOptions::new()
.read(true)
.create(false)
.open(&file_path)?,
);
file.check_magic_number()?;
Ok(file)
}
pub fn new_writable(file_path: impl AsRef<Path>) -> IoResult<Self> {
Ok(Self(
OpenOptions::new()
.create_new(true)
.write(true)
.open(file_path)?,
))
}
fn check_magic_number(&self) -> TieredStorageResult<()> {
self.seek_from_end(-(std::mem::size_of::<TieredStorageMagicNumber>() as i64))?;
let mut magic_number = TieredStorageMagicNumber::zeroed();
self.read_pod(&mut magic_number)?;
if magic_number != TieredStorageMagicNumber::default() {
return Err(TieredStorageError::MagicNumberMismatch(
TieredStorageMagicNumber::default().0,
magic_number.0,
));
}
Ok(())
}
pub fn read_pod<T: NoUninit + AnyBitPattern>(&self, value: &mut T) -> IoResult<()> {
unsafe { self.read_type(value) }
}
pub unsafe fn read_type<T>(&self, value: &mut T) -> IoResult<()> {
let ptr = ptr::from_mut(value).cast();
let bytes = unsafe { std::slice::from_raw_parts_mut(ptr, mem::size_of::<T>()) };
self.read_bytes(bytes)
}
pub fn seek(&self, offset: u64) -> IoResult<u64> {
(&self.0).seek(SeekFrom::Start(offset))
}
pub fn seek_from_end(&self, offset: i64) -> IoResult<u64> {
(&self.0).seek(SeekFrom::End(offset))
}
pub fn read_bytes(&self, buffer: &mut [u8]) -> IoResult<()> {
(&self.0).read_exact(buffer)
}
}
#[derive(Debug)]
pub struct TieredWritableFile(pub BufWriter<File>);
impl TieredWritableFile {
pub fn new(file_path: impl AsRef<Path>) -> IoResult<Self> {
Ok(Self(BufWriter::new(
OpenOptions::new()
.create_new(true)
.write(true)
.open(file_path)?,
)))
}
pub fn write_pod<T: NoUninit>(&mut self, value: &T) -> IoResult<usize> {
unsafe { self.write_type(value) }
}
pub unsafe fn write_type<T>(&mut self, value: &T) -> IoResult<usize> {
let ptr = ptr::from_ref(value).cast();
let bytes = unsafe { std::slice::from_raw_parts(ptr, mem::size_of::<T>()) };
self.write_bytes(bytes)
}
pub fn seek(&mut self, offset: u64) -> IoResult<u64> {
self.0.seek(SeekFrom::Start(offset))
}
pub fn seek_from_end(&mut self, offset: i64) -> IoResult<u64> {
self.0.seek(SeekFrom::End(offset))
}
pub fn write_bytes(&mut self, bytes: &[u8]) -> IoResult<usize> {
self.0.write_all(bytes)?;
Ok(bytes.len())
}
}
impl Drop for TieredWritableFile {
fn drop(&mut self) {
let result = self.0.flush();
if let Err(err) = result {
panic!("failed to flush TieredWritableFile on drop: {err}");
}
}
}
#[cfg(test)]
mod tests {
use {
crate::tiered_storage::{
error::TieredStorageError,
file::{TieredReadableFile, TieredWritableFile, FILE_MAGIC_NUMBER},
},
std::path::Path,
tempfile::TempDir,
};
fn generate_test_file_with_number(path: impl AsRef<Path>, number: u64) {
let mut file = TieredWritableFile::new(path).unwrap();
file.write_pod(&number).unwrap();
}
#[test]
fn test_new() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().join("test_new");
generate_test_file_with_number(&path, FILE_MAGIC_NUMBER);
assert!(TieredReadableFile::new(&path).is_ok());
}
#[test]
fn test_magic_number_mismatch() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().join("test_magic_number_mismatch");
generate_test_file_with_number(&path, !FILE_MAGIC_NUMBER);
assert!(matches!(
TieredReadableFile::new(&path),
Err(TieredStorageError::MagicNumberMismatch(_, _))
));
}
}