use std::fmt;
use std::io::Read;
use std::{sync::Arc, time::Duration};
use fuse_backend_rs::file_buf::FileVolatileSlice;
use nydus_utils::{
metrics::{BackendMetrics, ERROR_HOLDER},
DelayType, Delayer,
};
use crate::utils::{alloc_buf, copyv};
use crate::StorageError;
#[cfg(any(
feature = "backend-oss",
feature = "backend-registry",
feature = "backend-s3",
feature = "backend-http-proxy",
))]
pub mod connection;
#[cfg(feature = "backend-http-proxy")]
pub mod http_proxy;
#[cfg(feature = "backend-localdisk")]
pub mod localdisk;
#[cfg(feature = "backend-localfs")]
pub mod localfs;
#[cfg(any(feature = "backend-oss", feature = "backend-s3"))]
pub mod object_storage;
#[cfg(feature = "backend-oss")]
pub mod oss;
#[cfg(feature = "backend-registry")]
pub mod registry;
#[cfg(feature = "backend-s3")]
pub mod s3;
#[derive(Debug)]
pub enum BackendError {
Unsupported(String),
CopyData(StorageError),
#[cfg(feature = "backend-localdisk")]
LocalDisk(self::localdisk::LocalDiskError),
#[cfg(feature = "backend-registry")]
Registry(self::registry::RegistryError),
#[cfg(feature = "backend-localfs")]
LocalFs(self::localfs::LocalFsError),
#[cfg(any(feature = "backend-oss", feature = "backend-s3"))]
ObjectStorage(self::object_storage::ObjectStorageError),
#[cfg(feature = "backend-http-proxy")]
HttpProxy(self::http_proxy::HttpProxyError),
}
impl fmt::Display for BackendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BackendError::Unsupported(s) => write!(f, "{}", s),
BackendError::CopyData(e) => write!(f, "failed to copy data, {}", e),
#[cfg(feature = "backend-registry")]
BackendError::Registry(e) => write!(f, "{:?}", e),
#[cfg(feature = "backend-localfs")]
BackendError::LocalFs(e) => write!(f, "{}", e),
#[cfg(any(feature = "backend-oss", feature = "backend-s3"))]
BackendError::ObjectStorage(e) => write!(f, "{}", e),
#[cfg(feature = "backend-localdisk")]
BackendError::LocalDisk(e) => write!(f, "{:?}", e),
#[cfg(feature = "backend-http-proxy")]
BackendError::HttpProxy(e) => write!(f, "{}", e),
}
}
}
pub type BackendResult<T> = std::result::Result<T, BackendError>;
pub trait BlobReader: Send + Sync {
fn blob_size(&self) -> BackendResult<u64>;
fn try_read(&self, buf: &mut [u8], offset: u64) -> BackendResult<usize>;
fn read(&self, buf: &mut [u8], offset: u64) -> BackendResult<usize> {
let mut retry_count = self.retry_limit();
let begin_time = self.metrics().begin();
let mut delayer = Delayer::new(DelayType::BackOff, Duration::from_millis(500));
loop {
match self.try_read(buf, offset) {
Ok(size) => {
self.metrics().end(&begin_time, buf.len(), false);
return Ok(size);
}
Err(err) => {
if retry_count > 0 {
warn!(
"Read from backend failed: {:?}, retry count {}",
err, retry_count
);
retry_count -= 1;
delayer.delay();
} else {
self.metrics().end(&begin_time, buf.len(), true);
ERROR_HOLDER
.lock()
.unwrap()
.push(&format!("{:?}", err))
.unwrap_or_else(|_| error!("Failed when try to hold error"));
return Err(err);
}
}
}
}
}
fn read_all(&self, buf: &mut [u8], offset: u64) -> BackendResult<usize> {
let mut off = 0usize;
let mut left = buf.len();
while left > 0 {
let cnt = self.read(&mut buf[off..], offset + off as u64)?;
if cnt == 0 {
break;
}
off += cnt;
left -= cnt;
}
Ok(off as usize)
}
fn readv(
&self,
bufs: &[FileVolatileSlice],
offset: u64,
max_size: usize,
) -> BackendResult<usize> {
if bufs.len() == 1 && max_size >= bufs[0].len() {
let buf = unsafe { std::slice::from_raw_parts_mut(bufs[0].as_ptr(), bufs[0].len()) };
self.read(buf, offset)
} else {
let size = bufs.iter().fold(0usize, move |size, s| size + s.len());
let size = std::cmp::min(size, max_size);
let mut data = alloc_buf(size);
let result = self.read(&mut data, offset)?;
copyv(&[&data], bufs, 0, result, 0, 0)
.map(|r| r.0)
.map_err(BackendError::CopyData)
}
}
fn metrics(&self) -> &BackendMetrics;
fn retry_limit(&self) -> u8 {
0
}
}
pub trait BlobBackend: Send + Sync {
fn shutdown(&self);
fn metrics(&self) -> &BackendMetrics;
fn get_reader(&self, blob_id: &str) -> BackendResult<Arc<dyn BlobReader>>;
}
pub struct BlobBufReader {
buf: Vec<u8>,
pos: usize,
len: usize,
start: u64,
size: u64,
reader: Arc<dyn BlobReader>,
}
impl BlobBufReader {
pub fn new(buf_size: usize, reader: Arc<dyn BlobReader>, start: u64, size: u64) -> Self {
Self {
buf: alloc_buf(buf_size),
pos: 0,
len: 0,
start,
size,
reader,
}
}
}
impl Read for BlobBufReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let mut sz = self.len;
if sz == 0 && self.size == 0 {
return Ok(0);
}
if sz == 0 && self.size > 0 {
let cnt = std::cmp::min(self.buf.len() as u64, self.size) as usize;
let ret = self
.reader
.read(&mut self.buf[..cnt], self.start)
.map_err(|e| eio!(format!("failed to read data from backend, {:?}", e)))?;
self.start += ret as u64;
self.size -= ret as u64;
self.pos = 0;
self.len = ret;
sz = ret;
}
if self.size != 0 && sz == 0 {
return Err(eio!("unexpected EOF when reading data from backend"));
}
let sz = std::cmp::min(sz, buf.len());
buf[..sz].copy_from_slice(&self.buf[self.pos..self.pos + sz]);
self.pos += sz;
self.len -= sz;
Ok(sz)
}
}