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