Skip to main content

idb/cli/
find.rs

1use std::io::Write;
2use std::path::Path;
3
4use indicatif::{ProgressBar, ProgressStyle};
5use serde::Serialize;
6
7use crate::cli::wprintln;
8use crate::innodb::page::FilHeader;
9use crate::innodb::tablespace::Tablespace;
10use crate::IdbError;
11
12pub struct FindOptions {
13    pub datadir: String,
14    pub page: u64,
15    pub checksum: Option<u32>,
16    pub space_id: Option<u32>,
17    pub first: bool,
18    pub json: bool,
19    pub page_size: Option<u32>,
20}
21
22#[derive(Serialize)]
23struct FindResultJson {
24    datadir: String,
25    target_page: u64,
26    matches: Vec<FindMatchJson>,
27    files_searched: usize,
28}
29
30#[derive(Serialize)]
31struct FindMatchJson {
32    file: String,
33    page_number: u64,
34    checksum: u32,
35    space_id: u32,
36}
37
38pub fn execute(opts: &FindOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
39    let datadir = Path::new(&opts.datadir);
40    if !datadir.is_dir() {
41        return Err(IdbError::Argument(format!(
42            "Data directory does not exist: {}",
43            opts.datadir
44        )));
45    }
46
47    // Find all .ibd files in subdirectories
48    let ibd_files = find_ibd_files(datadir)?;
49
50    if ibd_files.is_empty() {
51        if opts.json {
52            let result = FindResultJson {
53                datadir: opts.datadir.clone(),
54                target_page: opts.page,
55                matches: Vec::new(),
56                files_searched: 0,
57            };
58            let json = serde_json::to_string_pretty(&result)
59                .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
60            wprintln!(writer, "{}", json)?;
61        } else {
62            wprintln!(writer, "No .ibd files found in {}", opts.datadir)?;
63        }
64        return Ok(());
65    }
66
67    let mut matches: Vec<FindMatchJson> = Vec::new();
68    let mut files_searched = 0;
69
70    let pb = if !opts.json {
71        let bar = ProgressBar::new(ibd_files.len() as u64);
72        bar.set_style(
73            ProgressStyle::default_bar()
74                .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} files ({eta})")
75                .unwrap()
76                .progress_chars("#>-"),
77        );
78        Some(bar)
79    } else {
80        None
81    };
82
83    for ibd_path in &ibd_files {
84        if let Some(ref pb) = pb {
85            pb.inc(1);
86        }
87        let display_path = ibd_path.strip_prefix(datadir).unwrap_or(ibd_path);
88        if !opts.json {
89            wprintln!(writer, "Checking {}.. ", display_path.display())?;
90        }
91
92        let ts_result = match opts.page_size {
93            Some(ps) => Tablespace::open_with_page_size(ibd_path, ps),
94            None => Tablespace::open(ibd_path),
95        };
96        let mut ts = match ts_result {
97            Ok(t) => t,
98            Err(_) => continue,
99        };
100
101        files_searched += 1;
102
103        for page_num in 0..ts.page_count() {
104            let page_data = match ts.read_page(page_num) {
105                Ok(d) => d,
106                Err(_) => continue,
107            };
108
109            let header = match FilHeader::parse(&page_data) {
110                Some(h) => h,
111                None => continue,
112            };
113
114            if header.page_number as u64 == opts.page {
115                // If checksum filter specified, must also match
116                if let Some(expected_csum) = opts.checksum {
117                    if header.checksum != expected_csum {
118                        continue;
119                    }
120                }
121                // If space_id filter specified, must also match
122                if let Some(expected_sid) = opts.space_id {
123                    if header.space_id != expected_sid {
124                        continue;
125                    }
126                }
127
128                if !opts.json {
129                    wprintln!(
130                        writer,
131                        "Found page {} in {} (checksum: {}, space_id: {})",
132                        opts.page,
133                        display_path.display(),
134                        header.checksum,
135                        header.space_id
136                    )?;
137                }
138
139                matches.push(FindMatchJson {
140                    file: display_path.display().to_string(),
141                    page_number: header.page_number as u64,
142                    checksum: header.checksum,
143                    space_id: header.space_id,
144                });
145
146                if opts.first {
147                    break;
148                }
149            }
150        }
151
152        if opts.first && !matches.is_empty() {
153            break;
154        }
155    }
156
157    if let Some(pb) = pb {
158        pb.finish_and_clear();
159    }
160
161    if opts.json {
162        let result = FindResultJson {
163            datadir: opts.datadir.clone(),
164            target_page: opts.page,
165            matches,
166            files_searched,
167        };
168        let json = serde_json::to_string_pretty(&result)
169            .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
170        wprintln!(writer, "{}", json)?;
171    } else if matches.is_empty() {
172        wprintln!(writer, "Page {} not found in any .ibd file.", opts.page)?;
173    } else {
174        wprintln!(writer)?;
175        wprintln!(
176            writer,
177            "Found {} match(es) in {} file(s) searched.",
178            matches.len(),
179            files_searched
180        )?;
181    }
182
183    Ok(())
184}
185
186/// Recursively find all .ibd files in subdirectories of the data directory.
187fn find_ibd_files(datadir: &Path) -> Result<Vec<std::path::PathBuf>, IdbError> {
188    let mut files = Vec::new();
189
190    let entries = std::fs::read_dir(datadir)
191        .map_err(|e| IdbError::Io(format!("Cannot read directory {}: {}", datadir.display(), e)))?;
192
193    for entry in entries {
194        let entry =
195            entry.map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
196        let path = entry.path();
197
198        if path.is_dir() {
199            // Look for .ibd files in subdirectories
200            let sub_entries = std::fs::read_dir(&path)
201                .map_err(|e| IdbError::Io(format!("Cannot read {}: {}", path.display(), e)))?;
202
203            for sub_entry in sub_entries {
204                let sub_entry = sub_entry
205                    .map_err(|e| IdbError::Io(format!("Cannot read directory entry: {}", e)))?;
206                let sub_path = sub_entry.path();
207                if sub_path.extension().is_some_and(|ext| ext == "ibd") {
208                    files.push(sub_path);
209                }
210            }
211        } else if path.extension().is_some_and(|ext| ext == "ibd") {
212            files.push(path);
213        }
214    }
215
216    files.sort();
217    Ok(files)
218}