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 {
14 pub datadir: String,
16 pub page: u64,
18 pub checksum: Option<u32>,
20 pub space_id: Option<u32>,
22 pub first: bool,
24 pub json: bool,
26 pub page_size: Option<u32>,
28}
29
30#[derive(Serialize)]
31struct FindResultJson {
32 datadir: String,
33 target_page: u64,
34 matches: Vec<FindMatchJson>,
35 files_searched: usize,
36}
37
38#[derive(Serialize)]
39struct FindMatchJson {
40 file: String,
41 page_number: u64,
42 checksum: u32,
43 space_id: u32,
44}
45
46pub fn execute(opts: &FindOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
66 let datadir = Path::new(&opts.datadir);
67 if !datadir.is_dir() {
68 return Err(IdbError::Argument(format!(
69 "Data directory does not exist: {}",
70 opts.datadir
71 )));
72 }
73
74 let ibd_files = find_tablespace_files(datadir, &["ibd"])?;
76
77 if ibd_files.is_empty() {
78 if opts.json {
79 let result = FindResultJson {
80 datadir: opts.datadir.clone(),
81 target_page: opts.page,
82 matches: Vec::new(),
83 files_searched: 0,
84 };
85 let json = serde_json::to_string_pretty(&result)
86 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
87 wprintln!(writer, "{}", json)?;
88 } else {
89 wprintln!(writer, "No .ibd files found in {}", opts.datadir)?;
90 }
91 return Ok(());
92 }
93
94 let mut matches: Vec<FindMatchJson> = Vec::new();
95 let mut files_searched = 0;
96
97 let pb = if !opts.json {
98 Some(create_progress_bar(ibd_files.len() as u64, "files"))
99 } else {
100 None
101 };
102
103 for ibd_path in &ibd_files {
104 if let Some(ref pb) = pb {
105 pb.inc(1);
106 }
107 let display_path = ibd_path.strip_prefix(datadir).unwrap_or(ibd_path);
108 if !opts.json {
109 wprintln!(writer, "Checking {}.. ", display_path.display())?;
110 }
111
112 let ts_result = match opts.page_size {
113 Some(ps) => Tablespace::open_with_page_size(ibd_path, ps),
114 None => Tablespace::open(ibd_path),
115 };
116 let mut ts = match ts_result {
117 Ok(t) => t,
118 Err(_) => continue,
119 };
120
121 files_searched += 1;
122
123 for page_num in 0..ts.page_count() {
124 let page_data = match ts.read_page(page_num) {
125 Ok(d) => d,
126 Err(_) => continue,
127 };
128
129 let header = match FilHeader::parse(&page_data) {
130 Some(h) => h,
131 None => continue,
132 };
133
134 if header.page_number as u64 == opts.page {
135 if let Some(expected_csum) = opts.checksum {
137 if header.checksum != expected_csum {
138 continue;
139 }
140 }
141 if let Some(expected_sid) = opts.space_id {
143 if header.space_id != expected_sid {
144 continue;
145 }
146 }
147
148 if !opts.json {
149 wprintln!(
150 writer,
151 "Found page {} in {} (checksum: {}, space_id: {})",
152 opts.page,
153 display_path.display(),
154 header.checksum,
155 header.space_id
156 )?;
157 }
158
159 matches.push(FindMatchJson {
160 file: display_path.display().to_string(),
161 page_number: header.page_number as u64,
162 checksum: header.checksum,
163 space_id: header.space_id,
164 });
165
166 if opts.first {
167 break;
168 }
169 }
170 }
171
172 if opts.first && !matches.is_empty() {
173 break;
174 }
175 }
176
177 if let Some(pb) = pb {
178 pb.finish_and_clear();
179 }
180
181 if opts.json {
182 let result = FindResultJson {
183 datadir: opts.datadir.clone(),
184 target_page: opts.page,
185 matches,
186 files_searched,
187 };
188 let json = serde_json::to_string_pretty(&result)
189 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
190 wprintln!(writer, "{}", json)?;
191 } else if matches.is_empty() {
192 wprintln!(writer, "Page {} not found in any .ibd file.", opts.page)?;
193 } else {
194 wprintln!(writer)?;
195 wprintln!(
196 writer,
197 "Found {} match(es) in {} file(s) searched.",
198 matches.len(),
199 files_searched
200 )?;
201 }
202
203 Ok(())
204}
205