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 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 let Some(expected_csum) = opts.checksum {
117 if header.checksum != expected_csum {
118 continue;
119 }
120 }
121 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
186fn 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 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}