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