1use std::io::Write;
2
3use colored::Colorize;
4use serde::Serialize;
5
6use crate::cli::{create_progress_bar, wprintln};
7use crate::innodb::checksum::{validate_checksum, validate_lsn, ChecksumAlgorithm};
8use crate::innodb::page::FilHeader;
9use crate::innodb::tablespace::Tablespace;
10use crate::IdbError;
11
12pub struct ChecksumOptions {
14 pub file: String,
16 pub verbose: bool,
18 pub json: bool,
20 pub page_size: Option<u32>,
22 pub keyring: Option<String>,
24}
25
26#[derive(Serialize)]
27struct ChecksumSummaryJson {
28 file: String,
29 page_size: u32,
30 total_pages: u64,
31 empty_pages: u64,
32 valid_pages: u64,
33 invalid_pages: u64,
34 lsn_mismatches: u64,
35 #[serde(skip_serializing_if = "Vec::is_empty")]
36 pages: Vec<PageChecksumJson>,
37}
38
39#[derive(Serialize)]
40struct PageChecksumJson {
41 page_number: u64,
42 status: String,
43 algorithm: String,
44 stored_checksum: u32,
45 calculated_checksum: u32,
46 lsn_valid: bool,
47}
48
49pub fn execute(opts: &ChecksumOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
68 let mut ts = match opts.page_size {
69 Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
70 None => Tablespace::open(&opts.file)?,
71 };
72
73 if let Some(ref keyring_path) = opts.keyring {
74 crate::cli::setup_decryption(&mut ts, keyring_path)?;
75 }
76
77 let page_size = ts.page_size();
78 let page_count = ts.page_count();
79
80 if opts.json {
81 return execute_json(opts, &mut ts, page_size, page_count, writer);
82 }
83
84 wprintln!(
85 writer,
86 "Validating checksums for {} ({} pages, page size {})...",
87 opts.file,
88 page_count,
89 page_size
90 )?;
91 wprintln!(writer)?;
92
93 let mut valid_count = 0u64;
94 let mut invalid_count = 0u64;
95 let mut empty_count = 0u64;
96 let mut lsn_mismatch_count = 0u64;
97
98 let pb = create_progress_bar(page_count, "pages");
99
100 for page_num in 0..page_count {
101 pb.inc(1);
102 let page_data = ts.read_page(page_num)?;
103
104 let header = match FilHeader::parse(&page_data) {
105 Some(h) => h,
106 None => {
107 eprintln!("Page {}: Could not parse FIL header", page_num);
108 invalid_count += 1;
109 continue;
110 }
111 };
112
113 if header.checksum == 0 && page_data.iter().all(|&b| b == 0) {
115 empty_count += 1;
116 if opts.verbose {
117 wprintln!(writer, "Page {}: EMPTY", page_num)?;
118 }
119 continue;
120 }
121
122 let csum_result = validate_checksum(&page_data, page_size, Some(ts.vendor_info()));
123 let lsn_valid = validate_lsn(&page_data, page_size);
124
125 if csum_result.valid {
126 valid_count += 1;
127 if opts.verbose {
128 wprintln!(
129 writer,
130 "Page {}: {} ({:?}, stored={}, calculated={})",
131 page_num,
132 "OK".green(),
133 csum_result.algorithm,
134 csum_result.stored_checksum,
135 csum_result.calculated_checksum,
136 )?;
137 }
138 } else {
139 invalid_count += 1;
140 wprintln!(
141 writer,
142 "Page {}: {} checksum (stored={}, calculated={}, algorithm={:?})",
143 page_num,
144 "INVALID".red(),
145 csum_result.stored_checksum,
146 csum_result.calculated_checksum,
147 csum_result.algorithm,
148 )?;
149 }
150
151 if !lsn_valid {
153 lsn_mismatch_count += 1;
154 if csum_result.valid {
155 wprintln!(
156 writer,
157 "Page {}: {} - header LSN low32 does not match trailer",
158 page_num,
159 "LSN MISMATCH".yellow(),
160 )?;
161 }
162 }
163 }
164
165 pb.finish_and_clear();
166
167 wprintln!(writer)?;
168 wprintln!(writer, "Summary:")?;
169 wprintln!(writer, " Total pages: {}", page_count)?;
170 wprintln!(writer, " Empty pages: {}", empty_count)?;
171 wprintln!(writer, " Valid checksums: {}", valid_count)?;
172 if invalid_count > 0 {
173 wprintln!(
174 writer,
175 " Invalid checksums: {}",
176 format!("{}", invalid_count).red()
177 )?;
178 } else {
179 wprintln!(
180 writer,
181 " Invalid checksums: {}",
182 format!("{}", invalid_count).green()
183 )?;
184 }
185 if lsn_mismatch_count > 0 {
186 wprintln!(
187 writer,
188 " LSN mismatches: {}",
189 format!("{}", lsn_mismatch_count).yellow()
190 )?;
191 }
192
193 if invalid_count > 0 {
194 return Err(IdbError::Parse(format!(
195 "{} pages with invalid checksums",
196 invalid_count
197 )));
198 }
199
200 Ok(())
201}
202
203fn execute_json(
204 opts: &ChecksumOptions,
205 ts: &mut Tablespace,
206 page_size: u32,
207 page_count: u64,
208 writer: &mut dyn Write,
209) -> Result<(), IdbError> {
210 let mut valid_count = 0u64;
211 let mut invalid_count = 0u64;
212 let mut empty_count = 0u64;
213 let mut lsn_mismatch_count = 0u64;
214 let mut pages = Vec::new();
215
216 for page_num in 0..page_count {
217 let page_data = ts.read_page(page_num)?;
218
219 let header = match FilHeader::parse(&page_data) {
220 Some(h) => h,
221 None => {
222 invalid_count += 1;
223 if opts.verbose {
224 pages.push(PageChecksumJson {
225 page_number: page_num,
226 status: "error".to_string(),
227 algorithm: "unknown".to_string(),
228 stored_checksum: 0,
229 calculated_checksum: 0,
230 lsn_valid: false,
231 });
232 }
233 continue;
234 }
235 };
236
237 if header.checksum == 0 && page_data.iter().all(|&b| b == 0) {
238 empty_count += 1;
239 continue;
240 }
241
242 let csum_result = validate_checksum(&page_data, page_size, Some(ts.vendor_info()));
243 let lsn_valid = validate_lsn(&page_data, page_size);
244
245 if csum_result.valid {
246 valid_count += 1;
247 } else {
248 invalid_count += 1;
249 }
250 if !lsn_valid {
251 lsn_mismatch_count += 1;
252 }
253
254 if opts.verbose || !csum_result.valid || !lsn_valid {
256 let algorithm_name = match csum_result.algorithm {
257 ChecksumAlgorithm::Crc32c => "crc32c",
258 ChecksumAlgorithm::InnoDB => "innodb",
259 ChecksumAlgorithm::MariaDbFullCrc32 => "mariadb_full_crc32",
260 ChecksumAlgorithm::None => "none",
261 };
262 pages.push(PageChecksumJson {
263 page_number: page_num,
264 status: if csum_result.valid {
265 "valid".to_string()
266 } else {
267 "invalid".to_string()
268 },
269 algorithm: algorithm_name.to_string(),
270 stored_checksum: csum_result.stored_checksum,
271 calculated_checksum: csum_result.calculated_checksum,
272 lsn_valid,
273 });
274 }
275 }
276
277 let summary = ChecksumSummaryJson {
278 file: opts.file.clone(),
279 page_size,
280 total_pages: page_count,
281 empty_pages: empty_count,
282 valid_pages: valid_count,
283 invalid_pages: invalid_count,
284 lsn_mismatches: lsn_mismatch_count,
285 pages,
286 };
287
288 let json = serde_json::to_string_pretty(&summary)
289 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
290 wprintln!(writer, "{}", json)?;
291
292 if invalid_count > 0 {
293 return Err(IdbError::Parse(format!(
294 "{} pages with invalid checksums",
295 invalid_count
296 )));
297 }
298
299 Ok(())
300}