1use std::io::Write;
2
3use colored::Colorize;
4use serde::Serialize;
5
6use crate::cli::{wprintln, create_progress_bar};
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, page_count, page_size
82 )?;
83 wprintln!(writer)?;
84
85 let mut valid_count = 0u64;
86 let mut invalid_count = 0u64;
87 let mut empty_count = 0u64;
88 let mut lsn_mismatch_count = 0u64;
89
90 let pb = create_progress_bar(page_count, "pages");
91
92 for page_num in 0..page_count {
93 pb.inc(1);
94 let page_data = ts.read_page(page_num)?;
95
96 let header = match FilHeader::parse(&page_data) {
97 Some(h) => h,
98 None => {
99 eprintln!("Page {}: Could not parse FIL header", page_num);
100 invalid_count += 1;
101 continue;
102 }
103 };
104
105 if header.checksum == 0 && page_data.iter().all(|&b| b == 0) {
107 empty_count += 1;
108 if opts.verbose {
109 wprintln!(writer, "Page {}: EMPTY", page_num)?;
110 }
111 continue;
112 }
113
114 let csum_result = validate_checksum(&page_data, page_size);
115 let lsn_valid = validate_lsn(&page_data, page_size);
116
117 if csum_result.valid {
118 valid_count += 1;
119 if opts.verbose {
120 wprintln!(
121 writer,
122 "Page {}: {} ({:?}, stored={}, calculated={})",
123 page_num,
124 "OK".green(),
125 csum_result.algorithm,
126 csum_result.stored_checksum,
127 csum_result.calculated_checksum,
128 )?;
129 }
130 } else {
131 invalid_count += 1;
132 wprintln!(
133 writer,
134 "Page {}: {} checksum (stored={}, calculated={}, algorithm={:?})",
135 page_num,
136 "INVALID".red(),
137 csum_result.stored_checksum,
138 csum_result.calculated_checksum,
139 csum_result.algorithm,
140 )?;
141 }
142
143 if !lsn_valid {
145 lsn_mismatch_count += 1;
146 if csum_result.valid {
147 wprintln!(
148 writer,
149 "Page {}: {} - header LSN low32 does not match trailer",
150 page_num,
151 "LSN MISMATCH".yellow(),
152 )?;
153 }
154 }
155 }
156
157 pb.finish_and_clear();
158
159 wprintln!(writer)?;
160 wprintln!(writer, "Summary:")?;
161 wprintln!(writer, " Total pages: {}", page_count)?;
162 wprintln!(writer, " Empty pages: {}", empty_count)?;
163 wprintln!(writer, " Valid checksums: {}", valid_count)?;
164 if invalid_count > 0 {
165 wprintln!(
166 writer,
167 " Invalid checksums: {}",
168 format!("{}", invalid_count).red()
169 )?;
170 } else {
171 wprintln!(
172 writer,
173 " Invalid checksums: {}",
174 format!("{}", invalid_count).green()
175 )?;
176 }
177 if lsn_mismatch_count > 0 {
178 wprintln!(
179 writer,
180 " LSN mismatches: {}",
181 format!("{}", lsn_mismatch_count).yellow()
182 )?;
183 }
184
185 if invalid_count > 0 {
186 std::process::exit(1);
187 }
188
189 Ok(())
190}
191
192fn execute_json(
193 opts: &ChecksumOptions,
194 ts: &mut Tablespace,
195 page_size: u32,
196 page_count: u64,
197 writer: &mut dyn Write,
198) -> Result<(), IdbError> {
199 let mut valid_count = 0u64;
200 let mut invalid_count = 0u64;
201 let mut empty_count = 0u64;
202 let mut lsn_mismatch_count = 0u64;
203 let mut pages = Vec::new();
204
205 for page_num in 0..page_count {
206 let page_data = ts.read_page(page_num)?;
207
208 let header = match FilHeader::parse(&page_data) {
209 Some(h) => h,
210 None => {
211 invalid_count += 1;
212 if opts.verbose {
213 pages.push(PageChecksumJson {
214 page_number: page_num,
215 status: "error".to_string(),
216 algorithm: "unknown".to_string(),
217 stored_checksum: 0,
218 calculated_checksum: 0,
219 lsn_valid: false,
220 });
221 }
222 continue;
223 }
224 };
225
226 if header.checksum == 0 && page_data.iter().all(|&b| b == 0) {
227 empty_count += 1;
228 continue;
229 }
230
231 let csum_result = validate_checksum(&page_data, page_size);
232 let lsn_valid = validate_lsn(&page_data, page_size);
233
234 if csum_result.valid {
235 valid_count += 1;
236 } else {
237 invalid_count += 1;
238 }
239 if !lsn_valid {
240 lsn_mismatch_count += 1;
241 }
242
243 if opts.verbose || !csum_result.valid || !lsn_valid {
245 let algorithm_name = match csum_result.algorithm {
246 ChecksumAlgorithm::Crc32c => "crc32c",
247 ChecksumAlgorithm::InnoDB => "innodb",
248 ChecksumAlgorithm::None => "none",
249 };
250 pages.push(PageChecksumJson {
251 page_number: page_num,
252 status: if csum_result.valid {
253 "valid".to_string()
254 } else {
255 "invalid".to_string()
256 },
257 algorithm: algorithm_name.to_string(),
258 stored_checksum: csum_result.stored_checksum,
259 calculated_checksum: csum_result.calculated_checksum,
260 lsn_valid,
261 });
262 }
263 }
264
265 let summary = ChecksumSummaryJson {
266 file: opts.file.clone(),
267 page_size,
268 total_pages: page_count,
269 empty_pages: empty_count,
270 valid_pages: valid_count,
271 invalid_pages: invalid_count,
272 lsn_mismatches: lsn_mismatch_count,
273 pages,
274 };
275
276 let json = serde_json::to_string_pretty(&summary)
277 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
278 wprintln!(writer, "{}", json)?;
279
280 if invalid_count > 0 {
281 std::process::exit(1);
282 }
283
284 Ok(())
285}