1use std::io::Write;
9
10use colored::Colorize;
11use serde::Serialize;
12
13use crate::cli::wprintln;
14use crate::innodb::page::FilHeader;
15use crate::innodb::verify::{
16 extract_chain_file_info, verify_backup_chain, verify_tablespace, ChainReport, VerifyConfig,
17};
18use crate::IdbError;
19
20pub struct VerifyOptions {
22 pub file: String,
24 pub verbose: bool,
26 pub json: bool,
28 pub page_size: Option<u32>,
30 pub keyring: Option<String>,
32 pub mmap: bool,
34 pub redo: Option<String>,
36 pub chain: Vec<String>,
38}
39
40#[derive(Debug, Serialize)]
42struct FullVerifyReport {
43 #[serde(flatten)]
44 structural: crate::innodb::verify::VerifyReport,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 redo: Option<crate::innodb::verify::RedoVerifyResult>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 chain: Option<ChainReport>,
49}
50
51pub fn execute(opts: &VerifyOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
53 if !opts.chain.is_empty() {
55 return execute_chain(opts, writer);
56 }
57
58 let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
59
60 if let Some(ref keyring_path) = opts.keyring {
61 crate::cli::setup_decryption(&mut ts, keyring_path)?;
62 }
63
64 let page_size = ts.page_size();
65 let page_count = ts.page_count();
66
67 let mut all_pages = Vec::with_capacity(page_size as usize * page_count as usize);
69 for i in 0..page_count {
70 let page = ts.read_page(i)?;
71 all_pages.extend_from_slice(&page);
72 }
73
74 let space_id = if all_pages.len() >= page_size as usize {
76 FilHeader::parse(&all_pages[..page_size as usize])
77 .map(|h| h.space_id)
78 .unwrap_or(0)
79 } else {
80 0
81 };
82
83 let config = VerifyConfig::default();
84 let report = verify_tablespace(&all_pages, page_size, space_id, &opts.file, &config);
85
86 let redo_result = if let Some(ref redo_path) = opts.redo {
88 Some(crate::innodb::verify::verify_redo_continuity(
89 redo_path, &all_pages, page_size,
90 )?)
91 } else {
92 None
93 };
94
95 let mut overall_passed = report.passed;
96 if let Some(ref redo) = redo_result {
97 if !redo.covers_tablespace {
98 overall_passed = false;
99 }
100 }
101
102 if opts.json {
103 let full = FullVerifyReport {
104 structural: report,
105 redo: redo_result,
106 chain: None,
107 };
108 let json = serde_json::to_string_pretty(&full)
109 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
110 wprintln!(writer, "{}", json)?;
111 } else {
112 wprintln!(writer, "Structural Verification: {}", opts.file)?;
114 wprintln!(writer, " Page size: {} bytes", report.page_size)?;
115 wprintln!(writer, " Total pages: {}", report.total_pages)?;
116 wprintln!(writer)?;
117
118 wprintln!(
120 writer,
121 " {:<30} {:>8} {:>8} {:>8}",
122 "Check",
123 "Checked",
124 "Issues",
125 "Status"
126 )?;
127 wprintln!(writer, " {}", "-".repeat(60))?;
128 for s in &report.summary {
129 let status = if s.passed {
130 "PASS".green().to_string()
131 } else {
132 "FAIL".red().to_string()
133 };
134 wprintln!(
135 writer,
136 " {:<30} {:>8} {:>8} {:>8}",
137 s.kind,
138 s.pages_checked,
139 s.issues_found,
140 status
141 )?;
142 }
143 wprintln!(writer)?;
144
145 if opts.verbose && !report.findings.is_empty() {
146 wprintln!(writer, " Findings:")?;
147 for f in &report.findings {
148 wprintln!(writer, " Page {:>4}: {}", f.page_number, f.message)?;
149 }
150 wprintln!(writer)?;
151 }
152
153 if let Some(ref redo) = redo_result {
155 wprintln!(writer, " Redo Log Continuity:")?;
156 wprintln!(writer, " Redo file: {}", redo.redo_file)?;
157 wprintln!(writer, " Checkpoint LSN: {}", redo.checkpoint_lsn)?;
158 wprintln!(writer, " Tablespace max: {}", redo.tablespace_max_lsn)?;
159 let redo_status = if redo.covers_tablespace {
160 "PASS".green().to_string()
161 } else {
162 format!("{} (gap: {} bytes)", "FAIL".red(), redo.lsn_gap)
163 };
164 wprintln!(writer, " Covers tablespace: {}", redo_status)?;
165 wprintln!(writer)?;
166 }
167
168 let overall = if overall_passed {
169 "PASS".green().to_string()
170 } else {
171 "FAIL".red().to_string()
172 };
173 wprintln!(writer, " Overall: {}", overall)?;
174 }
175
176 if !overall_passed {
177 return Err(IdbError::Argument("Verification failed".to_string()));
178 }
179
180 Ok(())
181}
182
183fn execute_chain(opts: &VerifyOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
185 if opts.chain.len() < 2 {
186 return Err(IdbError::Argument(
187 "--chain requires at least 2 files".to_string(),
188 ));
189 }
190
191 let mut files_info = Vec::new();
192
193 for path in &opts.chain {
194 let mut ts = crate::cli::open_tablespace(path, opts.page_size, opts.mmap)?;
195 if let Some(ref keyring_path) = opts.keyring {
196 crate::cli::setup_decryption(&mut ts, keyring_path)?;
197 }
198
199 let page_size = ts.page_size();
200 let page_count = ts.page_count();
201 let mut all_pages = Vec::with_capacity(page_size as usize * page_count as usize);
202 for i in 0..page_count {
203 let page = ts.read_page(i)?;
204 all_pages.extend_from_slice(&page);
205 }
206
207 files_info.push(extract_chain_file_info(&all_pages, page_size, path));
208 }
209
210 let chain_report = verify_backup_chain(files_info);
211
212 if opts.json {
213 let json = serde_json::to_string_pretty(&chain_report)
214 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
215 wprintln!(writer, "{}", json)?;
216 } else {
217 wprintln!(writer, "Backup Chain Verification")?;
218 wprintln!(writer)?;
219
220 wprintln!(
221 writer,
222 " {:<40} {:>12} {:>16} {:>16}",
223 "File",
224 "Space ID",
225 "Min LSN",
226 "Max LSN"
227 )?;
228 wprintln!(writer, " {}", "-".repeat(88))?;
229 for f in &chain_report.files {
230 wprintln!(
231 writer,
232 " {:<40} {:>12} {:>16} {:>16}",
233 f.file,
234 f.space_id,
235 f.min_lsn,
236 f.max_lsn
237 )?;
238 }
239 wprintln!(writer)?;
240
241 if !chain_report.gaps.is_empty() {
242 wprintln!(writer, " {} detected:", "Gaps".red())?;
243 for gap in &chain_report.gaps {
244 wprintln!(
245 writer,
246 " {} (max LSN {}) -> {} (min LSN {}): gap of {} bytes",
247 gap.from_file,
248 gap.from_max_lsn,
249 gap.to_file,
250 gap.to_min_lsn,
251 gap.gap_size
252 )?;
253 }
254 wprintln!(writer)?;
255 }
256
257 let space_status = if chain_report.consistent_space_id {
258 "PASS".green().to_string()
259 } else {
260 "FAIL (mixed space IDs)".red().to_string()
261 };
262 wprintln!(writer, " Space ID consistency: {}", space_status)?;
263
264 let chain_status = if chain_report.contiguous {
265 "PASS".green().to_string()
266 } else {
267 "FAIL".red().to_string()
268 };
269 wprintln!(writer, " Chain continuity: {}", chain_status)?;
270 }
271
272 if !chain_report.contiguous || !chain_report.consistent_space_id {
273 return Err(IdbError::Argument("Chain verification failed".to_string()));
274 }
275
276 Ok(())
277}