diff --git a/src/error.rs b/src/error.rs index 115cb21..311e8e1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,8 @@ use std::{fmt::Display, fmt::Formatter, io}; pub enum Error { /// IO error. Io(io::Error), + /// Database may be inconsistent since an earlier error. Writes are disabled. + Inconsistent, /// Bucket has too many elements or bucket bits > directory bits. BadBucket { /// Bucket file offset. diff --git a/src/lib.rs b/src/lib.rs index c85e3a0..fef9d51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,11 +60,20 @@ pub enum ExportBinMode { Exp64, } +#[derive(Copy, Clone, Debug, Default, PartialEq)] +enum WriteState { + #[default] + Clean, + Dirty, + Inconsistent, +} + #[derive(Copy, Clone, Debug, Default)] pub struct ReadOnly; #[derive(Copy, Clone, Debug, Default)] pub struct ReadWrite { sync: bool, + state: WriteState, } pub trait CacheBucket { @@ -458,6 +467,7 @@ impl Gdbm { bucket_cache, read_write: ReadWrite { sync: open_options.write.sync, + state: WriteState::Dirty, }, }; @@ -646,19 +656,29 @@ impl Gdbm { // write out any cached, not-yet-written metadata and data to storage fn write_dirty(&mut self) -> io::Result<()> { + self.read_write.state = WriteState::Inconsistent; + self.write_buckets()?; self.write_dir()?; self.write_header()?; + self.read_write.state = WriteState::Clean; + Ok(()) } // API: ensure database is flushed to stable storage pub fn sync(&mut self) -> Result<()> { - self.header.increment_numsync(); - self.write_dirty() - .and_then(|_| self.f.sync_data()) - .map_err(Error::Io) + match self.read_write.state { + WriteState::Clean => Ok(()), + WriteState::Inconsistent => Err(Error::Inconsistent), + WriteState::Dirty => { + self.header.increment_numsync(); + self.write_dirty() + .and_then(|_| self.f.sync_data()) + .map_err(Error::Io) + } + } } fn int_remove(&mut self, key: &[u8]) -> Result>> { @@ -667,6 +687,13 @@ impl Gdbm { if get_opt.is_none() { return Ok(None); } + + if self.read_write.state == WriteState::Inconsistent { + return Err(Error::Inconsistent); + } + + self.read_write.state = WriteState::Inconsistent; + let (elem_ofs, data) = get_opt.unwrap(); let elem = self @@ -678,6 +705,8 @@ impl Gdbm { // release record bytes to available-space pool self.free_record(elem.data_ofs, elem.key_size + elem.data_size)?; + self.read_write.state = WriteState::Dirty; + Ok(Some(data)) } @@ -718,6 +747,12 @@ impl Gdbm { } fn int_insert(&mut self, key: Vec, data: Vec) -> Result<()> { + if self.read_write.state == WriteState::Inconsistent { + return Err(Error::Inconsistent); + } + + self.read_write.state = WriteState::Inconsistent; + let offset = self.allocate_record((key.len() + data.len()) as u32)?; self.f @@ -738,6 +773,8 @@ impl Gdbm { .unwrap() .insert(bucket_elem); + self.read_write.state = WriteState::Dirty; + Ok(()) } @@ -842,11 +879,21 @@ impl Gdbm { // API: convert pub fn convert(&mut self, options: &ConvertOptions) -> Result<()> { + if self.read_write.state == WriteState::Inconsistent { + return Err(Error::Inconsistent); + } + + self.read_write.state = WriteState::Inconsistent; + self.header .convert_numsync(options.numsync) .into_iter() .try_for_each(|(offset, length)| self.free_record(offset, length)) - .map_err(Error::Io) + .map_err(Error::Io)?; + + self.read_write.state = WriteState::Dirty; + + Ok(()) } }