1use std::collections::HashMap;
2use std::io::Write;
3
4use byteorder::{BigEndian, ByteOrder};
5use colored::Colorize;
6use indicatif::{ProgressBar, ProgressStyle};
7
8use crate::cli::{wprintln, wprint};
9use crate::innodb::checksum;
10use crate::innodb::page::{FilHeader, FspHeader};
11use crate::innodb::page_types::PageType;
12use crate::innodb::tablespace::Tablespace;
13use crate::util::hex::format_offset;
14use crate::IdbError;
15
16pub struct ParseOptions {
18 pub file: String,
19 pub page: Option<u64>,
20 pub verbose: bool,
21 pub no_empty: bool,
22 pub page_size: Option<u32>,
23 pub json: bool,
24}
25
26#[derive(serde::Serialize)]
28struct PageJson {
29 page_number: u64,
30 header: FilHeader,
31 page_type_name: String,
32 page_type_description: String,
33 byte_start: u64,
34 byte_end: u64,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 fsp_header: Option<crate::innodb::page::FspHeader>,
37}
38
39pub fn execute(opts: &ParseOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
41 let mut ts = match opts.page_size {
42 Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
43 None => Tablespace::open(&opts.file)?,
44 };
45
46 let page_size = ts.page_size();
47
48 if opts.json {
49 return execute_json(opts, &mut ts, page_size, writer);
50 }
51
52 if let Some(page_num) = opts.page {
53 let page_data = ts.read_page(page_num)?;
55 print_page_info(writer, &page_data, page_num, page_size, opts.verbose)?;
56 } else {
57 let page0 = ts.read_page(0)?;
60 if let Some(fsp) = FspHeader::parse(&page0) {
61 print_fsp_header(writer, &fsp)?;
62 wprintln!(writer)?;
63 }
64
65 wprintln!(
66 writer,
67 "Pages in {} ({} pages, page size {}):",
68 opts.file,
69 ts.page_count(),
70 page_size
71 )?;
72 wprintln!(writer, "{}", "-".repeat(50))?;
73
74 let mut type_counts: HashMap<PageType, u64> = HashMap::new();
75
76 let pb = ProgressBar::new(ts.page_count());
77 pb.set_style(
78 ProgressStyle::default_bar()
79 .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} pages ({eta})")
80 .unwrap()
81 .progress_chars("#>-"),
82 );
83
84 for page_num in 0..ts.page_count() {
85 pb.inc(1);
86 let page_data = ts.read_page(page_num)?;
87 let header = match FilHeader::parse(&page_data) {
88 Some(h) => h,
89 None => continue,
90 };
91
92 *type_counts.entry(header.page_type).or_insert(0) += 1;
93
94 if opts.no_empty && header.checksum == 0 && header.page_type == PageType::Allocated {
96 continue;
97 }
98
99 if header.checksum == 0 && page_num != 0 && !opts.verbose {
101 continue;
102 }
103
104 print_page_info(writer, &page_data, page_num, page_size, opts.verbose)?;
105 }
106
107 pb.finish_and_clear();
108
109 wprintln!(writer)?;
111 wprintln!(writer, "{}", "Page Type Summary".bold())?;
112 let mut sorted_types: Vec<_> = type_counts.iter().collect();
113 sorted_types.sort_by(|a, b| b.1.cmp(a.1));
114 for (pt, count) in sorted_types {
115 let label = if *count == 1 { "page" } else { "pages" };
116 wprintln!(writer, " {:20} {:>6} {}", pt.name(), count, label)?;
117 }
118 }
119
120 Ok(())
121}
122
123fn execute_json(
125 opts: &ParseOptions,
126 ts: &mut Tablespace,
127 page_size: u32,
128 writer: &mut dyn Write,
129) -> Result<(), IdbError> {
130 let mut pages = Vec::new();
131
132 let range: Box<dyn Iterator<Item = u64>> = if let Some(p) = opts.page {
133 Box::new(std::iter::once(p))
134 } else {
135 Box::new(0..ts.page_count())
136 };
137
138 for page_num in range {
139 let page_data = ts.read_page(page_num)?;
140 let header = match FilHeader::parse(&page_data) {
141 Some(h) => h,
142 None => continue,
143 };
144
145 if opts.no_empty && header.checksum == 0 && header.page_type == PageType::Allocated {
146 continue;
147 }
148
149 let pt = header.page_type;
150 let byte_start = page_num * page_size as u64;
151
152 let fsp_header = if page_num == 0 {
153 FspHeader::parse(&page_data)
154 } else {
155 None
156 };
157
158 pages.push(PageJson {
159 page_number: page_num,
160 page_type_name: pt.name().to_string(),
161 page_type_description: pt.description().to_string(),
162 byte_start,
163 byte_end: byte_start + page_size as u64,
164 header,
165 fsp_header,
166 });
167 }
168
169 wprintln!(
170 writer,
171 "{}",
172 serde_json::to_string_pretty(&pages).unwrap_or_else(|_| "[]".to_string())
173 )?;
174 Ok(())
175}
176
177fn print_page_info(writer: &mut dyn Write, page_data: &[u8], page_num: u64, page_size: u32, verbose: bool) -> Result<(), IdbError> {
179 let header = match FilHeader::parse(page_data) {
180 Some(h) => h,
181 None => {
182 eprintln!("Could not parse FIL header for page {}", page_num);
183 return Ok(());
184 }
185 };
186
187 let byte_start = page_num * page_size as u64;
188 let byte_end = byte_start + page_size as u64;
189
190 let pt = header.page_type;
191
192 wprintln!(writer, "Page: {}", header.page_number)?;
193 wprintln!(writer, "{}", "-".repeat(20))?;
194 wprintln!(writer, "{}", "HEADER".bold())?;
195 wprintln!(writer, "Byte Start: {}", format_offset(byte_start))?;
196 wprintln!(
197 writer,
198 "Page Type: {}\n-- {}: {} - {}",
199 pt.as_u16(),
200 pt.name(),
201 pt.description(),
202 pt.usage()
203 )?;
204
205 if verbose {
206 wprintln!(writer, "PAGE_N_HEAP (Amount of records in page): {}", read_page_n_heap(page_data))?;
207 }
208
209 wprint!(writer, "Prev Page: ")?;
210 if !header.has_prev() {
211 wprintln!(writer, "Not used.")?;
212 } else {
213 wprintln!(writer, "{}", header.prev_page)?;
214 }
215
216 wprint!(writer, "Next Page: ")?;
217 if !header.has_next() {
218 wprintln!(writer, "Not used.")?;
219 } else {
220 wprintln!(writer, "{}", header.next_page)?;
221 }
222
223 wprintln!(writer, "LSN: {}", header.lsn)?;
224 wprintln!(writer, "Space ID: {}", header.space_id)?;
225 wprintln!(writer, "Checksum: {}", header.checksum)?;
226
227 let csum_result = checksum::validate_checksum(page_data, page_size);
229 if verbose {
230 let status = if csum_result.valid {
231 "OK".green().to_string()
232 } else {
233 "MISMATCH".red().to_string()
234 };
235 wprintln!(
236 writer,
237 "Checksum Status: {} ({:?}, stored={}, calculated={})",
238 status, csum_result.algorithm, csum_result.stored_checksum, csum_result.calculated_checksum
239 )?;
240 }
241
242 wprintln!(writer)?;
243
244 let ps = page_size as usize;
246 if page_data.len() >= ps {
247 let trailer_offset = ps - 8;
248 if let Some(trailer) = crate::innodb::page::FilTrailer::parse(&page_data[trailer_offset..]) {
249 wprintln!(writer, "{}", "TRAILER".bold())?;
250 wprintln!(writer, "Old-style Checksum: {}", trailer.checksum)?;
251 wprintln!(writer, "Low 32 bits of LSN: {}", trailer.lsn_low32)?;
252 wprintln!(writer, "Byte End: {}", format_offset(byte_end))?;
253
254 if verbose {
256 let lsn_valid = checksum::validate_lsn(page_data, page_size);
257 let lsn_status = if lsn_valid {
258 "OK".green().to_string()
259 } else {
260 "MISMATCH".red().to_string()
261 };
262 wprintln!(writer, "LSN Consistency: {}", lsn_status)?;
263 }
264 }
265 }
266 wprintln!(writer, "{}", "-".repeat(20))?;
267 Ok(())
268}
269
270fn print_fsp_header(writer: &mut dyn Write, fsp: &FspHeader) -> Result<(), IdbError> {
272 wprintln!(writer, "{}", "-".repeat(20))?;
273 wprintln!(writer, "{}", "FSP_HDR - Filespace Header".bold())?;
274 wprintln!(writer, "{}", "-".repeat(20))?;
275 wprintln!(writer, "Space ID: {}", fsp.space_id)?;
276 wprintln!(writer, "Size (pages): {}", fsp.size)?;
277 wprintln!(writer, "Page Free Limit: {}", fsp.free_limit)?;
278 wprintln!(writer, "Flags: {}", fsp.flags)?;
279 Ok(())
280}
281
282fn read_page_n_heap(page_data: &[u8]) -> u16 {
284 let offset = crate::innodb::constants::FIL_PAGE_DATA + 4; if page_data.len() < offset + 2 {
286 return 0;
287 }
288 BigEndian::read_u16(&page_data[offset..])
289}