use std::io::Read;
use std::path::Path;
use std::sync::Arc;
use crate::blob::{parse_blob_header_with_index, BlobKind, MAX_BLOB_HEADER_SIZE};
use crate::blob_meta::BlobIndex;
use crate::error::Result;
const HEADER_PROBE_SIZE: usize = 4096;
pub(crate) struct BlobHeaderMeta {
pub blob_type: BlobKind,
pub frame_start: u64,
pub data_offset: u64,
pub data_size: usize,
pub index: Option<BlobIndex>,
pub tagdata: Option<Box<[u8]>>,
pub frame_size: usize,
}
pub(crate) struct HeaderWalker {
file: Arc<std::fs::File>,
offset: u64,
file_size: u64,
header_buf: Vec<u8>,
}
impl HeaderWalker {
pub(crate) fn open(path: &Path) -> Result<Self> {
let file = std::fs::File::open(path).map_err(|e| {
crate::error::new_error(crate::error::ErrorKind::Io(std::io::Error::other(
format!("failed to open {}: {e}", path.display()),
)))
})?;
let file_size = file
.metadata()
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))?
.len();
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
unsafe {
libc::posix_fadvise(file.as_raw_fd(), 0, 0, libc::POSIX_FADV_RANDOM);
}
}
Ok(Self {
file: Arc::new(file),
offset: 0,
file_size,
header_buf: Vec::new(),
})
}
pub(crate) fn shared_file(&self) -> &Arc<std::fs::File> {
&self.file
}
pub(crate) fn file_size(&self) -> u64 {
self.file_size
}
pub(crate) fn next_header(&mut self) -> Result<Option<BlobHeaderMeta>> {
use std::os::unix::fs::FileExt as _;
if self.offset >= self.file_size {
return Ok(None);
}
let frame_start = self.offset;
let remaining = self.file_size - self.offset;
let probe_len = usize::try_from(remaining)
.unwrap_or(usize::MAX)
.min(HEADER_PROBE_SIZE);
if probe_len < 4 {
return Ok(None);
}
self.header_buf.resize(probe_len, 0);
self.file
.read_exact_at(&mut self.header_buf, self.offset)
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))?;
let header_len = u32::from_be_bytes([
self.header_buf[0],
self.header_buf[1],
self.header_buf[2],
self.header_buf[3],
]) as usize;
if header_len as u64 >= MAX_BLOB_HEADER_SIZE {
return Err(crate::error::new_blob_error(
crate::error::BlobError::HeaderTooBig { size: header_len as u64 },
));
}
let header_end = 4 + header_len;
if header_end > probe_len {
self.header_buf.resize(header_end, 0);
let tail_offset = self.offset + probe_len as u64;
self.file
.read_exact_at(&mut self.header_buf[probe_len..header_end], tail_offset)
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))?;
}
let (blob_type, data_size, raw_index, tagdata) =
parse_blob_header_with_index(&self.header_buf[4..header_end])?;
let index = raw_index
.as_ref()
.and_then(|b| BlobIndex::deserialize(b));
let data_offset = self.offset + header_end as u64;
let payload_end = data_offset
.checked_add(data_size as u64)
.ok_or_else(|| {
crate::error::new_error(crate::error::ErrorKind::Io(
::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
format!(
"blob at offset {} declares overflowing payload size {data_size}",
self.offset
),
),
))
})?;
if payload_end > self.file_size {
return Err(crate::error::new_error(crate::error::ErrorKind::Io(
::std::io::Error::new(
::std::io::ErrorKind::UnexpectedEof,
format!(
"blob payload truncated: declared {data_size} bytes \
from offset {data_offset}, file_size {}",
self.file_size
),
),
)));
}
self.offset = payload_end;
let frame_size = 4 + header_len + data_size;
Ok(Some(BlobHeaderMeta {
blob_type,
frame_start,
data_offset,
data_size,
index,
tagdata,
frame_size,
}))
}
pub(crate) fn pread_data(&self, offset: u64, size: usize, buf: &mut Vec<u8>) -> Result<()> {
use std::os::unix::fs::FileExt as _;
buf.resize(size, 0);
self.file
.read_exact_at(buf, offset)
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))
}
}
#[allow(dead_code)]
pub(crate) fn pread_exact(
file: &std::fs::File,
offset: u64,
size: usize,
buf: &mut Vec<u8>,
) -> Result<()> {
use std::os::unix::fs::FileExt as _;
buf.resize(size, 0);
file.read_exact_at(buf, offset)
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))
}
#[allow(dead_code)]
pub(crate) fn read_blob_data<R: Read>(reader: &mut R, size: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; size];
reader
.read_exact(&mut buf)
.map_err(|e| crate::error::new_error(crate::error::ErrorKind::Io(e)))?;
Ok(buf)
}