From 285dcdd2c1a4507a6ea67a04ad42ad8a2f134beb Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Wed, 13 Aug 2025 15:08:01 -0400 Subject: [PATCH] Prevent potential corruption from copying db file without holding proper locks --- core/lib.rs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index c0dc2e90d..7728411b3 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1921,13 +1921,38 @@ impl Connection { /// Because we are instead making a copy of the File, as a side-effect we are /// also having to checkpoint the database. pub fn copy_db(&self, file: &str) -> Result<()> { - // use a new PlatformIO instance here to allow for copying in-memory databases - let io: Arc = Arc::new(PlatformIO::new()?); - // checkpoint so everything is in the DB file before copying - self.pager - .borrow_mut() - .wal_checkpoint(CheckpointMode::Truncate)?; - self.pager.borrow_mut().db_file.copy_to(&*io, file) + if !self.is_db_initialized() { + return Err(LimboError::Page1NotAlloc); + } + let pager = self.pager.borrow(); + if let Some(wal) = pager.wal.as_ref() { + let checkpoint_result = self + ._db + .io + .block(|| wal.borrow_mut().checkpoint(&pager, CheckpointMode::Full))?; + + if checkpoint_result.everything_backfilled() { + let c = Completion::new_sync(|_| {}); + let c = pager.db_file.sync(c)?; + // sync the db file to ensure checkpointed frames were flushed. + self._db.io.wait_for_completion(c)?; + + // CheckpointResult here still holds the checkpoint/writer lock until it goes out of scope, + // so it's safe to copy from file. + + let src_io = self._db.io.clone(); + // use a new PlatformIO instance as the dest_io when copying the file to account that src could be memory + let dest_io: Arc = Arc::new(PlatformIO::new()?); + pager.db_file.copy_to(&*src_io, &*dest_io, file)?; + return Ok(()); + } else { + // unable to backfill all frames to the db file, so we cannot support copying file + return Err(LimboError::Busy); + } + } + Err(LimboError::InternalError( + "not yet supported for DB's without WAL".into(), + )) } /// Creates a HashSet of modules that have been loaded