idb/cli/app.rs
1use clap::{Parser, Subcommand, ValueEnum};
2
3/// Top-level CLI definition for the `inno` binary.
4#[derive(Parser)]
5#[command(name = "inno")]
6#[command(about = "InnoDB file analysis toolkit")]
7#[command(version)]
8pub struct Cli {
9 /// Control colored output
10 #[arg(long, default_value = "auto", global = true)]
11 pub color: ColorMode,
12
13 /// Write output to a file instead of stdout
14 #[arg(short, long, global = true)]
15 pub output: Option<String>,
16
17 #[command(subcommand)]
18 pub command: Commands,
19}
20
21/// Controls when colored output is emitted.
22#[derive(Clone, Copy, ValueEnum)]
23pub enum ColorMode {
24 Auto,
25 Always,
26 Never,
27}
28
29/// Available subcommands for the `inno` CLI.
30#[derive(Subcommand)]
31pub enum Commands {
32 /// Parse .ibd file and display page summary
33 ///
34 /// Reads the 38-byte FIL header of every page in a tablespace, decodes the
35 /// page type, checksum, LSN, prev/next pointers, and space ID, then prints
36 /// a per-page breakdown followed by a page-type frequency summary table.
37 /// Page 0 additionally shows the FSP header (space ID, size, flags).
38 /// Use `--no-empty` to skip zero-checksum allocated pages, or `-p` to
39 /// inspect a single page in detail. With `--verbose`, checksum validation
40 /// and LSN consistency results are included for each page.
41 Parse {
42 /// Path to InnoDB data file (.ibd)
43 #[arg(short, long)]
44 file: String,
45
46 /// Display a specific page number
47 #[arg(short, long)]
48 page: Option<u64>,
49
50 /// Display additional information
51 #[arg(short, long)]
52 verbose: bool,
53
54 /// Skip empty/allocated pages
55 #[arg(short = 'e', long = "no-empty")]
56 no_empty: bool,
57
58 /// Output in JSON format
59 #[arg(long)]
60 json: bool,
61
62 /// Override page size (default: auto-detect)
63 #[arg(long = "page-size")]
64 page_size: Option<u32>,
65 },
66
67 /// Detailed page structure analysis
68 ///
69 /// Goes beyond FIL headers to decode the internal structure of each page
70 /// type: INDEX pages show the B+Tree index header, FSEG inode pointers, and
71 /// infimum/supremum system records; UNDO pages show the undo page header
72 /// and segment state; BLOB/LOB pages show chain pointers and data lengths;
73 /// and page 0 shows extended FSP header fields including compression and
74 /// encryption flags. Use `-l` for a compact one-line-per-page listing,
75 /// `-t INDEX` to filter by page type, or `-p` for a single page deep dive.
76 Pages {
77 /// Path to InnoDB data file (.ibd)
78 #[arg(short, long)]
79 file: String,
80
81 /// Display a specific page number
82 #[arg(short, long)]
83 page: Option<u64>,
84
85 /// Display additional information
86 #[arg(short, long)]
87 verbose: bool,
88
89 /// Show empty/allocated pages
90 #[arg(short = 'e', long = "show-empty")]
91 show_empty: bool,
92
93 /// Compact list mode (one line per page)
94 #[arg(short, long)]
95 list: bool,
96
97 /// Filter by page type (e.g., INDEX)
98 #[arg(short = 't', long = "type")]
99 filter_type: Option<String>,
100
101 /// Output in JSON format
102 #[arg(long)]
103 json: bool,
104
105 /// Override page size (default: auto-detect)
106 #[arg(long = "page-size")]
107 page_size: Option<u32>,
108 },
109
110 /// Hex dump of raw page bytes
111 ///
112 /// Operates in two modes: **page mode** (default) reads a full page by
113 /// number and produces a formatted hex dump with file-relative offsets;
114 /// **offset mode** (`--offset`) reads bytes at an arbitrary file position,
115 /// useful for inspecting structures that cross page boundaries. Use
116 /// `--length` to limit the number of bytes shown, or `--raw` to emit
117 /// unformatted binary bytes suitable for piping to other tools.
118 Dump {
119 /// Path to InnoDB data file
120 #[arg(short, long)]
121 file: String,
122
123 /// Page number to dump (default: 0)
124 #[arg(short, long)]
125 page: Option<u64>,
126
127 /// Absolute byte offset to start dumping (bypasses page mode)
128 #[arg(long)]
129 offset: Option<u64>,
130
131 /// Number of bytes to dump (default: page size or 256 for offset mode)
132 #[arg(short, long)]
133 length: Option<usize>,
134
135 /// Output raw binary bytes (no formatting)
136 #[arg(long)]
137 raw: bool,
138
139 /// Override page size (default: auto-detect)
140 #[arg(long = "page-size")]
141 page_size: Option<u32>,
142 },
143
144 /// Intentionally corrupt pages for testing
145 ///
146 /// Writes random bytes into a tablespace file to simulate data corruption.
147 /// Targets can be the FIL header (`-k`), the record data area (`-r`), or
148 /// an absolute byte offset (`--offset`). If no page is specified, one is
149 /// chosen at random. Use `--verify` to print before/after checksum
150 /// comparisons confirming the page is now invalid — useful for verifying
151 /// that `inno checksum` correctly detects the damage.
152 Corrupt {
153 /// Path to data file
154 #[arg(short, long)]
155 file: String,
156
157 /// Page number to corrupt (random if not specified)
158 #[arg(short, long)]
159 page: Option<u64>,
160
161 /// Number of bytes to corrupt
162 #[arg(short, long, default_value = "1")]
163 bytes: usize,
164
165 /// Corrupt the FIL header area
166 #[arg(short = 'k', long = "header")]
167 header: bool,
168
169 /// Corrupt the record data area
170 #[arg(short, long)]
171 records: bool,
172
173 /// Absolute byte offset to corrupt (bypasses page calculation)
174 #[arg(long)]
175 offset: Option<u64>,
176
177 /// Show before/after checksum comparison
178 #[arg(long)]
179 verify: bool,
180
181 /// Output in JSON format
182 #[arg(long)]
183 json: bool,
184
185 /// Override page size (default: auto-detect)
186 #[arg(long = "page-size")]
187 page_size: Option<u32>,
188 },
189
190 /// Search for pages across data directory
191 ///
192 /// Recursively discovers all `.ibd` files under a MySQL data directory,
193 /// opens each as a tablespace, and reads the FIL header of every page
194 /// looking for a matching `page_number` field. Optional `--checksum` and
195 /// `--space-id` filters narrow results when the same page number appears
196 /// in multiple tablespaces. Use `--first` to stop after the first match
197 /// for faster lookups.
198 Find {
199 /// MySQL data directory path
200 #[arg(short, long)]
201 datadir: String,
202
203 /// Page number to search for
204 #[arg(short, long)]
205 page: u64,
206
207 /// Checksum to match
208 #[arg(short, long)]
209 checksum: Option<u32>,
210
211 /// Space ID to match
212 #[arg(short, long)]
213 space_id: Option<u32>,
214
215 /// Stop at first match
216 #[arg(long)]
217 first: bool,
218
219 /// Output in JSON format
220 #[arg(long)]
221 json: bool,
222
223 /// Override page size (default: auto-detect)
224 #[arg(long = "page-size")]
225 page_size: Option<u32>,
226 },
227
228 /// List/find tablespace IDs
229 ///
230 /// Scans `.ibd` and `.ibu` files under a MySQL data directory and reads
231 /// the space ID from the FSP header (page 0, offset 38) of each file.
232 /// In **list mode** (`-l`) it prints every file and its space ID; in
233 /// **lookup mode** (`-t <id>`) it finds the file that owns a specific
234 /// tablespace ID. Useful for mapping a space ID seen in error logs or
235 /// `INFORMATION_SCHEMA` back to a physical file on disk.
236 Tsid {
237 /// MySQL data directory path
238 #[arg(short, long)]
239 datadir: String,
240
241 /// List all tablespace IDs
242 #[arg(short, long)]
243 list: bool,
244
245 /// Find table file by tablespace ID
246 #[arg(short = 't', long = "tsid")]
247 tablespace_id: Option<u32>,
248
249 /// Output in JSON format
250 #[arg(long)]
251 json: bool,
252
253 /// Override page size (default: auto-detect)
254 #[arg(long = "page-size")]
255 page_size: Option<u32>,
256 },
257
258 /// Extract SDI metadata (MySQL 8.0+)
259 ///
260 /// Locates SDI (Serialized Dictionary Information) pages in a tablespace
261 /// by scanning for page type 17853, then reassembles multi-page SDI
262 /// records by following the page chain. The zlib-compressed payload is
263 /// decompressed and printed as JSON. Each tablespace in MySQL 8.0+
264 /// embeds its own table/column/index definitions as SDI records,
265 /// eliminating the need for the `.frm` files used in older versions.
266 /// Use `--pretty` for indented JSON output.
267 Sdi {
268 /// Path to InnoDB data file (.ibd)
269 #[arg(short, long)]
270 file: String,
271
272 /// Pretty-print JSON output
273 #[arg(short, long)]
274 pretty: bool,
275
276 /// Override page size (default: auto-detect)
277 #[arg(long = "page-size")]
278 page_size: Option<u32>,
279 },
280
281 /// Analyze InnoDB redo log files
282 ///
283 /// Opens an InnoDB redo log file (`ib_logfile0`/`ib_logfile1` for
284 /// MySQL < 8.0.30, or `#ib_redo*` files for 8.0.30+) and displays
285 /// the log file header, both checkpoint records, and per-block details
286 /// including block number, data length, checkpoint number, and CRC-32C
287 /// checksum status. With `--verbose`, MLOG record types within each
288 /// data block are decoded and summarized. Use `--blocks N` to limit
289 /// output to the first N data blocks, or `--no-empty` to skip blocks
290 /// that contain no redo data.
291 Log {
292 /// Path to redo log file (ib_logfile0, ib_logfile1, or #ib_redo*)
293 #[arg(short, long)]
294 file: String,
295
296 /// Limit to first N data blocks
297 #[arg(short, long)]
298 blocks: Option<u64>,
299
300 /// Skip empty blocks
301 #[arg(long)]
302 no_empty: bool,
303
304 /// Display additional information
305 #[arg(short, long)]
306 verbose: bool,
307
308 /// Output in JSON format
309 #[arg(long)]
310 json: bool,
311 },
312
313 /// Show InnoDB file and system information
314 ///
315 /// Operates in three modes. **`--ibdata`** reads the `ibdata1` page 0
316 /// FIL header and redo log checkpoint LSNs. **`--lsn-check`** compares
317 /// the `ibdata1` header LSN with the latest redo log checkpoint LSN to
318 /// detect whether the system tablespace and redo log are in sync (useful
319 /// for diagnosing crash-recovery state). **`-D`/`-t`** queries a live
320 /// MySQL instance via `INFORMATION_SCHEMA.INNODB_TABLES` and
321 /// `INNODB_INDEXES` for tablespace IDs, table IDs, index root pages,
322 /// and key InnoDB status metrics (requires the `mysql` feature).
323 Info {
324 /// Inspect ibdata1 page 0 header
325 #[arg(long)]
326 ibdata: bool,
327
328 /// Compare ibdata1 and redo log LSNs
329 #[arg(long = "lsn-check")]
330 lsn_check: bool,
331
332 /// MySQL data directory path
333 #[arg(short, long)]
334 datadir: Option<String>,
335
336 /// Database name (for table/index info)
337 #[arg(short = 'D', long)]
338 database: Option<String>,
339
340 /// Table name (for table/index info)
341 #[arg(short, long)]
342 table: Option<String>,
343
344 /// MySQL host
345 #[arg(long)]
346 host: Option<String>,
347
348 /// MySQL port
349 #[arg(long)]
350 port: Option<u16>,
351
352 /// MySQL user
353 #[arg(long)]
354 user: Option<String>,
355
356 /// MySQL password
357 #[arg(long)]
358 password: Option<String>,
359
360 /// Path to MySQL defaults file (.my.cnf)
361 #[arg(long = "defaults-file")]
362 defaults_file: Option<String>,
363
364 /// Output in JSON format
365 #[arg(long)]
366 json: bool,
367
368 /// Override page size (default: auto-detect)
369 #[arg(long = "page-size")]
370 page_size: Option<u32>,
371 },
372
373 /// Recover data from corrupt/damaged tablespace files
374 ///
375 /// Scans a tablespace file and classifies each page as intact, corrupt,
376 /// empty, or unreadable. For INDEX pages, counts recoverable user records
377 /// by walking the compact record chain. Produces a recovery assessment
378 /// showing how many pages and records can be salvaged.
379 ///
380 /// Use `--force` to also extract records from pages with bad checksums
381 /// but valid-looking headers — useful when data is partially damaged
382 /// but the record chain is still intact. Use `--page-size` to override
383 /// page size detection when page 0 is corrupt.
384 ///
385 /// With `--verbose`, per-page details are shown including page type,
386 /// status, LSN, and record count. With `--json`, a structured report
387 /// is emitted including optional per-record detail when combined with
388 /// `--verbose`.
389 Recover {
390 /// Path to InnoDB data file (.ibd)
391 #[arg(short, long)]
392 file: String,
393
394 /// Analyze a single page instead of full scan
395 #[arg(short, long)]
396 page: Option<u64>,
397
398 /// Show per-page details
399 #[arg(short, long)]
400 verbose: bool,
401
402 /// Output in JSON format
403 #[arg(long)]
404 json: bool,
405
406 /// Extract records from corrupt pages with valid headers
407 #[arg(long)]
408 force: bool,
409
410 /// Override page size (critical when page 0 is corrupt)
411 #[arg(long = "page-size")]
412 page_size: Option<u32>,
413 },
414
415 /// Validate page checksums
416 ///
417 /// Reads every page in a tablespace and validates its stored checksum
418 /// against both CRC-32C (MySQL 5.7.7+) and legacy InnoDB algorithms.
419 /// Also checks that the header LSN low-32 bits match the FIL trailer.
420 /// All-zero pages are counted as empty and skipped. With `--verbose`,
421 /// per-page results are printed including the detected algorithm and
422 /// stored vs. calculated values. Exits with code 1 if any page has an
423 /// invalid checksum, making it suitable for use in scripts and CI.
424 Checksum {
425 /// Path to InnoDB data file (.ibd)
426 #[arg(short, long)]
427 file: String,
428
429 /// Show per-page checksum details
430 #[arg(short, long)]
431 verbose: bool,
432
433 /// Output in JSON format
434 #[arg(long)]
435 json: bool,
436
437 /// Override page size (default: auto-detect)
438 #[arg(long = "page-size")]
439 page_size: Option<u32>,
440 },
441}