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(
46            &opts.file,
47            abs_offset,
48            opts.length.unwrap_or(256),
49            opts.raw,
50            writer,
51        );
52    }
53
54    // Page mode: dump a specific page (or page 0 by default)
55    let mut ts = match opts.page_size {
56        Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
57        None => Tablespace::open(&opts.file)?,
58    };
59
60    let page_size = ts.page_size();
61    let page_num = opts.page.unwrap_or(0);
62    let page_data = ts.read_page(page_num)?;
63
64    let length = opts.length.unwrap_or(page_size as usize);
65    let dump_len = length.min(page_data.len());
66    let base_offset = page_num * page_size as u64;
67
68    if opts.raw {
69        writer
70            .write_all(&page_data[..dump_len])
71            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
72    } else {
73        wprintln!(
74            writer,
75            "Hex dump of {} page {} ({} bytes):",
76            opts.file,
77            page_num,
78            dump_len
79        )?;
80        wprintln!(writer)?;
81        wprintln!(writer, "{}", hex_dump(&page_data[..dump_len], base_offset))?;
82    }
83
84    Ok(())
85}
86
87fn dump_at_offset(
88    file: &str,
89    offset: u64,
90    length: usize,
91    raw: bool,
92    writer: &mut dyn Write,
93) -> Result<(), IdbError> {
94    let mut f =
95        File::open(file).map_err(|e| IdbError::Io(format!("Cannot open {}: {}", file, e)))?;
96
97    let file_size = f
98        .metadata()
99        .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", file, e)))?
100        .len();
101
102    if offset >= file_size {
103        return Err(IdbError::Argument(format!(
104            "Offset {} is beyond file size {}",
105            offset, file_size
106        )));
107    }
108
109    let available = (file_size - offset) as usize;
110    let read_len = length.min(available);
111
112    f.seek(SeekFrom::Start(offset))
113        .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
114
115    let mut buf = vec![0u8; read_len];
116    f.read_exact(&mut buf).map_err(|e| {
117        IdbError::Io(format!(
118            "Cannot read {} bytes at offset {}: {}",
119            read_len, offset, e
120        ))
121    })?;
122
123    if raw {
124        writer
125            .write_all(&buf)
126            .map_err(|e| IdbError::Io(format!("Cannot write to stdout: {}", e)))?;
127    } else {
128        wprintln!(
129            writer,
130            "Hex dump of {} at offset {} ({} bytes):",
131            file,
132            offset,
133            read_len
134        )?;
135        wprintln!(writer)?;
136        wprintln!(writer, "{}", hex_dump(&buf, offset))?;
137    }
138
139    Ok(())
140}