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