Skip to main content

idb/cli/
mod.rs

1//! CLI subcommand implementations for the `inno` binary.
2//!
3//! The `inno` binary provides twenty subcommands for analyzing InnoDB data files,
4//! redo logs, and system tablespaces. CLI argument parsing uses clap derive macros,
5//! with the top-level [`app::Cli`] struct and [`app::Commands`] enum defined in
6//! [`app`] and shared between `main.rs` and `build.rs` (for man page generation)
7//! via `include!()`.
8//!
9//! Each subcommand module follows the same pattern: an `Options` struct holding
10//! the parsed arguments and a `pub fn execute(opts, writer) -> Result<(), IdbError>`
11//! entry point. The `writer: &mut dyn Write` parameter allows output to be
12//! captured in tests or redirected to a file via the global `--output` flag.
13//!
14//! # Subcommands
15//!
16//! | Command | Module | Purpose |
17//! |---------|--------|---------|
18//! | `inno parse` | [`parse`] | Parse FIL headers for every page and show a page-type summary table |
19//! | `inno pages` | [`pages`] | Deep structure analysis of INDEX, UNDO, BLOB/LOB, and SDI pages |
20//! | `inno dump` | [`dump`] | Hex dump of raw bytes by page number or absolute file offset |
21//! | `inno checksum` | [`checksum`] | Validate CRC-32C and legacy InnoDB checksums for every page |
22//! | `inno diff` | [`diff`] | Compare two tablespace files page-by-page and report differences |
23//! | `inno watch` | [`watch`] | Monitor a tablespace file for page-level changes in real time |
24//! | `inno corrupt` | [`corrupt`] | Inject random bytes into a page for testing recovery workflows |
25//! | `inno recover` | [`recover`] | Assess page-level recoverability and count salvageable records |
26//! | `inno repair` | [`repair`] | Recalculate and fix corrupt page checksums |
27//! | `inno find` | [`find`] | Search a MySQL data directory for pages matching a page number |
28//! | `inno tsid` | [`tsid`] | List or look up tablespace (space) IDs across `.ibd`/`.ibu` files |
29//! | `inno sdi` | [`sdi`] | Extract SDI metadata (MySQL 8.0+ serialized data dictionary) |
30//! | `inno schema` | [`schema`] | Extract schema and reconstruct DDL from tablespace metadata |
31//! | `inno export` | [`export`] | Export record-level data from INDEX pages as CSV, JSON, or hex |
32//! | `inno health` | [`health`] | Per-index B+Tree health metrics (fill factor, fragmentation, garbage) |
33//! | `inno log` | [`log`] | Analyze redo log file headers, checkpoints, and data blocks |
34//! | `inno info` | [`info`] | Inspect `ibdata1`, compare LSNs, or query a live MySQL instance |
35//! | `inno defrag` | [`defrag`] | Defragment a tablespace by reclaiming free space and reordering pages |
36//! | `inno transplant` | [`transplant`] | Copy specific pages from a donor tablespace into a target |
37//! | `inno audit` | [`audit`] | Audit a data directory for integrity, health, or corrupt pages |
38//! | `inno completions` | — | Generate shell completion scripts for bash, zsh, fish, or powershell |
39//!
40//! # Common patterns
41//!
42//! - **`--json`** — Every subcommand supports structured JSON output via
43//!   `#[derive(Serialize)]` structs and `serde_json`.
44//! - **`--page-size`** — Override auto-detected page size (useful for non-standard
45//!   4K, 8K, 32K, or 64K tablespaces).
46//! - **`--verbose` / `-v`** — Show additional detail such as per-page checksum
47//!   status, FSEG internals, or MLOG record types.
48//! - **`--color`** (global) — Control colored terminal output (`auto`, `always`,
49//!   `never`).
50//! - **`--output` / `-o`** (global) — Redirect output to a file instead of stdout.
51//!
52//! Progress bars (via [`indicatif`]) are displayed for long-running operations
53//! in `parse`, `checksum`, `find`, and `audit`. The `wprintln!` and `wprint!` macros
54//! wrap `writeln!`/`write!` to convert `io::Error` into `IdbError`.
55
56pub mod app;
57pub mod audit;
58pub mod backup;
59pub mod binlog;
60pub mod checksum;
61pub mod compat;
62pub mod comply;
63pub mod corrupt;
64pub mod defrag;
65pub mod diff;
66pub mod dump;
67pub mod export;
68pub mod find;
69pub mod health;
70pub mod info;
71pub mod log;
72pub mod pages;
73pub mod parse;
74pub mod recover;
75pub mod repair;
76pub mod schema;
77pub mod sdi;
78pub mod simulate;
79pub mod timeline;
80pub mod transplant;
81pub mod tsid;
82pub mod undelete;
83pub mod undo;
84pub mod validate;
85pub mod verify;
86pub mod watch;
87
88/// Write a line to the given writer, converting io::Error to IdbError.
89macro_rules! wprintln {
90    ($w:expr) => {
91        writeln!($w).map_err(|e| $crate::IdbError::Io(e.to_string()))
92    };
93    ($w:expr, $($arg:tt)*) => {
94        writeln!($w, $($arg)*).map_err(|e| $crate::IdbError::Io(e.to_string()))
95    };
96}
97
98/// Write (without newline) to the given writer, converting io::Error to IdbError.
99macro_rules! wprint {
100    ($w:expr, $($arg:tt)*) => {
101        write!($w, $($arg)*).map_err(|e| $crate::IdbError::Io(e.to_string()))
102    };
103}
104
105pub(crate) use wprint;
106pub(crate) use wprintln;
107
108/// Escape a string value for CSV output.
109///
110/// If the value contains a comma, double-quote, or newline, it is enclosed
111/// in double-quotes with internal double-quotes doubled. Otherwise the value
112/// is returned as-is.
113pub(crate) fn csv_escape(val: &str) -> String {
114    if val.contains(',') || val.contains('"') || val.contains('\n') {
115        format!("\"{}\"", val.replace('"', "\"\""))
116    } else {
117        val.to_string()
118    }
119}
120
121use crate::innodb::decryption::DecryptionContext;
122use crate::innodb::keyring::Keyring;
123use crate::innodb::tablespace::Tablespace;
124use crate::IdbError;
125use indicatif::{ProgressBar, ProgressStyle};
126
127/// Open a tablespace file, selecting mmap or buffered I/O based on the flag.
128///
129/// When `use_mmap` is true, the file is memory-mapped via `mmap(2)` for
130/// potentially better performance on large files (especially with parallel
131/// processing). When `page_size` is `Some`, auto-detection is bypassed.
132pub(crate) fn open_tablespace(
133    path: &str,
134    page_size: Option<u32>,
135    use_mmap: bool,
136) -> Result<Tablespace, IdbError> {
137    match (use_mmap, page_size) {
138        (true, Some(ps)) => Tablespace::open_mmap_with_page_size(path, ps),
139        (true, None) => Tablespace::open_mmap(path),
140        (false, Some(ps)) => Tablespace::open_with_page_size(path, ps),
141        (false, None) => Tablespace::open(path),
142    }
143}
144
145/// Set up decryption on a tablespace if a keyring path is provided.
146///
147/// Loads the keyring file, reads the encryption info from page 0,
148/// decrypts the tablespace key, and installs the decryption context
149/// on the tablespace for transparent page decryption.
150pub(crate) fn setup_decryption(ts: &mut Tablespace, keyring_path: &str) -> Result<(), IdbError> {
151    let keyring = Keyring::load(keyring_path)?;
152    let enc_info = ts.encryption_info().ok_or_else(|| {
153        IdbError::Parse(
154            "Keyring provided but tablespace has no encryption info on page 0".to_string(),
155        )
156    })?;
157    let ctx = DecryptionContext::from_encryption_info(enc_info, &keyring)?;
158    ts.set_decryption_context(ctx);
159    Ok(())
160}
161
162/// Create a styled progress bar for iterating over pages or files.
163pub(crate) fn create_progress_bar(count: u64, unit: &str) -> ProgressBar {
164    let pb = ProgressBar::new(count);
165    pb.set_style(
166        ProgressStyle::default_bar()
167            .template(&format!(
168                "{{spinner:.green}} [{{bar:40.cyan/blue}}] {{pos}}/{{len}} {} ({{eta}})",
169                unit
170            ))
171            .unwrap()
172            .progress_chars("#>-"),
173    );
174    pb
175}