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