use std::fs::{File, Metadata};
use std::io;
use std::path::Path;
use tempfile::NamedTempFile;
#[derive(Debug)]
pub enum PositionedReadError {
Io(std::io::Error),
ShortRead {
bytes_read: usize,
},
}
impl std::fmt::Display for PositionedReadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(error) => write!(f, "positioned read failed: {error}"),
Self::ShortRead { bytes_read } => {
write!(f, "short read: only {bytes_read} byte(s) read before EOF")
}
}
}
}
impl std::error::Error for PositionedReadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::ShortRead { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileKind {
File,
Dir,
Symlink,
Other,
}
#[derive(Clone, Copy, Debug)]
pub struct FileStat {
pub len: u64,
pub kind: FileKind,
}
#[derive(Clone, Debug)]
pub struct DirEntryInfo {
pub name: std::ffi::OsString,
pub kind: FileKind,
}
pub(crate) fn file_kind_of(file_type: std::fs::FileType) -> FileKind {
if file_type.is_symlink() {
FileKind::Symlink
} else if file_type.is_dir() {
FileKind::Dir
} else if file_type.is_file() {
FileKind::File
} else {
FileKind::Other
}
}
pub(crate) fn file_stat_of(meta: &Metadata) -> FileStat {
FileStat {
len: meta.len(),
kind: file_kind_of(meta.file_type()),
}
}
pub trait StoreFile: Send + Sync {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()>;
fn sync_data(&mut self) -> io::Result<()>;
fn sync_all(&mut self) -> io::Result<()>;
fn len(&self) -> io::Result<u64>;
fn is_empty(&self) -> io::Result<bool> {
Ok(self.len()? == 0)
}
fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> io::Result<usize>;
fn read_exact_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<(), PositionedReadError> {
let mut total_read = 0;
while total_read < buf.len() {
let n = self
.read_at(
offset.saturating_add(u64::try_from(total_read).unwrap_or(u64::MAX)),
&mut buf[total_read..],
)
.map_err(PositionedReadError::Io)?;
if n == 0 {
return Err(PositionedReadError::ShortRead {
bytes_read: total_read,
});
}
total_read = total_read.saturating_add(n);
}
Ok(())
}
fn as_std_file(&self) -> Option<&File>;
}
pub trait StagedFile: Send + Sync {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()>;
fn sync_all(&mut self) -> io::Result<()>;
fn persist(
self: Box<Self>,
final_path: &Path,
admission: super::sync::ParentDirSyncAdmission,
) -> io::Result<()>;
}
pub trait StoreDirLockGuard: Send + Sync {}
pub(crate) struct StoreFileWriter<'a>(pub(crate) &'a mut dyn StoreFile);
impl io::Write for StoreFileWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) struct StoreFileAppendWriter<'a> {
handle: &'a mut dyn StoreFile,
pos: u64,
}
impl<'a> StoreFileAppendWriter<'a> {
pub(crate) fn new(handle: &'a mut dyn StoreFile, start_pos: u64) -> Self {
Self {
handle,
pos: start_pos,
}
}
}
impl io::Write for StoreFileAppendWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.handle.write_all(buf)?;
self.pos = self
.pos
.saturating_add(u64::try_from(buf.len()).unwrap_or(u64::MAX));
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for StoreFileAppendWriter<'_> {
fn seek(&mut self, seek_from: io::SeekFrom) -> io::Result<u64> {
match seek_from {
io::SeekFrom::Current(0) => Ok(self.pos),
io::SeekFrom::Start(_) | io::SeekFrom::End(_) | io::SeekFrom::Current(_) => {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"append-only store handle: only stream_position is supported",
))
}
}
}
}
pub(crate) struct StoreFileCursor {
handle: Box<dyn StoreFile>,
pos: u64,
}
impl StoreFileCursor {
pub(crate) fn new(handle: Box<dyn StoreFile>) -> Self {
Self { handle, pos: 0 }
}
pub(crate) fn get_ref(&self) -> &dyn StoreFile {
self.handle.as_ref()
}
}
impl io::Read for StoreFileCursor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.handle.read_at(self.pos, buf)?;
self.pos = self
.pos
.saturating_add(u64::try_from(n).unwrap_or(u64::MAX));
Ok(n)
}
}
impl io::Seek for StoreFileCursor {
fn seek(&mut self, seek_from: io::SeekFrom) -> io::Result<u64> {
let new_pos = match seek_from {
io::SeekFrom::Start(offset) => Some(offset),
io::SeekFrom::End(delta) => {
let len = self.handle.len()?;
len.checked_add_signed(delta)
}
io::SeekFrom::Current(delta) => self.pos.checked_add_signed(delta),
};
match new_pos {
Some(pos) => {
self.pos = pos;
Ok(pos)
}
None => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek to a negative or overflowing position",
)),
}
}
}
pub(crate) struct RealStoreFile {
file: File,
}
impl RealStoreFile {
pub(crate) fn new(file: File) -> Self {
Self { file }
}
}
impl StoreFile for RealStoreFile {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
io::Write::write_all(&mut self.file, buf)
}
fn sync_data(&mut self) -> io::Result<()> {
self.file.sync_data()
}
fn sync_all(&mut self) -> io::Result<()> {
self.file.sync_all()
}
fn len(&self) -> io::Result<u64> {
Ok(self.file.metadata()?.len())
}
fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
read_at_impl(&mut self.file, offset, buf)
}
fn as_std_file(&self) -> Option<&File> {
Some(&self.file)
}
}
pub(crate) fn read_at_impl(file: &mut File, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
#[cfg(unix)]
{
use std::os::unix::fs::FileExt;
file.read_at(buf, offset)
}
#[cfg(not(unix))]
{
use std::io::{Read, Seek, SeekFrom};
file.seek(SeekFrom::Start(offset))?;
file.read(buf)
}
}
pub(crate) struct RealStagedFile {
tmp: NamedTempFile,
}
impl RealStagedFile {
pub(crate) fn new(tmp: NamedTempFile) -> Self {
Self { tmp }
}
}
impl StagedFile for RealStagedFile {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
io::Write::write_all(&mut self.tmp.as_file(), buf)
}
fn sync_all(&mut self) -> io::Result<()> {
self.tmp.as_file().sync_all()
}
fn persist(
self: Box<Self>,
final_path: &Path,
admission: super::sync::ParentDirSyncAdmission,
) -> io::Result<()> {
crate::store::platform::sync::persist_temp_with_parent_sync(self.tmp, final_path, admission)
}
}
pub(crate) struct RealDirLockGuard {
_file: File,
}
impl RealDirLockGuard {
pub(crate) fn new(file: File) -> Self {
Self { _file: file }
}
}
impl StoreDirLockGuard for RealDirLockGuard {}