1use crate::{is_database_empty, TableSet, Tables};
4use eyre::Context;
5use std::path::Path;
6use tracing::{info, warn};
7
8pub use crate::implementation::mdbx::*;
9use crate::version::{check_db_version_file, create_db_version_file, DatabaseVersionError};
10pub use reth_libmdbx::*;
11
12const ORPHAN_TABLES: &[&str] = &["AccountsTrieChangeSets", "StoragesTrieChangeSets"];
15
16fn warn_if_zfs(path: &Path) {
21 if matches!(is_zfs(path), Ok(true)) {
22 warn!(
23 target: "reth::db",
24 path = %path.display(),
25 "Database is on a ZFS filesystem. ZFS's copy-on-write behavior causes significant \
26 performance degradation with MDBX. Consider using ext4 or xfs instead."
27 );
28 }
29}
30
31#[cfg(target_os = "linux")]
33fn is_zfs(path: &Path) -> std::io::Result<bool> {
34 use std::{ffi::CString, os::unix::ffi::OsStrExt};
35
36 const ZFS_SUPER_MAGIC: i64 = 0x2fc12fc1;
38
39 let c_path = CString::new(path.as_os_str().as_bytes())
40 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
41
42 unsafe {
43 let mut stat: libc::statfs = std::mem::zeroed();
44 if libc::statfs(c_path.as_ptr(), &raw mut stat) == 0 {
45 Ok(stat.f_type == ZFS_SUPER_MAGIC)
46 } else {
47 Err(std::io::Error::last_os_error())
48 }
49 }
50}
51
52#[cfg(target_os = "macos")]
54fn is_zfs(path: &Path) -> std::io::Result<bool> {
55 use std::{ffi::CString, os::unix::ffi::OsStrExt};
56
57 let c_path = CString::new(path.as_os_str().as_bytes())
58 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
59
60 unsafe {
61 let mut stat: libc::statfs = std::mem::zeroed();
62 if libc::statfs(c_path.as_ptr(), &raw mut stat) == 0 {
63 let fstype = std::ffi::CStr::from_ptr(stat.f_fstypename.as_ptr());
64 Ok(fstype.to_bytes() == b"zfs")
65 } else {
66 Err(std::io::Error::last_os_error())
67 }
68 }
69}
70
71#[cfg(not(any(target_os = "linux", target_os = "macos")))]
73fn is_zfs(_path: &Path) -> std::io::Result<bool> {
74 Ok(false)
75}
76
77pub fn create_db<P: AsRef<Path>>(path: P, args: DatabaseArguments) -> eyre::Result<DatabaseEnv> {
80 let rpath = path.as_ref();
81 warn_if_zfs(rpath);
82
83 if is_database_empty(rpath) {
84 reth_fs_util::create_dir_all(rpath)
85 .wrap_err_with(|| format!("Could not create database directory {}", rpath.display()))?;
86 create_db_version_file(rpath)?;
87 } else {
88 match check_db_version_file(rpath) {
89 Ok(_) => (),
90 Err(DatabaseVersionError::MissingFile) => create_db_version_file(rpath)?,
91 Err(err) => return Err(err.into()),
92 }
93 }
94
95 Ok(DatabaseEnv::open(rpath, DatabaseEnvKind::RW, args)?)
96}
97
98pub fn init_db<P: AsRef<Path>>(path: P, args: DatabaseArguments) -> eyre::Result<DatabaseEnv> {
101 init_db_for::<P, Tables>(path, args)
102}
103
104pub fn init_db_for<P: AsRef<Path>, TS: TableSet>(
107 path: P,
108 args: DatabaseArguments,
109) -> eyre::Result<DatabaseEnv> {
110 let client_version = args.client_version().clone();
111 let mut db = create_db(path, args)?;
112 db.create_and_track_tables_for::<TS>()?;
113 db.record_client_version(client_version)?;
114 drop_orphan_tables(&db);
115 Ok(db)
116}
117
118fn drop_orphan_tables(db: &DatabaseEnv) {
120 for table_name in ORPHAN_TABLES {
121 match db.drop_orphan_table(table_name) {
122 Ok(true) => {
123 info!(target: "reth::db", table = %table_name, "Dropped orphaned database table");
124 }
125 Ok(false) => {}
126 Err(e) => {
127 tracing::warn!(
128 target: "reth::db",
129 table = %table_name,
130 %e,
131 "Failed to drop orphaned database table"
132 );
133 }
134 }
135 }
136}
137
138pub fn open_db_read_only(
140 path: impl AsRef<Path>,
141 args: DatabaseArguments,
142) -> eyre::Result<DatabaseEnv> {
143 let path = path.as_ref();
144 DatabaseEnv::open(path, DatabaseEnvKind::RO, args)
145 .with_context(|| format!("Could not open database at path: {}", path.display()))
146}
147
148pub fn open_db(path: impl AsRef<Path>, args: DatabaseArguments) -> eyre::Result<DatabaseEnv> {
151 fn open(path: &Path, args: DatabaseArguments) -> eyre::Result<DatabaseEnv> {
152 let client_version = args.client_version().clone();
153 let db = DatabaseEnv::open(path, DatabaseEnvKind::RW, args)
154 .with_context(|| format!("Could not open database at path: {}", path.display()))?;
155 db.record_client_version(client_version)?;
156 Ok(db)
157 }
158 open(path.as_ref(), args)
159}