use redb::{
Database, DatabaseError, ReadableDatabase, StorageBackend, StorageError, TableDefinition,
};
use std::sync::{Arc, RwLock};
const TABLE: TableDefinition<u64, &[u8]> = TableDefinition::new("t");
#[derive(Clone, Debug, Default)]
struct PatchBackend {
inner: Arc<RwLock<Vec<u8>>>,
}
impl PatchBackend {
fn recovery_required(&self) -> bool {
self.inner.read().unwrap()[9] & 2 != 0
}
fn corrupt_first_occurrence(&self, needle: &[u8]) -> bool {
let mut guard = self.inner.write().unwrap();
let mut i = 0;
while i + needle.len() <= guard.len() {
if &guard[i..i + needle.len()] == needle {
for b in &mut guard[i..i + needle.len()] {
*b ^= 0xFF;
}
return true;
}
i += 1;
}
false
}
}
impl StorageBackend for PatchBackend {
fn len(&self) -> Result<u64, std::io::Error> {
Ok(self.inner.read().unwrap().len() as u64)
}
fn read(&self, offset: u64, out: &mut [u8]) -> Result<(), std::io::Error> {
let offset = usize::try_from(offset).unwrap();
let guard = self.inner.read().unwrap();
if offset + out.len() > guard.len() {
return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
}
out.copy_from_slice(&guard[offset..offset + out.len()]);
Ok(())
}
fn set_len(&self, len: u64) -> Result<(), std::io::Error> {
self.inner
.write()
.unwrap()
.resize(len.try_into().unwrap(), 0);
Ok(())
}
fn sync_data(&self) -> Result<(), std::io::Error> {
Ok(())
}
fn write(&self, offset: u64, data: &[u8]) -> Result<(), std::io::Error> {
let offset = usize::try_from(offset).unwrap();
let mut guard = self.inner.write().unwrap();
if offset + data.len() > guard.len() {
return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
}
guard[offset..offset + data.len()].copy_from_slice(data);
Ok(())
}
}
#[test]
fn writes_are_refused_after_a_failed_check_integrity() {
let backend = PatchBackend::default();
let marker = vec![0xC7u8; 2000];
{
let db = Database::builder()
.create_with_backend(backend.clone())
.unwrap();
let txn = db.begin_write().unwrap();
txn.open_table(TABLE)
.unwrap()
.insert(&1u64, marker.as_slice())
.unwrap();
txn.commit().unwrap();
}
assert!(
backend.corrupt_first_occurrence(&marker),
"could not locate the value on disk to corrupt"
);
let mut db = Database::builder()
.create_with_backend(backend.clone())
.unwrap();
assert!(matches!(
db.check_integrity(),
Err(DatabaseError::Storage(StorageError::Corrupted(_)))
));
assert!(matches!(
db.begin_write(),
Err(redb::TransactionError::Storage(StorageError::Corrupted(_)))
));
assert!(matches!(
db.check_integrity(),
Err(DatabaseError::Storage(StorageError::Corrupted(_)))
));
assert_eq!(db.begin_read().unwrap().list_tables().unwrap().count(), 1);
drop(db);
assert!(backend.recovery_required());
}