@@ -48,8 +48,10 @@ use std::{
4848 ops:: { Add , AddAssign } ,
4949 path:: PathBuf ,
5050} ;
51+ use tokio:: task:: spawn_blocking;
5152
5253pub mod remote;
54+ use remote:: verify_snapshot;
5355
5456#[ derive( Debug , Copy , Clone ) ]
5557/// An object which may be associated with an error during snapshotting.
@@ -543,6 +545,7 @@ impl fmt::Debug for SnapshotSize {
543545}
544546
545547/// A repository of snapshots of a particular database instance.
548+ #[ derive( Clone ) ]
546549pub struct SnapshotRepository {
547550 /// The directory which contains all the snapshots.
548551 root : SnapshotsPath ,
@@ -752,6 +755,7 @@ impl SnapshotRepository {
752755 } ) ;
753756 }
754757
758+ let snapshot_dir = self . snapshot_dir_path ( tx_offset) ;
755759 let object_repo = Self :: object_repo ( & snapshot_dir) ?;
756760
757761 let blob_store = snapshot. reconstruct_blob_store ( & object_repo) ?;
@@ -769,6 +773,60 @@ impl SnapshotRepository {
769773 } )
770774 }
771775
776+ /// Read the [`Snapshot`] metadata at `tx_offset` and verify the integrity
777+ /// of all objects it refers to.
778+ ///
779+ /// Fails if:
780+ ///
781+ /// - No snapshot exists in `self` for `tx_offset`
782+ /// - The snapshot is incomplete, as detected by its lockfile still existing.
783+ /// - The snapshot file's magic number does not match [`MAGIC`].
784+ /// - Any object file (page or large blob) referenced by the snapshot file
785+ /// is missing or corrupted.
786+ ///
787+ /// The following conditions are not detected or considered as errors:
788+ ///
789+ /// - The snapshot file's version does not match [`CURRENT_SNAPSHOT_VERSION`].
790+ /// - The snapshot file's database identity or instance ID do not match
791+ /// those in `self`.
792+ /// - The snapshot file's module ABI version does not match
793+ /// [`CURRENT_MODULE_ABI_VERSION`].
794+ /// - The snapshot file's recorded transaction offset does not match
795+ /// `tx_offset`.
796+ ///
797+ /// Callers may want to inspect the returned [`Snapshot`] and ensure its
798+ /// contents match their expectations.
799+ pub async fn verify_snapshot ( & self , tx_offset : TxOffset ) -> Result < Snapshot , SnapshotError > {
800+ let snapshot_dir = self . snapshot_dir_path ( tx_offset) ;
801+ let snapshot = spawn_blocking ( {
802+ let snapshot_dir = snapshot_dir. clone ( ) ;
803+ move || {
804+ let lockfile = Lockfile :: lock_path ( & snapshot_dir) ;
805+ if lockfile. try_exists ( ) ? {
806+ return Err ( SnapshotError :: Incomplete { tx_offset, lockfile } ) ;
807+ }
808+
809+ let snapshot_file_path = snapshot_dir. snapshot_file ( tx_offset) ;
810+ let ( snapshot, _compress_type) = Snapshot :: read_from_file ( & snapshot_file_path) ?;
811+
812+ if snapshot. magic != MAGIC {
813+ return Err ( SnapshotError :: BadMagic {
814+ tx_offset,
815+ magic : snapshot. magic ,
816+ } ) ;
817+ }
818+ Ok ( snapshot)
819+ }
820+ } )
821+ . await
822+ . unwrap ( ) ?;
823+ let object_repo = Self :: object_repo ( & snapshot_dir) ?;
824+ verify_snapshot ( object_repo, self . root . clone ( ) , snapshot. clone ( ) )
825+ . await
826+ . map ( drop) ?;
827+ Ok ( snapshot)
828+ }
829+
772830 /// Open a repository at `root`, failing if the `root` doesn't exist or isn't a directory.
773831 ///
774832 /// Calls [`Path::is_dir`] and requires that the result is `true`.
0 commit comments