1use std::fs::{File, OpenOptions};
2use std::io::{Seek, SeekFrom, Write};
3
4use colored::Colorize;
5use rand::Rng;
6use serde::Serialize;
7
8use crate::cli::wprintln;
9use crate::innodb::checksum::{validate_checksum, ChecksumAlgorithm};
10use crate::innodb::constants::{SIZE_FIL_HEAD, SIZE_FIL_TRAILER};
11use crate::innodb::tablespace::Tablespace;
12use crate::util::hex::format_bytes;
13use crate::IdbError;
14
15pub struct CorruptOptions {
16 pub file: String,
17 pub page: Option<u64>,
18 pub bytes: usize,
19 pub header: bool,
20 pub records: bool,
21 pub offset: Option<u64>,
22 pub verify: bool,
23 pub json: bool,
24 pub page_size: Option<u32>,
25}
26
27#[derive(Serialize)]
28struct CorruptResultJson {
29 file: String,
30 offset: u64,
31 page: Option<u64>,
32 bytes_written: usize,
33 data: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 verify: Option<VerifyResultJson>,
36}
37
38#[derive(Serialize)]
39struct VerifyResultJson {
40 page: u64,
41 before: ChecksumInfoJson,
42 after: ChecksumInfoJson,
43}
44
45#[derive(Serialize)]
46struct ChecksumInfoJson {
47 valid: bool,
48 algorithm: String,
49 stored_checksum: u32,
50 calculated_checksum: u32,
51}
52
53pub fn execute(opts: &CorruptOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
54 if let Some(abs_offset) = opts.offset {
56 return corrupt_at_offset(opts, abs_offset, writer);
57 }
58
59 let ts = match opts.page_size {
61 Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
62 None => Tablespace::open(&opts.file)?,
63 };
64
65 let page_size = ts.page_size() as usize;
66 let page_count = ts.page_count();
67
68 let mut rng = rand::rng();
69
70 let page_num = match opts.page {
72 Some(p) => {
73 if p >= page_count {
74 return Err(IdbError::Argument(format!(
75 "Page {} out of range (tablespace has {} pages)",
76 p, page_count
77 )));
78 }
79 p
80 }
81 None => {
82 let p = rng.random_range(0..page_count);
83 if !opts.json {
84 wprintln!(
85 writer,
86 "No page specified. Choosing random page {}.",
87 format!("{}", p).yellow()
88 )?;
89 }
90 p
91 }
92 };
93
94 let byte_start = page_num * page_size as u64;
95
96 let corrupt_offset = if opts.header {
98 let header_offset = rng.random_range(0..SIZE_FIL_HEAD as u64);
100 byte_start + header_offset
101 } else if opts.records {
102 let user_data_start = 120u64; let max_offset = page_size as u64 - user_data_start - SIZE_FIL_TRAILER as u64;
105 let record_offset = rng.random_range(0..max_offset);
106 byte_start + user_data_start + record_offset
107 } else {
108 byte_start
110 };
111
112 let random_data: Vec<u8> = (0..opts.bytes).map(|_| rng.random::<u8>()).collect();
114
115 let pre_checksum = if opts.verify {
117 let pre_data = read_page_bytes(&opts.file, page_num, page_size as u32)?;
118 Some(validate_checksum(&pre_data, page_size as u32))
119 } else {
120 None
121 };
122
123 if opts.json {
124 write_corruption(&opts.file, corrupt_offset, &random_data)?;
126 let verify_json = if opts.verify {
127 let post_data = read_page_bytes(&opts.file, page_num, page_size as u32)?;
128 let post_result = validate_checksum(&post_data, page_size as u32);
129 let pre = pre_checksum.unwrap();
130 Some(VerifyResultJson {
131 page: page_num,
132 before: checksum_to_json(&pre),
133 after: checksum_to_json(&post_result),
134 })
135 } else {
136 None
137 };
138 return output_json_with_verify(opts, corrupt_offset, Some(page_num), &random_data, verify_json, writer);
139 }
140
141 wprintln!(
142 writer,
143 "Writing {} bytes of random data to {} at offset {} (page {})...",
144 opts.bytes,
145 opts.file,
146 corrupt_offset,
147 format!("{}", page_num).yellow()
148 )?;
149
150 write_corruption(&opts.file, corrupt_offset, &random_data)?;
151
152 wprintln!(writer, "Data written: {}", format_bytes(&random_data).red())?;
153 wprintln!(writer, "Completed.")?;
154
155 if opts.verify {
157 let post_data = read_page_bytes(&opts.file, page_num, page_size as u32)?;
158 let post_result = validate_checksum(&post_data, page_size as u32);
159 let pre = pre_checksum.unwrap();
160 wprintln!(writer)?;
161 wprintln!(writer, "{}:", "Verification".bold())?;
162 wprintln!(
163 writer,
164 " Before: {} (algorithm={:?}, stored={}, calculated={})",
165 if pre.valid { "OK".green().to_string() } else { "INVALID".red().to_string() },
166 pre.algorithm, pre.stored_checksum, pre.calculated_checksum
167 )?;
168 wprintln!(
169 writer,
170 " After: {} (algorithm={:?}, stored={}, calculated={})",
171 if post_result.valid { "OK".green().to_string() } else { "INVALID".red().to_string() },
172 post_result.algorithm, post_result.stored_checksum, post_result.calculated_checksum
173 )?;
174 }
175
176 Ok(())
177}
178
179fn corrupt_at_offset(opts: &CorruptOptions, abs_offset: u64, writer: &mut dyn Write) -> Result<(), IdbError> {
180 let file_size = File::open(&opts.file)
182 .map_err(|e| IdbError::Io(format!("Cannot open {}: {}", opts.file, e)))?
183 .metadata()
184 .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", opts.file, e)))?
185 .len();
186
187 if abs_offset >= file_size {
188 return Err(IdbError::Argument(format!(
189 "Offset {} is beyond file size {}",
190 abs_offset, file_size
191 )));
192 }
193
194 let mut rng = rand::rng();
195 let random_data: Vec<u8> = (0..opts.bytes).map(|_| rng.random::<u8>()).collect();
196
197 write_corruption(&opts.file, abs_offset, &random_data)?;
199
200 if opts.json {
201 return output_json_with_verify(opts, abs_offset, None, &random_data, None, writer);
202 }
203
204 wprintln!(
205 writer,
206 "Writing {} bytes of random data to {} at offset {}...",
207 opts.bytes, opts.file, abs_offset
208 )?;
209
210 wprintln!(writer, "Data written: {}", format_bytes(&random_data).red())?;
211 wprintln!(writer, "Completed.")?;
212
213 if opts.verify {
214 wprintln!(writer, "Note: --verify is not available in absolute offset mode (no page context).")?;
215 }
216
217 Ok(())
218}
219
220fn write_corruption(file_path: &str, offset: u64, data: &[u8]) -> Result<(), IdbError> {
221 let mut file = OpenOptions::new()
222 .write(true)
223 .open(file_path)
224 .map_err(|e| IdbError::Io(format!("Cannot open {} for writing: {}", file_path, e)))?;
225
226 file.seek(SeekFrom::Start(offset))
227 .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
228
229 file.write_all(data)
230 .map_err(|e| IdbError::Io(format!("Cannot write corruption data: {}", e)))?;
231
232 Ok(())
233}
234
235fn output_json_with_verify(
236 opts: &CorruptOptions,
237 offset: u64,
238 page: Option<u64>,
239 data: &[u8],
240 verify: Option<VerifyResultJson>,
241 writer: &mut dyn Write,
242) -> Result<(), IdbError> {
243 let result = CorruptResultJson {
244 file: opts.file.clone(),
245 offset,
246 page,
247 bytes_written: data.len(),
248 data: format_bytes(data),
249 verify,
250 };
251
252 let json = serde_json::to_string_pretty(&result)
253 .map_err(|e| IdbError::Parse(format!("JSON serialization error: {}", e)))?;
254 wprintln!(writer, "{}", json)?;
255
256 Ok(())
257}
258
259fn read_page_bytes(file_path: &str, page_num: u64, page_size: u32) -> Result<Vec<u8>, IdbError> {
260 use std::io::Read;
261 let offset = page_num * page_size as u64;
262 let mut f = File::open(file_path)
263 .map_err(|e| IdbError::Io(format!("Cannot open {}: {}", file_path, e)))?;
264 f.seek(SeekFrom::Start(offset))
265 .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
266 let mut buf = vec![0u8; page_size as usize];
267 f.read_exact(&mut buf)
268 .map_err(|e| IdbError::Io(format!("Cannot read page {}: {}", page_num, e)))?;
269 Ok(buf)
270}
271
272fn checksum_to_json(result: &crate::innodb::checksum::ChecksumResult) -> ChecksumInfoJson {
273 let algorithm_name = match result.algorithm {
274 ChecksumAlgorithm::Crc32c => "crc32c",
275 ChecksumAlgorithm::InnoDB => "innodb",
276 ChecksumAlgorithm::None => "none",
277 };
278 ChecksumInfoJson {
279 valid: result.valid,
280 algorithm: algorithm_name.to_string(),
281 stored_checksum: result.stored_checksum,
282 calculated_checksum: result.calculated_checksum,
283 }
284}