use crate::UserKey;
use alloc::string::String;
use alloc::vec::Vec;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum DropReason {
HeaderCorrupted(String),
ChecksumMismatch,
ReadError(String),
DecodeError(String),
}
#[derive(Debug, Clone)]
pub struct DroppedBlock {
pub offset: u64,
pub section: Vec<u8>,
pub reason: DropReason,
pub key_range: Option<(UserKey, UserKey)>,
}
#[derive(Debug)]
pub struct SalvageReport {
pub salvaged_path: Option<PathBuf>,
pub blocks_total: usize,
pub blocks_salvaged: usize,
pub entries_salvaged: u64,
pub dropped: Vec<DroppedBlock>,
}
impl SalvageReport {
#[must_use]
pub fn is_complete(&self) -> bool {
self.dropped.is_empty()
}
}
pub fn salvage_sst(
source: &std::path::Path,
dest: std::path::PathBuf,
fs: &alloc::sync::Arc<dyn crate::fs::Fs>,
) -> crate::Result<SalvageReport> {
salvage_sst_with_comparator(source, dest, fs, &crate::comparator::default_comparator())
}
pub(crate) fn salvage_sst_with_comparator(
source: &std::path::Path,
dest: std::path::PathBuf,
fs: &alloc::sync::Arc<dyn crate::fs::Fs>,
comparator: &crate::comparator::SharedComparator,
) -> crate::Result<SalvageReport> {
use alloc::sync::Arc;
let checksum = crate::Checksum::from_raw(crate::repair::compute_table_checksum(&**fs, source)?);
let cache = Arc::new(crate::cache::Cache::with_capacity_bytes(8 * 1024 * 1024));
let descriptor = Arc::new(crate::descriptor_table::DescriptorTable::new(64));
#[cfg(feature = "metrics")]
let metrics = Arc::new(crate::Metrics::default());
let table = crate::table::Table::recover_inner(
source.to_path_buf(),
checksum,
0,
0,
0,
cache,
Some(descriptor),
Arc::clone(fs),
false,
false,
None,
#[cfg(zstd_any)]
None,
comparator.clone(),
#[cfg(feature = "metrics")]
metrics,
true,
)?;
if !table.range_tombstones().is_empty() {
return Err(crate::Error::FeatureUnsupported(
"salvage of an SST with range tombstones",
));
}
let writer = crate::table::Writer::new(dest.clone(), table.id(), 0, Arc::clone(fs))?
.use_data_block_compression(table.metadata.data_block_compression)
.use_ecc(table.metadata.ecc_params);
let walk = match salvage_blocks(&table, writer, comparator) {
Ok(walk) => walk,
Err(e) => {
discard_partial(fs, &dest);
return Err(e);
}
};
let salvaged_path = if walk.wrote {
Some(dest)
} else {
discard_partial(fs, &dest);
None
};
Ok(SalvageReport {
salvaged_path,
blocks_total: walk.blocks_total,
blocks_salvaged: walk.blocks_salvaged,
entries_salvaged: walk.entries_salvaged,
dropped: walk.dropped,
})
}
struct SalvageWalk {
blocks_total: usize,
blocks_salvaged: usize,
entries_salvaged: u64,
dropped: Vec<DroppedBlock>,
wrote: bool,
}
fn discard_partial(fs: &alloc::sync::Arc<dyn crate::fs::Fs>, dest: &std::path::Path) {
if let Err(e) = fs.remove_file(dest) {
log::warn!(
"salvage: could not remove the incomplete destination {}: {e}",
dest.display(),
);
}
}
fn salvage_blocks(
table: &crate::table::Table,
mut writer: crate::table::Writer,
comparator: &crate::comparator::SharedComparator,
) -> crate::Result<SalvageWalk> {
use crate::table::block::ParsedItem;
use alloc::format;
let mut blocks_total = 0usize;
let mut blocks_salvaged = 0usize;
let mut entries_salvaged = 0u64;
let mut dropped: Vec<DroppedBlock> = Vec::new();
let mut prev_end: Option<UserKey> = None;
for handle in table.data_block_handles() {
blocks_total += 1;
let keyed = match handle {
Ok(k) => k,
Err(e) => {
dropped.push(DroppedBlock {
offset: 0,
section: b"index".to_vec(),
reason: DropReason::HeaderCorrupted(format!("{e:?}")),
key_range: None,
});
break;
}
};
let end_key = keyed.end_key().clone();
let offset = *keyed.as_ref().offset();
match table.load_data_block(keyed.as_ref()) {
Ok(Some(block)) => match block.try_iter(comparator.clone()) {
Ok(iter) => {
for parsed in iter {
writer.write(parsed.materialize(block.as_slice()))?;
entries_salvaged += 1;
}
blocks_salvaged += 1;
}
Err(e) => dropped.push(DroppedBlock {
offset,
section: b"data".to_vec(),
reason: DropReason::DecodeError(format!("{e:?}")),
key_range: Some((
prev_end.clone().unwrap_or_else(UserKey::empty),
end_key.clone(),
)),
}),
},
Ok(None) => {}
Err(e) => {
let reason = match &e {
crate::Error::ChecksumMismatch { .. } => DropReason::ChecksumMismatch,
crate::Error::InvalidHeader(_) | crate::Error::InvalidTag(_) => {
DropReason::DecodeError(format!("{e:?}"))
}
_ => DropReason::ReadError(format!("{e:?}")),
};
dropped.push(DroppedBlock {
offset,
section: b"data".to_vec(),
reason,
key_range: Some((
prev_end.clone().unwrap_or_else(UserKey::empty),
end_key.clone(),
)),
});
}
}
prev_end = Some(end_key);
}
let wrote = blocks_salvaged > 0;
if wrote {
writer.finish()?;
} else {
drop(writer);
}
Ok(SalvageWalk {
blocks_total,
blocks_salvaged,
entries_salvaged,
dropped,
wrote,
})
}
#[cfg(test)]
mod tests;