Skip to main content

idb/cli/
dump.rs

1use std::fs::File;
2use std::io::{Read, Seek, SeekFrom, Write};
3
4use crate::cli::wprintln;
5use crate::util::hex::hex_dump;
6use crate::IdbError;
7
8/// Options for the `inno dump` subcommand.
9pub struct DumpOptions {
10    /// Path to the InnoDB tablespace file (.ibd).
11    pub file: String,
12    /// Page number to dump (defaults to page 0 when not specified).
13    pub page: Option<u64>,
14    /// Absolute byte offset to start dumping (bypasses page mode).
15    pub offset: Option<u64>,
16    /// Number of bytes to dump (defaults to page size in page mode, or 256 in offset mode).
17    pub length: Option<usize>,
18    /// Output raw binary bytes instead of formatted hex dump.
19    pub raw: bool,
20    /// Override the auto-detected page size.
21    pub page_size: Option<u32>,
22    /// Path to MySQL keyring file for decrypting encrypted tablespaces.
23    pub keyring: Option<String>,
24    /// Decrypt page before dumping (requires --keyring).
25    pub decrypt: bool,
26    /// Use memory-mapped I/O for file access.
27    pub mmap: bool,
28}
29
30/// Produce a hex dump of raw bytes from an InnoDB tablespace file.
31///
32/// Operates in two modes:
33///
34/// - **Page mode** (default): Opens the file as a tablespace, reads the page
35///   specified by `-p` (or page 0 if omitted), and prints a formatted hex dump
36///   with file-relative byte offsets. The dump length defaults to the full page
37///   size but can be shortened with `--length`.
38/// - **Offset mode** (`--offset`): Reads bytes starting at an arbitrary
39///   absolute file position without page-size awareness. The default read
40///   length is 256 bytes. This is useful for inspecting raw structures that
41///   do not align to page boundaries (e.g., redo log headers, doublewrite
42///   buffer regions).
43///
44/// In either mode, `--raw` suppresses the formatted hex layout and writes
45/// the raw binary bytes directly to the writer, suitable for piping into
46/// `xxd`, `hexdump`, or other tools.
47pub fn execute(opts: &DumpOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
48    if let Some(abs_offset) = opts.offset {
49        // Absolute offset mode: dump raw bytes from file position
50        return dump_at_offset(
51            &opts.file,
52            abs_offset,
53            opts.length.unwrap_or(256),
54            opts.raw,
55            writer,
56        );
57    }
58
59    // Page mode: dump a specific page (or page 0 by default)
60    let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
61
62    if opts.decrypt {
63        if let Some(ref keyring_path) = opts.keyring {
64            crate::cli::setup_decryption(&mut ts, keyring_path)?;
65        } else {
66            return Err(IdbError::Argument(
67                "--decrypt requires --keyring <path>".to_string(),
68            ));
69        }
70    }
71
72    let page_size = ts.page_size();
73    let page_num = opts.page.unwrap_or(0);
74    let page_data = ts.read_page(page_num)?;
75
76    let length = opts.length.unwrap_or(page_size as usize);
77    let dump_len = length.min(page_data.len());
78    let base_offset = page_num * page_size as u64;
79
80    if opts.raw {
81        writer
82            .write_all(&page_data[..dump_len])
83            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
84    } else {
85        wprintln!(
86            writer,
87            "Hex dump of {} page {} ({} bytes):",
88            opts.file,
89            page_num,
90            dump_len
91        )?;
92        wprintln!(writer)?;
93        wprintln!(writer, "{}", hex_dump(&page_data[..dump_len], base_offset))?;
94    }
95
96    Ok(())
97}
98
99fn dump_at_offset(
100    file: &str,
101    offset: u64,
102    length: usize,
103    raw: bool,
104    writer: &mut dyn Write,
105) -> Result<(), IdbError> {
106    let mut f =
107        File::open(file).map_err(|e| IdbError::Io(format!("Cannot open {}: {}", file, e)))?;
108
109    let file_size = f
110        .metadata()
111        .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", file, e)))?
112        .len();
113
114    if offset >= file_size {
115        return Err(IdbError::Argument(format!(
116            "Offset {} is beyond file size {}",
117            offset, file_size
118        )));
119    }
120
121    let available = (file_size - offset) as usize;
122    let read_len = length.min(available);
123
124    f.seek(SeekFrom::Start(offset))
125        .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
126
127    let mut buf = vec![0u8; read_len];
128    f.read_exact(&mut buf).map_err(|e| {
129        IdbError::Io(format!(
130            "Cannot read {} bytes at offset {}: {}",
131            read_len, offset, e
132        ))
133    })?;
134
135    if raw {
136        writer
137            .write_all(&buf)
138            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
139    } else {
140        wprintln!(
141            writer,
142            "Hex dump of {} at offset {} ({} bytes):",
143            file,
144            offset,
145            read_len
146        )?;
147        wprintln!(writer)?;
148        wprintln!(writer, "{}", hex_dump(&buf, offset))?;
149    }
150
151    Ok(())
152}