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 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 let Some(expected_csum) = opts.checksum {
110 if header.checksum != expected_csum {
111 continue;
112 }
113 }
114 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