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