Skip to main content

idb/cli/
find.rs

1use std::io::Write;
2use std::path::Path;
3
4use serde::Serialize;
5
6use crate::cli::{wprintln, create_progress_bar};
7use crate::innodb::page::FilHeader;
8use crate::innodb::tablespace::Tablespace;
9use crate::util::fs::find_tablespace_files;
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_tablespace_files(datadir, &["ibd"])?;
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        Some(create_progress_bar(ibd_files.len() as u64, "files"))
72    } else {
73        None
74    };
75
76    for ibd_path in &ibd_files {
77        if let Some(ref pb) = pb {
78            pb.inc(1);
79        }
80        let display_path = ibd_path.strip_prefix(datadir).unwrap_or(ibd_path);
81        if !opts.json {
82            wprintln!(writer, "Checking {}.. ", display_path.display())?;
83        }
84
85        let ts_result = match opts.page_size {
86            Some(ps) => Tablespace::open_with_page_size(ibd_path, ps),
87            None => Tablespace::open(ibd_path),
88        };
89        let mut ts = match ts_result {
90            Ok(t) => t,
91            Err(_) => continue,
92        };
93
94        files_searched += 1;
95
96        for page_num in 0..ts.page_count() {
97            let page_data = match ts.read_page(page_num) {
98                Ok(d) => d,
99                Err(_) => continue,
100            };
101
102            let header = match FilHeader::parse(&page_data) {
103                Some(h) => h,
104                None => continue,
105            };
106
107            if header.page_number as u64 == opts.page {
108                // If checksum filter specified, must also match
109                if let Some(expected_csum) = opts.checksum {
110                    if header.checksum != expected_csum {
111                        continue;
112                    }
113                }
114                // If space_id filter specified, must also match
115                if let Some(expected_sid) = opts.space_id {
116                    if header.space_id != expected_sid {
117                        continue;
118                    }
119                }
120
121                if !opts.json {
122                    wprintln!(
123                        writer,
124                        "Found page {} in {} (checksum: {}, space_id: {})",
125                        opts.page,
126                        display_path.display(),
127                        header.checksum,
128                        header.space_id
129                    )?;
130                }
131
132                matches.push(FindMatchJson {
133                    file: display_path.display().to_string(),
134                    page_number: header.page_number as u64,
135                    checksum: header.checksum,
136                    space_id: header.space_id,
137                });
138
139                if opts.first {
140                    break;
141                }
142            }
143        }
144
145        if opts.first && !matches.is_empty() {
146            break;
147        }
148    }
149
150    if let Some(pb) = pb {
151        pb.finish_and_clear();
152    }
153
154    if opts.json {
155        let result = FindResultJson {
156            datadir: opts.datadir.clone(),
157            target_page: opts.page,
158            matches,
159            files_searched,
160        };
161        let json = serde_json::to_string_pretty(&result)
162            .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
163        wprintln!(writer, "{}", json)?;
164    } else if matches.is_empty() {
165        wprintln!(writer, "Page {} not found in any .ibd file.", opts.page)?;
166    } else {
167        wprintln!(writer)?;
168        wprintln!(
169            writer,
170            "Found {} match(es) in {} file(s) searched.",
171            matches.len(),
172            files_searched
173        )?;
174    }
175
176    Ok(())
177}
178