Skip to main content

rust_rocksdb/
backup.rs

1// Copyright 2016 Alex Regueiro
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use crate::env::Env;
17use crate::{
18    DBCommon, Error, RateLimiterMode, ThreadMode,
19    db::DBInner,
20    ffi,
21    ffi_util::{raw_data, to_cpath},
22};
23
24use libc::{c_char, c_int, c_uchar, c_void};
25use std::ffi::CString;
26use std::ops::BitOr;
27use std::panic::{AssertUnwindSafe, catch_unwind};
28use std::path::{Path, PathBuf};
29use std::process;
30use std::slice;
31use std::sync::Arc;
32
33/// Represents information of a backup including timestamp of the backup
34/// and the size (please note that sum of all backups' sizes is bigger than the actual
35/// size of the backup directory because some data is shared by multiple backups).
36/// Backups are identified by their always-increasing IDs.
37pub struct BackupEngineInfo {
38    /// Timestamp of the backup
39    pub timestamp: i64,
40    /// ID of the backup
41    pub backup_id: u32,
42    /// Size of the backup
43    pub size: u64,
44    /// Number of files related to the backup
45    pub num_files: u32,
46    /// Application metadata passed to
47    /// [`BackupEngine::create_new_backup_with_metadata`].
48    ///
49    /// Empty when the backup carries no metadata. RocksDB stores the metadata
50    /// as a `std::string` and skips writing it when empty, so an absent value
51    /// and an empty value are indistinguishable on read back.
52    pub app_metadata: Vec<u8>,
53}
54
55pub struct BackupEngine {
56    inner: *mut ffi::rocksdb_backup_engine_t,
57    _outlive: Env,
58    // `BackupEngineOptions::backup_env` is stored in the engine as a raw
59    // `Env*` (see `rocksdb_backup_engine_options_set_env` in `db/c.cc`) and
60    // `BackupEngineImpl` keeps a by-value copy of the options, so that Env has
61    // to outlive the engine, not just the options it was set on.
62    _outlive_backup_env: Option<Env>,
63}
64
65pub struct BackupEngineOptions {
66    inner: *mut ffi::rocksdb_backup_engine_options_t,
67    // Kept alive because RocksDB only borrows the `Env*`. See
68    // `BackupEngineOptions::set_env`.
69    backup_env: Option<Env>,
70}
71
72pub struct RestoreOptions {
73    inner: *mut ffi::rocksdb_restore_options_t,
74}
75
76/// CPU priority for the background threads that copy files during a backup.
77///
78/// Mirrors `rocksdb::CpuPriority` in `include/rocksdb/port_defs.h`. The
79/// discriminants are the upstream values, which is what the C API expects.
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81#[repr(i32)]
82pub enum CpuPriority {
83    KIdle = 0,
84    KLow = 1,
85    KNormal = 2,
86    KHigh = 3,
87}
88
89impl From<i32> for CpuPriority {
90    /// Values RocksDB does not define map to [`CpuPriority::KNormal`], which
91    /// is both the upstream default for `background_thread_cpu_priority` and
92    /// the priority the copy threads start at.
93    fn from(value: i32) -> Self {
94        match value {
95            0 => Self::KIdle,
96            1 => Self::KLow,
97            3 => Self::KHigh,
98            // 2 is kNormal, and so is anything unrecognised.
99            _ => Self::KNormal,
100        }
101    }
102}
103
104/// How much of the destination database a restore may reuse.
105///
106/// Mirrors `RestoreOptions::Mode` in
107/// `include/rocksdb/utilities/backup_engine.h`. The values look like flags but
108/// RocksDB only ever compares the field for equality, so the modes are
109/// exclusive and must not be combined.
110#[derive(Debug, Copy, Clone, PartialEq, Eq)]
111#[repr(i32)]
112pub enum RestoreMode {
113    /// Cheapest way to restore a healthy database. Files whose names already
114    /// encode the DB session id are kept, so an interrupted or partial copy
115    /// can be completed instead of recopied. This mode also cooperates with
116    /// [`CreateBackupOptions::set_exclude_files_callback`] by looking for
117    /// excluded files in the existing database directory.
118    ///
119    /// Only effective for files using a modern share files naming scheme, see
120    /// [`ShareFilesNaming`].
121    KKeepLatestDbSessionIdFiles = 1,
122    /// For a database suspected to be corrupt. Each existing file is checksummed
123    /// against the checksum recorded in the backup metadata, and only files that
124    /// fail the comparison are replaced.
125    KVerifyChecksum = 2,
126    /// Zero trust and least efficient. Deletes every file in the destination
127    /// and copies everything from the backup. This is the default.
128    KPurgeAllFiles = 0xffff,
129}
130
131impl From<i32> for RestoreMode {
132    /// Values RocksDB does not define map to [`RestoreMode::KPurgeAllFiles`],
133    /// the upstream default and the only mode that makes no assumptions about
134    /// the destination directory.
135    fn from(value: i32) -> Self {
136        match value {
137            1 => Self::KKeepLatestDbSessionIdFiles,
138            2 => Self::KVerifyChecksum,
139            // 0xffff is kPurgeAllFiles, and so is anything unrecognised.
140            _ => Self::KPurgeAllFiles,
141        }
142    }
143}
144
145/// Naming scheme for table and blob files in the `shared_checksum` directory.
146///
147/// Mirrors `BackupEngineOptions::ShareFilesNaming` in
148/// `include/rocksdb/utilities/backup_engine.h`. It is a bit mask with two
149/// parts: the low 16 bits select one naming scheme, the high bits carry
150/// independent flags. RocksDB masks the two apart with
151/// [`Self::MASK_NO_NAMING_FLAGS`] and [`Self::MASK_NAMING_FLAGS`].
152///
153/// Only takes effect when both `share_table_files` and
154/// `share_files_with_checksum` are true.
155#[derive(Copy, Clone, Debug, Eq, PartialEq)]
156pub struct ShareFilesNaming(c_int);
157
158impl ShareFilesNaming {
159    /// `kLegacyCrc32cAndFileSize`. Names files
160    /// `<file_number>_<crc32c>_<file_size>.sst` and `.blob`. Only recommended
161    /// for preserving old behaviour: the triple can collide at massive scale,
162    /// and picking the name requires reading the whole file to checksum it.
163    pub const LEGACY_CRC32C_AND_FILE_SIZE: Self = Self(1);
164
165    /// `kUseDbSessionId`. Names SST files `<file_number>_s<db_session_id>.sst`,
166    /// which is strongly unique and known without reading the file. Blob files
167    /// and SST files with no session id fall back to
168    /// [`Self::LEGACY_CRC32C_AND_FILE_SIZE`].
169    pub const USE_DB_SESSION_ID: Self = Self(2);
170
171    /// `kFlagIncludeFileSize`. Inserts `_<file_size>` before the extension when
172    /// the scheme does not already include it.
173    ///
174    /// This is bit 31. The C API passes the mask as an `int`, so a value
175    /// carrying this flag reads back negative from [`Self::bits`].
176    pub const FLAG_INCLUDE_FILE_SIZE: Self = Self(1 << 31);
177
178    /// `kMaskNoNamingFlags`. Selects the naming scheme bits.
179    pub const MASK_NO_NAMING_FLAGS: Self = Self(0xffff);
180
181    /// `kMaskNamingFlags`. Selects the flag bits.
182    pub const MASK_NAMING_FLAGS: Self = Self(!0xffff);
183
184    /// The RocksDB default, [`Self::USE_DB_SESSION_ID`] with
185    /// [`Self::FLAG_INCLUDE_FILE_SIZE`].
186    pub const DEFAULT: Self = Self(Self::USE_DB_SESSION_ID.0 | Self::FLAG_INCLUDE_FILE_SIZE.0);
187
188    /// Returns the raw mask as the C API represents it.
189    pub const fn bits(self) -> c_int {
190        self.0
191    }
192
193    /// Returns true when every bit set in `other` is also set in `self`.
194    pub const fn contains(self, other: Self) -> bool {
195        (self.0 & other.0) == other.0
196    }
197}
198
199impl BitOr for ShareFilesNaming {
200    type Output = Self;
201
202    fn bitor(self, rhs: Self) -> Self {
203        Self(self.0 | rhs.0)
204    }
205}
206
207type ProgressFn = dyn Fn() + Send + Sync + 'static;
208type ExcludeFilesFn = dyn Fn(&[u8]) -> bool + Send + Sync + 'static;
209
210/// Owns a progress closure. [`CreateBackupOptions`] hands the address of the
211/// holder to the C API as the opaque `state` pointer, so the holder is kept
212/// behind an [`Arc`] rather than a `Box`: the C side holds a raw pointer to it
213/// while this crate still owns it, which is exactly what `Box`'s uniqueness
214/// guarantee rules out. This mirrors how `Options::set_callback_logger` keeps
215/// its log callback alive.
216struct ProgressCallback {
217    callback: Box<ProgressFn>,
218}
219
220/// Owns an exclude files closure. See [`ProgressCallback`].
221struct ExcludeFilesCallback {
222    callback: Box<ExcludeFilesFn>,
223}
224
225/// Trampoline for `rocksdb_create_backup_options_progress_cb`.
226unsafe extern "C" fn progress_callback_trampoline(state: *mut c_void) {
227    // SAFETY: `state` is the address of the `ProgressCallback` that
228    // `set_progress_callback` registered. `CreateBackupOptions` keeps it alive
229    // until after `rocksdb_create_backup_options_destroy` has run, and a
230    // shared reference is enough because `ProgressFn` is a `dyn Fn`. RocksDB
231    // serialises progress reports behind a mutex but may report from any copy
232    // thread, so `&mut` here would alias.
233    let holder = unsafe { &*state.cast::<ProgressCallback>() };
234    // A Rust panic cannot unwind out of an `extern "C"` function, and RocksDB
235    // only knows how to turn a C++ exception into Status::Aborted.
236    if catch_unwind(AssertUnwindSafe(|| (holder.callback)())).is_err() {
237        process::abort();
238    }
239}
240
241/// Trampoline for `rocksdb_create_backup_options_exclude_files_cb`.
242unsafe extern "C" fn exclude_files_callback_trampoline(
243    state: *mut c_void,
244    relative_file: *const c_char,
245    relative_file_len: usize,
246) -> c_uchar {
247    // SAFETY: see `progress_callback_trampoline`.
248    let holder = unsafe { &*state.cast::<ExcludeFilesCallback>() };
249    let relative_file = if relative_file_len == 0 {
250        &[][..]
251    } else {
252        // SAFETY: `db/c.cc` passes `relative_file.data()` and
253        // `relative_file.size()` of a live `std::string`, and the closure only
254        // borrows it for the duration of this call.
255        unsafe { slice::from_raw_parts(relative_file.cast::<u8>(), relative_file_len) }
256    };
257    match catch_unwind(AssertUnwindSafe(|| (holder.callback)(relative_file))) {
258        Ok(true) => 1,
259        Ok(false) => 0,
260        Err(_) => process::abort(),
261    }
262}
263
264/// Options for a single call to [`BackupEngine::create_new_backup_with_options`]
265/// or [`BackupEngine::create_new_backup_with_metadata`].
266///
267/// Wraps `rocksdb::CreateBackupOptions`.
268///
269/// # Callback ownership
270///
271/// `rocksdb_create_backup_options_set_progress_callback` and
272/// `rocksdb_create_backup_options_set_exclude_files_callback` take a bare
273/// `void* state` with no destructor argument. `db/c.cc` stores it in
274/// `rocksdb_create_backup_options_t::progress_state` /
275/// `exclude_files_state` and copies it into a C++ lambda by value.
276/// `rocksdb_create_backup_options_destroy` is just `delete options`, so the C
277/// API never frees the state. This type therefore owns the closures: it frees
278/// them when it is dropped, and frees the previous one when a setter is called
279/// a second time.
280///
281/// The C++ lambda captures the state pointer by value rather than the options
282/// wrapper, so the state must stay alive for the whole backup, not merely
283/// until the FFI call returns. Backup creation is synchronous and borrows
284/// these options for its duration, so holding the closures in this type is
285/// enough.
286pub struct CreateBackupOptions {
287    inner: *mut ffi::rocksdb_create_backup_options_t,
288    progress_callback: Option<Arc<ProgressCallback>>,
289    exclude_files_callback: Option<Arc<ExcludeFilesCallback>>,
290}
291
292// BackupEngine is a simple pointer wrapper, so it's safe to send to another thread
293// since the underlying RocksDB backup engine is thread-safe.
294unsafe impl Send for BackupEngine {}
295
296impl BackupEngine {
297    /// Open a backup engine with the specified options and RocksDB Env.
298    pub fn open(opts: &BackupEngineOptions, env: &Env) -> Result<Self, Error> {
299        let be: *mut ffi::rocksdb_backup_engine_t;
300        unsafe {
301            be = ffi_try!(ffi::rocksdb_backup_engine_open_opts(
302                opts.inner,
303                env.0.inner
304            ));
305        }
306
307        if be.is_null() {
308            return Err(Error::new("Could not initialize backup engine.".to_owned()));
309        }
310
311        Ok(Self {
312            inner: be,
313            _outlive: env.clone(),
314            _outlive_backup_env: opts.backup_env.clone(),
315        })
316    }
317
318    /// Captures the state of the database in the latest backup.
319    ///
320    /// Note: no flush before backup is performed. User might want to
321    /// use `create_new_backup_flush` instead.
322    pub fn create_new_backup<T: ThreadMode, D: DBInner>(
323        &mut self,
324        db: &DBCommon<T, D>,
325    ) -> Result<(), Error> {
326        self.create_new_backup_flush(db, false)
327    }
328
329    /// Captures the state of the database in the latest backup.
330    ///
331    /// Set flush_before_backup=true to avoid losing unflushed key/value
332    /// pairs from the memtable.
333    pub fn create_new_backup_flush<T: ThreadMode, D: DBInner>(
334        &mut self,
335        db: &DBCommon<T, D>,
336        flush_before_backup: bool,
337    ) -> Result<(), Error> {
338        unsafe {
339            ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_flush(
340                self.inner,
341                db.inner.inner(),
342                c_uchar::from(flush_before_backup),
343            ));
344            Ok(())
345        }
346    }
347
348    /// Captures the state of the database in the latest backup and returns the
349    /// id of the new backup.
350    ///
351    /// Unlike [`Self::create_new_backup_flush`] this exposes the full
352    /// `CreateBackupOptions` surface: flush behaviour, CPU priority of the copy
353    /// threads, and the progress and exclude files callbacks.
354    pub fn create_new_backup_with_options<T: ThreadMode, D: DBInner>(
355        &mut self,
356        db: &DBCommon<T, D>,
357        opts: &CreateBackupOptions,
358    ) -> Result<u32, Error> {
359        let mut backup_id: u32 = 0;
360        unsafe {
361            ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_with_options(
362                self.inner,
363                db.inner.inner(),
364                opts.inner,
365                &raw mut backup_id,
366            ));
367        }
368        Ok(backup_id)
369    }
370
371    /// Same as [`Self::create_new_backup_with_options`] but also stores
372    /// application metadata with the backup, and returns the id of the new
373    /// backup.
374    ///
375    /// Read the metadata back with [`BackupEngineInfo::app_metadata`].
376    ///
377    /// `app_metadata` is passed with an explicit length and RocksDB stores it
378    /// hex encoded in the backup meta file, so any byte sequence is preserved,
379    /// including embedded NULs.
380    ///
381    /// RocksDB rejects metadata larger than 1 MiB with an
382    /// `Invalid argument: App metadata too large` error, and nothing is
383    /// written in that case.
384    pub fn create_new_backup_with_metadata<T: ThreadMode, D: DBInner>(
385        &mut self,
386        db: &DBCommon<T, D>,
387        opts: &CreateBackupOptions,
388        app_metadata: &[u8],
389    ) -> Result<u32, Error> {
390        // `db/c.cc` substitutes an empty string for a null pointer, which
391        // avoids handing it the dangling pointer an empty slice yields.
392        let (metadata_ptr, metadata_len) = if app_metadata.is_empty() {
393            (std::ptr::null(), 0)
394        } else {
395            (app_metadata.as_ptr().cast::<c_char>(), app_metadata.len())
396        };
397
398        let mut backup_id: u32 = 0;
399        unsafe {
400            ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_with_metadata(
401                self.inner,
402                db.inner.inner(),
403                opts.inner,
404                metadata_ptr,
405                metadata_len,
406                &raw mut backup_id,
407            ));
408        }
409        Ok(backup_id)
410    }
411
412    pub fn purge_old_backups(&mut self, num_backups_to_keep: usize) -> Result<(), Error> {
413        unsafe {
414            ffi_try!(ffi::rocksdb_backup_engine_purge_old_backups(
415                self.inner,
416                num_backups_to_keep as u32,
417            ));
418            Ok(())
419        }
420    }
421
422    /// Restore from the latest backup
423    ///
424    /// # Arguments
425    ///
426    /// * `db_dir` - A path to the database directory
427    /// * `wal_dir` - A path to the wal directory
428    /// * `opts` - Restore options
429    ///
430    /// # Examples
431    ///
432    /// ```ignore
433    /// use rust_rocksdb::backup::{BackupEngine, BackupEngineOptions};
434    /// let backup_opts = BackupEngineOptions::default();
435    /// let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
436    /// let mut restore_option = rust_rocksdb::backup::RestoreOptions::default();
437    /// restore_option.set_keep_log_files(true); /// true to keep log files
438    /// if let Err(e) = backup_engine.restore_from_latest_backup(&db_path, &wal_dir, &restore_option) {
439    ///     error!("Failed to restore from the backup. Error: {:?}", e);
440    ///     return Err(e.to_string());
441    /// }
442    /// ```
443    pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
444        &mut self,
445        db_dir: D,
446        wal_dir: W,
447        opts: &RestoreOptions,
448    ) -> Result<(), Error> {
449        let c_db_dir = to_cpath(db_dir)?;
450        let c_wal_dir = to_cpath(wal_dir)?;
451
452        unsafe {
453            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
454                self.inner,
455                c_db_dir.as_ptr(),
456                c_wal_dir.as_ptr(),
457                opts.inner,
458            ));
459        }
460        Ok(())
461    }
462
463    /// Restore from a specified backup
464    ///
465    /// The specified backup id should be passed in as an additional parameter.
466    pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
467        &mut self,
468        db_dir: D,
469        wal_dir: W,
470        opts: &RestoreOptions,
471        backup_id: u32,
472    ) -> Result<(), Error> {
473        let c_db_dir = to_cpath(db_dir)?;
474        let c_wal_dir = to_cpath(wal_dir)?;
475
476        unsafe {
477            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
478                self.inner,
479                c_db_dir.as_ptr(),
480                c_wal_dir.as_ptr(),
481                opts.inner,
482                backup_id,
483            ));
484        }
485        Ok(())
486    }
487
488    /// Checks that each file exists and that the size of the file matches our
489    /// expectations. it does not check file checksum.
490    ///
491    /// If this BackupEngine created the backup, it compares the files' current
492    /// sizes against the number of bytes written to them during creation.
493    /// Otherwise, it compares the files' current sizes against their sizes when
494    /// the BackupEngine was opened.
495    pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
496        unsafe {
497            ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
498                self.inner, backup_id,
499            ));
500        }
501        Ok(())
502    }
503
504    /// Get a list of all backups together with information on timestamp of the backup
505    /// and the size (please note that sum of all backups' sizes is bigger than the actual
506    /// size of the backup directory because some data is shared by multiple backups).
507    /// Backups are identified by their always-increasing IDs.
508    ///
509    /// You can perform this function safely, even with other BackupEngine performing
510    /// backups on the same directory
511    pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
512        unsafe {
513            let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
514
515            let n = ffi::rocksdb_backup_engine_info_count(i);
516
517            let mut info = Vec::with_capacity(n as usize);
518            for index in 0..n {
519                // `rocksdb_backup_engine_info_app_metadata` returns
520                // `BackupInfo::app_metadata.data()`, a pointer borrowed from
521                // the info object rather than a malloc'd copy, so it has to be
522                // copied before `rocksdb_backup_engine_info_destroy` below. A
523                // backup with no metadata yields an empty string, so the
524                // pointer stays valid and the length is 0.
525                let mut metadata_len: usize = 0;
526                let metadata_ptr =
527                    ffi::rocksdb_backup_engine_info_app_metadata(i, index, &raw mut metadata_len);
528                let app_metadata = if metadata_len == 0 {
529                    Vec::new()
530                } else {
531                    raw_data(metadata_ptr, metadata_len).unwrap_or_default()
532                };
533
534                info.push(BackupEngineInfo {
535                    timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
536                    backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
537                    size: ffi::rocksdb_backup_engine_info_size(i, index),
538                    num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
539                    app_metadata,
540                });
541            }
542
543            // destroy backup info object
544            ffi::rocksdb_backup_engine_info_destroy(i);
545
546            info
547        }
548    }
549
550    /// Aborts a backup that is currently running.
551    ///
552    /// This is one way. Once it is called on a backup engine, every later backup
553    /// request on that engine fails. The backup directory is left consistent and is
554    /// cleaned up by the next backup or garbage collection performed through a new
555    /// backup engine on the same directory.
556    pub fn stop_backup(&mut self) {
557        unsafe {
558            ffi::rocksdb_backup_engine_stop_backup(self.inner);
559        }
560    }
561}
562
563impl BackupEngineOptions {
564    /// Initializes `BackupEngineOptions` with the directory to be used for storing/accessing the
565    /// backup files.
566    pub fn new<P: AsRef<Path>>(backup_dir: P) -> Result<Self, Error> {
567        let backup_dir = backup_dir.as_ref();
568        let c_backup_dir = CString::new(backup_dir.to_string_lossy().as_bytes()).map_err(|_| {
569            Error::new(
570                "Failed to convert backup_dir to CString \
571                     when constructing BackupEngineOptions"
572                    .to_owned(),
573            )
574        })?;
575
576        unsafe {
577            let opts = ffi::rocksdb_backup_engine_options_create(c_backup_dir.as_ptr());
578            assert!(!opts.is_null(), "Could not create RocksDB backup options");
579
580            Ok(Self {
581                inner: opts,
582                backup_env: None,
583            })
584        }
585    }
586
587    /// Sets the number of operations (such as file copies or file checksums) that `RocksDB` may
588    /// perform in parallel when executing a backup or restore.
589    ///
590    /// Default: 1
591    pub fn set_max_background_operations(&mut self, max_background_operations: i32) {
592        unsafe {
593            ffi::rocksdb_backup_engine_options_set_max_background_operations(
594                self.inner,
595                max_background_operations,
596            );
597        }
598    }
599
600    /// Sets whether to use fsync(2) to sync file data and metadata to disk after every file write,
601    /// guaranteeing that backups will be consistent after a reboot or if machine crashes. Setting
602    /// it to false will speed things up a bit, but some (newer) backups might be inconsistent. In
603    /// most cases, everything should be fine, though.
604    ///
605    /// Default: true
606    ///
607    /// Documentation: <https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB#advanced-usage>
608    pub fn set_sync(&mut self, sync: bool) {
609        unsafe {
610            ffi::rocksdb_backup_engine_options_set_sync(self.inner, c_uchar::from(sync));
611        }
612    }
613
614    /// Returns the value of the `sync` option.
615    pub fn get_sync(&mut self) -> bool {
616        let val_u8 = unsafe { ffi::rocksdb_backup_engine_options_get_sync(self.inner) };
617        val_u8 != 0
618    }
619
620    /// (Experimental - subject to change or removal) When taking a backup and saving file
621    /// temperature info (minimum schema_version is 2), there are two potential sources of
622    /// truth for the placement of files into temperature tiers: (a) the current file
623    /// temperature reported by the FileSystem or (b) the expected file temperature recorded
624    /// in DB manifest. When this option is false (default), (b) overrides (a) if both are not
625    /// UNKNOWN. When true, (a) overrides (b) if both are not UNKNOWN. Regardless of this
626    /// setting, a known temperature overrides UNKNOWN.
627    pub fn set_current_temperatures_override_manifest(&mut self, val: bool) {
628        unsafe {
629            ffi::rocksdb_backup_engine_options_set_current_temperatures_override_manifest(
630                self.inner,
631                c_uchar::from(val),
632            );
633        }
634    }
635
636    /// Returns the value of the `current_temperatures_override_manifest` option.
637    pub fn get_current_temperatures_override_manifest(&self) -> bool {
638        unsafe {
639            ffi::rocksdb_backup_engine_options_get_current_temperatures_override_manifest(
640                self.inner,
641            ) != 0
642        }
643    }
644
645    /// Sets the `io_buffer_size` option.
646    pub fn set_io_buffer_size(&mut self, val: u64) {
647        unsafe {
648            ffi::rocksdb_backup_engine_options_set_io_buffer_size(self.inner, val);
649        }
650    }
651
652    /// Returns the value of the `io_buffer_size` option.
653    pub fn get_io_buffer_size(&self) -> u64 {
654        unsafe { ffi::rocksdb_backup_engine_options_get_io_buffer_size(self.inner) }
655    }
656
657    /// Major schema version to use when writing backup meta files 1 (default) - compatible
658    /// with very old versions of RocksDB. 2 - can be read by RocksDB versions >= 6.19.0.
659    /// Minimum schema version for
660    /// - (Experimental) saving and restoring file temperature metadata
661    pub fn set_schema_version(&mut self, val: c_int) {
662        unsafe {
663            ffi::rocksdb_backup_engine_options_set_schema_version(self.inner, val);
664        }
665    }
666
667    /// Returns the value of the `schema_version` option.
668    pub fn get_schema_version(&self) -> c_int {
669        unsafe { ffi::rocksdb_backup_engine_options_get_schema_version(self.inner) }
670    }
671
672    /// share_files_with_checksum supports table and blob files.
673    ///
674    /// Only used if share_table_files is set to true. Setting to false is DEPRECATED and
675    /// potentially dangerous because in that case BackupEngine can lose data if backing up
676    /// databases with distinct or divergent history, for example if restoring from a backup
677    /// other than the latest, writing to the DB, and creating another backup. Setting to true
678    /// (default) prevents these issues by ensuring that different table files (SSTs) and blob
679    /// files with the same number are treated as distinct. See
680    /// share_files_with_checksum_naming and ShareFilesNaming.
681    ///
682    /// Default: true
683    pub fn set_share_files_with_checksum(&mut self, val: bool) {
684        unsafe {
685            ffi::rocksdb_backup_engine_options_set_share_files_with_checksum(
686                self.inner,
687                c_uchar::from(val),
688            );
689        }
690    }
691
692    /// Returns the value of the `share_files_with_checksum` option.
693    pub fn get_share_files_with_checksum(&self) -> bool {
694        unsafe { ffi::rocksdb_backup_engine_options_get_share_files_with_checksum(self.inner) != 0 }
695    }
696
697    /// Returns the current `backup_log_files` setting.
698    ///
699    /// See [`Self::set_backup_log_files`] for what this controls.
700    pub fn get_backup_log_files(&self) -> bool {
701        unsafe { ffi::rocksdb_backup_engine_options_get_backup_log_files(self.inner) != 0 }
702    }
703
704    /// Returns the current `backup_rate_limit` setting.
705    ///
706    /// See [`Self::set_backup_rate_limit`] for what this controls.
707    pub fn get_backup_rate_limit(&self) -> u64 {
708        unsafe { ffi::rocksdb_backup_engine_options_get_backup_rate_limit(self.inner) }
709    }
710
711    /// Returns the current `callback_trigger_interval_size` setting.
712    ///
713    /// See [`Self::set_callback_trigger_interval_size`] for what this controls.
714    pub fn get_callback_trigger_interval_size(&self) -> u64 {
715        unsafe { ffi::rocksdb_backup_engine_options_get_callback_trigger_interval_size(self.inner) }
716    }
717
718    /// Returns the current `destroy_old_data` setting.
719    ///
720    /// See [`Self::set_destroy_old_data`] for what this controls.
721    pub fn get_destroy_old_data(&self) -> bool {
722        unsafe { ffi::rocksdb_backup_engine_options_get_destroy_old_data(self.inner) != 0 }
723    }
724
725    /// Returns the current `max_background_operations` setting.
726    ///
727    /// See [`Self::set_max_background_operations`] for what this controls.
728    pub fn get_max_background_operations(&self) -> c_int {
729        unsafe { ffi::rocksdb_backup_engine_options_get_max_background_operations(self.inner) }
730    }
731
732    /// Returns the current `max_valid_backups_to_open` setting.
733    ///
734    /// See [`Self::set_max_valid_backups_to_open`] for what this controls.
735    pub fn get_max_valid_backups_to_open(&self) -> c_int {
736        unsafe { ffi::rocksdb_backup_engine_options_get_max_valid_backups_to_open(self.inner) }
737    }
738
739    /// Returns the current `restore_rate_limit` setting.
740    ///
741    /// See [`Self::set_restore_rate_limit`] for what this controls.
742    pub fn get_restore_rate_limit(&self) -> u64 {
743        unsafe { ffi::rocksdb_backup_engine_options_get_restore_rate_limit(self.inner) }
744    }
745
746    /// Returns the current `share_table_files` setting.
747    ///
748    /// See [`Self::set_share_table_files`] for what this controls.
749    pub fn get_share_table_files(&self) -> bool {
750        unsafe { ffi::rocksdb_backup_engine_options_get_share_table_files(self.inner) != 0 }
751    }
752
753    /// If false, we won't backup log files. This option can be useful for backing up
754    /// in-memory databases where log file are persisted, but table files are in memory.
755    /// Default: true.
756    pub fn set_backup_log_files(&mut self, val: bool) {
757        unsafe {
758            ffi::rocksdb_backup_engine_options_set_backup_log_files(self.inner, c_uchar::from(val));
759        }
760    }
761
762    /// Max bytes that can be transferred in a second while creating a backup.
763    ///
764    /// If 0, go as fast as you can. This limit only applies to writes.
765    ///
766    /// Default: `0`
767    pub fn set_backup_rate_limit(&mut self, val: u64) {
768        unsafe {
769            ffi::rocksdb_backup_engine_options_set_backup_rate_limit(self.inner, val);
770        }
771    }
772
773    /// During backup user can get callback every time next callback_trigger_interval_size
774    /// bytes being copied. Default: 4194304.
775    pub fn set_callback_trigger_interval_size(&mut self, val: u64) {
776        unsafe {
777            ffi::rocksdb_backup_engine_options_set_callback_trigger_interval_size(self.inner, val);
778        }
779    }
780
781    /// If true, it will delete whatever backups there are already Default: false.
782    pub fn set_destroy_old_data(&mut self, val: bool) {
783        unsafe {
784            ffi::rocksdb_backup_engine_options_set_destroy_old_data(self.inner, c_uchar::from(val));
785        }
786    }
787
788    /// For BackupEngineReadOnly, Open() will open at most this many of the latest
789    /// non-corrupted backups.
790    ///
791    /// Note: this setting is ignored (behaves like INT_MAX) for any kind of writable
792    /// BackupEngine because it would inhibit accounting for shared files for proper backup
793    /// deletion, including purging any incompletely created backups on creation of a new
794    /// backup.
795    ///
796    /// Default: INT_MAX.
797    pub fn set_max_valid_backups_to_open(&mut self, val: c_int) {
798        unsafe {
799            ffi::rocksdb_backup_engine_options_set_max_valid_backups_to_open(self.inner, val);
800        }
801    }
802
803    /// Max bytes that can be transferred in a second during restore. If 0, go as fast as you
804    /// can This limit only applies to writes. To also limit reads, a rate limiter able to
805    /// also limit reads (e.g, its mode = kAllIo) have to be passed in through the option
806    /// "restore_rate_limiter" Default: 0.
807    pub fn set_restore_rate_limit(&mut self, val: u64) {
808        unsafe {
809            ffi::rocksdb_backup_engine_options_set_restore_rate_limit(self.inner, val);
810        }
811    }
812
813    /// Share_table_files supports table and blob files.
814    ///
815    /// If share_table_files == true, the backup directory will share table and blob files
816    /// among backups, to save space among backups of the same DB and to enable incremental
817    /// backups by only copying new files. If share_table_files == false, each backup will be
818    /// on its own and will not share any data with other backups.
819    ///
820    /// default: true.
821    pub fn set_share_table_files(&mut self, val: bool) {
822        unsafe {
823            ffi::rocksdb_backup_engine_options_set_share_table_files(
824                self.inner,
825                c_uchar::from(val),
826            );
827        }
828    }
829
830    /// Changes the directory backups are written to and read from, replacing
831    /// the one given to [`Self::new`].
832    ///
833    /// RocksDB copies the string, so the path is not borrowed. It cannot
834    /// contain an interior NUL.
835    pub fn set_backup_dir<P: AsRef<Path>>(&mut self, backup_dir: P) -> Result<(), Error> {
836        let c_backup_dir = to_cpath(backup_dir)?;
837        unsafe {
838            ffi::rocksdb_backup_engine_options_set_backup_dir(self.inner, c_backup_dir.as_ptr());
839        }
840        Ok(())
841    }
842
843    /// Returns the directory backups are written to and read from.
844    pub fn get_backup_dir(&self) -> PathBuf {
845        unsafe {
846            // The C API returns `backup_dir.data()`, borrowed from the options
847            // object rather than malloc'd, so it must be copied here and must
848            // not be freed.
849            let mut len: usize = 0;
850            let ptr = ffi::rocksdb_backup_engine_options_get_backup_dir(self.inner, &raw mut len);
851            if len == 0 {
852                return PathBuf::new();
853            }
854            path_from_bytes(slice::from_raw_parts(ptr.cast::<u8>(), len))
855        }
856    }
857
858    /// Sets the naming scheme used for table and blob files stored in the
859    /// `shared_checksum` directory.
860    ///
861    /// The value must select exactly one naming scheme from the low bits, see
862    /// [`ShareFilesNaming`]. Default: [`ShareFilesNaming::DEFAULT`].
863    ///
864    /// Changing this is downgrade safe, because RocksDB can read, restore and
865    /// delete backups whose files use a different scheme. It does mean that
866    /// adding more backups to the same directory can store a file a second
867    /// time under its new shared name.
868    pub fn set_share_files_with_checksum_naming(&mut self, val: ShareFilesNaming) {
869        unsafe {
870            ffi::rocksdb_backup_engine_options_set_share_files_with_checksum_naming(
871                self.inner,
872                val.bits(),
873            );
874        }
875    }
876
877    /// Returns the current `share_files_with_checksum_naming` setting.
878    pub fn get_share_files_with_checksum_naming(&self) -> ShareFilesNaming {
879        unsafe {
880            ShareFilesNaming(
881                ffi::rocksdb_backup_engine_options_get_share_files_with_checksum_naming(self.inner),
882            )
883        }
884    }
885
886    /// Sets the Env used for the backup directory itself. The database being
887    /// backed up keeps using its own Env.
888    ///
889    /// RocksDB stores a borrowed `Env*` and `BackupEngineImpl` copies the
890    /// options by value, so the Env has to outlive the engine. This type keeps
891    /// a handle, and [`BackupEngine::open`] takes another one, so the Env stays
892    /// alive for as long as either of them does.
893    pub fn set_env(&mut self, env: &Env) {
894        unsafe {
895            ffi::rocksdb_backup_engine_options_set_env(self.inner, env.0.inner);
896        }
897        self.backup_env = Some(env.clone());
898    }
899
900    /// Sets a rate limiter for backup I/O, which unlike
901    /// [`Self::set_backup_rate_limit`] can be tuned beyond a byte budget.
902    ///
903    /// The limiter created here defaults to limiting writes only. Use
904    /// [`Self::set_backup_rate_limiter_with_mode`] to limit reads as well.
905    ///
906    /// The C API holds the limiter in a `shared_ptr`, so it stays alive for as
907    /// long as the options and the engine need it and there is nothing for the
908    /// caller to keep.
909    pub fn set_backup_rate_limiter(
910        &mut self,
911        rate_bytes_per_sec: i64,
912        refill_period_us: i64,
913        fairness: i32,
914    ) {
915        unsafe {
916            let ratelimiter =
917                ffi::rocksdb_ratelimiter_create(rate_bytes_per_sec, refill_period_us, fairness);
918            ffi::rocksdb_backup_engine_options_set_backup_rate_limiter(self.inner, ratelimiter);
919            ffi::rocksdb_ratelimiter_destroy(ratelimiter);
920        }
921    }
922
923    /// Sets a rate limiter for backup I/O, choosing which operations count
924    /// against the limit and whether the limit is auto tuned.
925    ///
926    /// See [`crate::Options::set_ratelimiter_with_mode`] for what the
927    /// parameters mean.
928    pub fn set_backup_rate_limiter_with_mode(
929        &mut self,
930        rate_bytes_per_sec: i64,
931        refill_period_us: i64,
932        fairness: i32,
933        mode: RateLimiterMode,
934        auto_tuned: bool,
935    ) {
936        unsafe {
937            let ratelimiter = ffi::rocksdb_ratelimiter_create_with_mode(
938                rate_bytes_per_sec,
939                refill_period_us,
940                fairness,
941                mode as c_int,
942                auto_tuned,
943            );
944            ffi::rocksdb_backup_engine_options_set_backup_rate_limiter(self.inner, ratelimiter);
945            ffi::rocksdb_ratelimiter_destroy(ratelimiter);
946        }
947    }
948
949    /// Sets a rate limiter for restore I/O.
950    ///
951    /// The limiter created here defaults to limiting writes only, which is
952    /// what [`Self::set_restore_rate_limit`] already does. Use
953    /// [`Self::set_restore_rate_limiter_with_mode`] with
954    /// [`RateLimiterMode::KAllIo`] to also limit reads.
955    ///
956    /// Ownership works as described on [`Self::set_backup_rate_limiter`].
957    pub fn set_restore_rate_limiter(
958        &mut self,
959        rate_bytes_per_sec: i64,
960        refill_period_us: i64,
961        fairness: i32,
962    ) {
963        unsafe {
964            let ratelimiter =
965                ffi::rocksdb_ratelimiter_create(rate_bytes_per_sec, refill_period_us, fairness);
966            ffi::rocksdb_backup_engine_options_set_restore_rate_limiter(self.inner, ratelimiter);
967            ffi::rocksdb_ratelimiter_destroy(ratelimiter);
968        }
969    }
970
971    /// Sets a rate limiter for restore I/O, choosing which operations count
972    /// against the limit and whether the limit is auto tuned.
973    ///
974    /// See [`crate::Options::set_ratelimiter_with_mode`] for what the
975    /// parameters mean.
976    pub fn set_restore_rate_limiter_with_mode(
977        &mut self,
978        rate_bytes_per_sec: i64,
979        refill_period_us: i64,
980        fairness: i32,
981        mode: RateLimiterMode,
982        auto_tuned: bool,
983    ) {
984        unsafe {
985            let ratelimiter = ffi::rocksdb_ratelimiter_create_with_mode(
986                rate_bytes_per_sec,
987                refill_period_us,
988                fairness,
989                mode as c_int,
990                auto_tuned,
991            );
992            ffi::rocksdb_backup_engine_options_set_restore_rate_limiter(self.inner, ratelimiter);
993            ffi::rocksdb_ratelimiter_destroy(ratelimiter);
994        }
995    }
996}
997
998/// Converts bytes borrowed from a RocksDB `std::string` into a path.
999///
1000/// Unix paths are arbitrary bytes, so this is lossless there. Other platforms
1001/// have no byte level path constructor, so invalid UTF-8 is replaced.
1002#[cfg(unix)]
1003fn path_from_bytes(bytes: &[u8]) -> PathBuf {
1004    use std::os::unix::ffi::OsStrExt;
1005    PathBuf::from(std::ffi::OsStr::from_bytes(bytes))
1006}
1007
1008#[cfg(not(unix))]
1009fn path_from_bytes(bytes: &[u8]) -> PathBuf {
1010    PathBuf::from(String::from_utf8_lossy(bytes).into_owned())
1011}
1012
1013impl CreateBackupOptions {
1014    /// If true, flush the memtables before taking the backup, so that writes
1015    /// that never reached a WAL are not lost. A flush always happens when 2PC
1016    /// is enabled.
1017    ///
1018    /// Default: false
1019    pub fn set_flush_before_backup(&mut self, val: bool) {
1020        unsafe {
1021            ffi::rocksdb_create_backup_options_set_flush_before_backup(
1022                self.inner,
1023                c_uchar::from(val),
1024            );
1025        }
1026    }
1027
1028    /// Returns the current `flush_before_backup` setting.
1029    pub fn get_flush_before_backup(&self) -> bool {
1030        unsafe { ffi::rocksdb_create_backup_options_get_flush_before_backup(self.inner) != 0 }
1031    }
1032
1033    /// If true, flush all column families atomically, giving cross column
1034    /// family consistency without WAL files. Combined with
1035    /// `BackupEngineOptions::backup_log_files = false` this makes it safe to
1036    /// skip backing up WALs for a multi column family database.
1037    ///
1038    /// Only takes effect when `flush_before_backup` is also true.
1039    ///
1040    /// Default: false
1041    pub fn set_atomic_flush(&mut self, val: bool) {
1042        unsafe {
1043            ffi::rocksdb_create_backup_options_set_atomic_flush(self.inner, c_uchar::from(val));
1044        }
1045    }
1046
1047    /// Returns the current `atomic_flush` setting.
1048    pub fn get_atomic_flush(&self) -> bool {
1049        unsafe { ffi::rocksdb_create_backup_options_get_atomic_flush(self.inner) != 0 }
1050    }
1051
1052    /// If false, `background_thread_cpu_priority` is ignored. If true, the
1053    /// priority of the copy threads can be lowered. Raising it has no effect,
1054    /// since the threads start at [`CpuPriority::KNormal`].
1055    ///
1056    /// Default: false
1057    pub fn set_decrease_background_thread_cpu_priority(&mut self, val: bool) {
1058        unsafe {
1059            ffi::rocksdb_create_backup_options_set_decrease_background_thread_cpu_priority(
1060                self.inner,
1061                c_uchar::from(val),
1062            );
1063        }
1064    }
1065
1066    /// Returns the current `decrease_background_thread_cpu_priority` setting.
1067    pub fn get_decrease_background_thread_cpu_priority(&self) -> bool {
1068        unsafe {
1069            ffi::rocksdb_create_backup_options_get_decrease_background_thread_cpu_priority(
1070                self.inner,
1071            ) != 0
1072        }
1073    }
1074
1075    /// CPU priority for the threads that copy files during the backup. Only
1076    /// used when [`Self::set_decrease_background_thread_cpu_priority`] is true.
1077    ///
1078    /// Default: [`CpuPriority::KNormal`]
1079    pub fn set_background_thread_cpu_priority(&mut self, val: CpuPriority) {
1080        unsafe {
1081            ffi::rocksdb_create_backup_options_set_background_thread_cpu_priority(
1082                self.inner,
1083                val as c_int,
1084            );
1085        }
1086    }
1087
1088    /// Returns the current `background_thread_cpu_priority` setting.
1089    pub fn get_background_thread_cpu_priority(&self) -> CpuPriority {
1090        unsafe {
1091            CpuPriority::from(
1092                ffi::rocksdb_create_backup_options_get_background_thread_cpu_priority(self.inner),
1093            )
1094        }
1095    }
1096
1097    /// Registers a closure that RocksDB calls every
1098    /// `BackupEngineOptions::callback_trigger_interval_size` bytes copied.
1099    ///
1100    /// The closure is called from the copy threads, not the thread that
1101    /// started the backup. RocksDB serialises the calls behind a mutex, but
1102    /// every copy thread holds a copy of the callback at the same time, so it
1103    /// must be `Send + Sync`. `'static` is required because these options
1104    /// store the closure without borrowing from the caller.
1105    ///
1106    /// Calling this again replaces and frees the previously registered
1107    /// closure. See the type level docs for why this type owns it.
1108    ///
1109    /// A panic out of the closure aborts the process, because a Rust panic
1110    /// cannot unwind through the C boundary.
1111    pub fn set_progress_callback<F>(&mut self, callback: F)
1112    where
1113        F: Fn() + Send + Sync + 'static,
1114    {
1115        let holder = Arc::new(ProgressCallback {
1116            callback: Box::new(callback),
1117        });
1118        let state = std::ptr::from_ref::<ProgressCallback>(holder.as_ref())
1119            .cast::<c_void>()
1120            .cast_mut();
1121        unsafe {
1122            ffi::rocksdb_create_backup_options_set_progress_callback(
1123                self.inner,
1124                state,
1125                Some(progress_callback_trampoline),
1126            );
1127        }
1128        // The C side now points at the new holder, so dropping the old one
1129        // here cannot leave it with a dangling state pointer.
1130        self.progress_callback = Some(holder);
1131    }
1132
1133    /// Registers a closure that decides which shared files to leave out of the
1134    /// backup. It is called once per candidate file with the file name relative
1135    /// to the backup directory, and returning true excludes that file.
1136    ///
1137    /// This is an advanced feature. RocksDB trusts the caller to keep the
1138    /// excluded files somewhere the database can still be restored from, for
1139    /// example an alternate backup directory. Restoring such a backup needs
1140    /// those files supplied through `RestoreOptions::alternate_dirs`, which the
1141    /// C API does not expose, so from this crate the only recovery path is
1142    /// [`RestoreMode::KKeepLatestDbSessionIdFiles`], which opportunistically
1143    /// looks for the missing files in the existing database directory.
1144    ///
1145    /// Only shared files are ever offered to the closure, so this needs
1146    /// `share_table_files` and `share_files_with_checksum` to be true. It also
1147    /// needs `schema_version` to be at least 2, otherwise creating the backup
1148    /// fails with `Invalid argument: exclude_files_callback requires
1149    /// schema_version >= 2`.
1150    ///
1151    /// `db/c.cc` calls this on the thread driving the backup, but the bound
1152    /// matches [`Self::set_progress_callback`] so that both closures have the
1153    /// same requirements.
1154    ///
1155    /// Calling this again replaces and frees the previously registered
1156    /// closure. A panic out of the closure aborts the process.
1157    pub fn set_exclude_files_callback<F>(&mut self, callback: F)
1158    where
1159        F: Fn(&[u8]) -> bool + Send + Sync + 'static,
1160    {
1161        let holder = Arc::new(ExcludeFilesCallback {
1162            callback: Box::new(callback),
1163        });
1164        let state = std::ptr::from_ref::<ExcludeFilesCallback>(holder.as_ref())
1165            .cast::<c_void>()
1166            .cast_mut();
1167        unsafe {
1168            ffi::rocksdb_create_backup_options_set_exclude_files_callback(
1169                self.inner,
1170                state,
1171                Some(exclude_files_callback_trampoline),
1172            );
1173        }
1174        // See `set_progress_callback`.
1175        self.exclude_files_callback = Some(holder);
1176    }
1177}
1178
1179impl Default for CreateBackupOptions {
1180    fn default() -> Self {
1181        unsafe {
1182            let opts = ffi::rocksdb_create_backup_options_create();
1183            assert!(
1184                !opts.is_null(),
1185                "Could not create RocksDB create backup options"
1186            );
1187
1188            Self {
1189                inner: opts,
1190                progress_callback: None,
1191                exclude_files_callback: None,
1192            }
1193        }
1194    }
1195}
1196
1197impl RestoreOptions {
1198    /// Sets `keep_log_files`. If true, restore won't overwrite the existing log files in wal_dir.
1199    /// It will also move all log files from archive directory to wal_dir. Use this option in
1200    /// combination with BackupEngineOptions::backup_log_files = false for persisting in-memory
1201    /// databases.
1202    ///
1203    /// Default: false
1204    pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
1205        unsafe {
1206            ffi::rocksdb_restore_options_set_keep_log_files(self.inner, i32::from(keep_log_files));
1207        }
1208    }
1209
1210    /// If true, restore won't overwrite the existing log files in wal_dir. It will also move
1211    /// all log files from archive directory to wal_dir. Use this option in combination with
1212    /// BackupEngineOptions::backup_log_files = false for persisting in-memory databases.
1213    /// Default: false
1214    pub fn get_keep_log_files(&self) -> bool {
1215        unsafe { ffi::rocksdb_restore_options_get_keep_log_files(self.inner) != 0 }
1216    }
1217
1218    /// Sets how much of the destination database the restore may reuse.
1219    ///
1220    /// Default: [`RestoreMode::KPurgeAllFiles`]
1221    pub fn set_mode(&mut self, mode: RestoreMode) {
1222        unsafe {
1223            ffi::rocksdb_restore_options_set_mode(self.inner, mode as c_int);
1224        }
1225    }
1226
1227    /// Returns the current restore mode.
1228    pub fn get_mode(&self) -> RestoreMode {
1229        unsafe { RestoreMode::from(ffi::rocksdb_restore_options_get_mode(self.inner)) }
1230    }
1231}
1232
1233impl Default for RestoreOptions {
1234    fn default() -> Self {
1235        unsafe {
1236            let opts = ffi::rocksdb_restore_options_create();
1237            assert!(!opts.is_null(), "Could not create RocksDB restore options");
1238
1239            Self { inner: opts }
1240        }
1241    }
1242}
1243
1244impl Drop for BackupEngine {
1245    fn drop(&mut self) {
1246        unsafe {
1247            ffi::rocksdb_backup_engine_close(self.inner);
1248        }
1249    }
1250}
1251
1252impl Drop for BackupEngineOptions {
1253    fn drop(&mut self) {
1254        unsafe {
1255            ffi::rocksdb_backup_engine_options_destroy(self.inner);
1256        }
1257    }
1258}
1259
1260impl Drop for CreateBackupOptions {
1261    fn drop(&mut self) {
1262        unsafe {
1263            ffi::rocksdb_create_backup_options_destroy(self.inner);
1264        }
1265        // The closure holders are fields, so they are dropped after this body
1266        // runs, which is the order that matters: the C++ lambdas holding their
1267        // addresses are gone by then.
1268    }
1269}
1270
1271impl Drop for RestoreOptions {
1272    fn drop(&mut self) {
1273        unsafe {
1274            ffi::rocksdb_restore_options_destroy(self.inner);
1275        }
1276    }
1277}
1278
1279#[cfg(test)]
1280mod tests {
1281    use super::BackupEngineOptions;
1282
1283    #[test]
1284    fn test_sync() {
1285        let dir = tempfile::Builder::new()
1286            .prefix("rocksdb-test-sync")
1287            .tempdir()
1288            .expect("Failed to create temporary path for db.");
1289
1290        let mut opts = BackupEngineOptions::new(dir.path()).unwrap();
1291        assert!(opts.get_sync());
1292        opts.set_sync(false);
1293        assert!(!opts.get_sync());
1294    }
1295}