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}
24
25/// Produce a hex dump of raw bytes from an InnoDB tablespace file.
26///
27/// Operates in two modes:
28///
29/// - **Page mode** (default): Opens the file as a tablespace, reads the page
30///   specified by `-p` (or page 0 if omitted), and prints a formatted hex dump
31///   with file-relative byte offsets. The dump length defaults to the full page
32///   size but can be shortened with `--length`.
33/// - **Offset mode** (`--offset`): Reads bytes starting at an arbitrary
34///   absolute file position without page-size awareness. The default read
35///   length is 256 bytes. This is useful for inspecting raw structures that
36///   do not align to page boundaries (e.g., redo log headers, doublewrite
37///   buffer regions).
38///
39/// In either mode, `--raw` suppresses the formatted hex layout and writes
40/// the raw binary bytes directly to the writer, suitable for piping into
41/// `xxd`, `hexdump`, or other tools.
42pub fn execute(opts: &DumpOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
43    if let Some(abs_offset) = opts.offset {
44        // Absolute offset mode: dump raw bytes from file position
45        return dump_at_offset(&opts.file, abs_offset, opts.length.unwrap_or(256), opts.raw, writer);
46    }
47
48    // Page mode: dump a specific page (or page 0 by default)
49    let mut ts = match opts.page_size {
50        Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
51        None => Tablespace::open(&opts.file)?,
52    };
53
54    let page_size = ts.page_size();
55    let page_num = opts.page.unwrap_or(0);
56    let page_data = ts.read_page(page_num)?;
57
58    let length = opts.length.unwrap_or(page_size as usize);
59    let dump_len = length.min(page_data.len());
60    let base_offset = page_num * page_size as u64;
61
62    if opts.raw {
63        writer
64            .write_all(&page_data[..dump_len])
65            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
66    } else {
67        wprintln!(
68            writer,
69            "Hex dump of {} page {} ({} bytes):",
70            opts.file, page_num, dump_len
71        )?;
72        wprintln!(writer)?;
73        wprintln!(writer, "{}", hex_dump(&page_data[..dump_len], base_offset))?;
74    }
75
76    Ok(())
77}
78
79fn dump_at_offset(file: &str, offset: u64, length: usize, raw: bool, writer: &mut dyn Write) -> Result<(), IdbError> {
80    let mut f = File::open(file)
81        .map_err(|e| IdbError::Io(format!("Cannot open {}: {}", file, e)))?;
82
83    let file_size = f
84        .metadata()
85        .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", file, e)))?
86        .len();
87
88    if offset >= file_size {
89        return Err(IdbError::Argument(format!(
90            "Offset {} is beyond file size {}",
91            offset, file_size
92        )));
93    }
94
95    let available = (file_size - offset) as usize;
96    let read_len = length.min(available);
97
98    f.seek(SeekFrom::Start(offset))
99        .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
100
101    let mut buf = vec![0u8; read_len];
102    f.read_exact(&mut buf)
103        .map_err(|e| IdbError::Io(format!("Cannot read {} bytes at offset {}: {}", read_len, offset, e)))?;
104
105    if raw {
106        writer
107            .write_all(&buf)
108            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
109    } else {
110        wprintln!(
111            writer,
112            "Hex dump of {} at offset {} ({} bytes):",
113            file, offset, read_len
114        )?;
115        wprintln!(writer)?;
116        wprintln!(writer, "{}", hex_dump(&buf, offset))?;
117    }
118
119    Ok(())
120}