Skip to main content

idb/cli/
checksum.rs

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        // Skip all-zero pages
83        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        // Check LSN consistency
121        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        // In verbose JSON mode, include all pages; otherwise only invalid
221        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}