1use std::fs::{self, File, OpenOptions};
11use std::io::{Read, Seek, SeekFrom, Write};
12use std::path::{Path, PathBuf};
13
14use byteorder::{BigEndian, ByteOrder};
15
16use crate::innodb::checksum::{recalculate_checksum, validate_checksum, ChecksumAlgorithm};
17use crate::innodb::constants::*;
18use crate::innodb::vendor::VendorInfo;
19use crate::IdbError;
20
21pub fn create_backup(path: &str) -> Result<PathBuf, IdbError> {
26 let src = Path::new(path);
27 if !src.exists() {
28 return Err(IdbError::Io(format!("File not found: {}", path)));
29 }
30
31 let mut backup_path = PathBuf::from(format!("{}.bak", path));
32 let mut counter = 1u32;
33 while backup_path.exists() {
34 backup_path = PathBuf::from(format!("{}.bak.{}", path, counter));
35 counter += 1;
36 if counter > 999 {
37 return Err(IdbError::Io(format!("Too many backup files for {}", path)));
38 }
39 }
40
41 fs::copy(src, &backup_path).map_err(|e| {
42 IdbError::Io(format!(
43 "Cannot create backup {}: {}",
44 backup_path.display(),
45 e
46 ))
47 })?;
48
49 Ok(backup_path)
50}
51
52pub fn read_page_raw(path: &str, page_num: u64, page_size: u32) -> Result<Vec<u8>, IdbError> {
56 let offset = page_num * page_size as u64;
57 let mut f =
58 File::open(path).map_err(|e| IdbError::Io(format!("Cannot open {}: {}", path, e)))?;
59 f.seek(SeekFrom::Start(offset))
60 .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
61 let mut buf = vec![0u8; page_size as usize];
62 f.read_exact(&mut buf)
63 .map_err(|e| IdbError::Io(format!("Cannot read page {}: {}", page_num, e)))?;
64 Ok(buf)
65}
66
67pub fn write_page(path: &str, page_num: u64, page_size: u32, data: &[u8]) -> Result<(), IdbError> {
71 if data.len() != page_size as usize {
72 return Err(IdbError::Argument(format!(
73 "Page data is {} bytes, expected {}",
74 data.len(),
75 page_size
76 )));
77 }
78 let offset = page_num * page_size as u64;
79 let mut f = OpenOptions::new()
80 .write(true)
81 .open(path)
82 .map_err(|e| IdbError::Io(format!("Cannot open {} for writing: {}", path, e)))?;
83 f.seek(SeekFrom::Start(offset))
84 .map_err(|e| IdbError::Io(format!("Cannot seek to offset {}: {}", offset, e)))?;
85 f.write_all(data)
86 .map_err(|e| IdbError::Io(format!("Cannot write page {}: {}", page_num, e)))?;
87 Ok(())
88}
89
90pub fn write_tablespace(path: &str, pages: &[Vec<u8>]) -> Result<(), IdbError> {
94 let mut f =
95 File::create(path).map_err(|e| IdbError::Io(format!("Cannot create {}: {}", path, e)))?;
96 for (i, page) in pages.iter().enumerate() {
97 f.write_all(page)
98 .map_err(|e| IdbError::Io(format!("Cannot write page {}: {}", i, e)))?;
99 }
100 f.flush()
101 .map_err(|e| IdbError::Io(format!("Cannot flush {}: {}", path, e)))?;
102 Ok(())
103}
104
105pub fn build_fsp_page(
108 space_id: u32,
109 total_pages: u32,
110 flags: u32,
111 lsn: u64,
112 page_size: u32,
113 algorithm: ChecksumAlgorithm,
114) -> Vec<u8> {
115 let ps = page_size as usize;
116 let mut page = vec![0u8; ps];
117
118 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 0); BigEndian::write_u32(&mut page[FIL_PAGE_PREV..], FIL_NULL);
121 BigEndian::write_u32(&mut page[FIL_PAGE_NEXT..], FIL_NULL);
122 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], lsn);
123 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 8); BigEndian::write_u64(&mut page[FIL_PAGE_FILE_FLUSH_LSN..], lsn);
125 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], space_id);
126
127 let fsp = FIL_PAGE_DATA;
129 BigEndian::write_u32(&mut page[fsp + FSP_SPACE_ID..], space_id);
130 BigEndian::write_u32(&mut page[fsp + FSP_SIZE..], total_pages);
131 BigEndian::write_u32(&mut page[fsp + FSP_FREE_LIMIT..], total_pages);
132 BigEndian::write_u32(&mut page[fsp + FSP_SPACE_FLAGS..], flags);
133
134 let trailer = ps - SIZE_FIL_TRAILER;
136 BigEndian::write_u32(&mut page[trailer + 4..], (lsn & 0xFFFFFFFF) as u32);
137
138 recalculate_checksum(&mut page, page_size, algorithm);
140
141 page
142}
143
144pub fn detect_algorithm(
150 page_data: &[u8],
151 page_size: u32,
152 vendor_info: Option<&VendorInfo>,
153) -> ChecksumAlgorithm {
154 let result = validate_checksum(page_data, page_size, vendor_info);
155 result.algorithm
156}
157
158pub fn fix_page_checksum(
165 page_data: &mut [u8],
166 page_size: u32,
167 algorithm: ChecksumAlgorithm,
168) -> (u32, u32) {
169 let ps = page_size as usize;
170 if page_data.len() < ps {
171 return (0, 0);
172 }
173
174 let old_checksum = match algorithm {
176 ChecksumAlgorithm::MariaDbFullCrc32 => BigEndian::read_u32(&page_data[ps - 4..]),
177 _ => BigEndian::read_u32(&page_data[FIL_PAGE_SPACE_OR_CHKSUM..]),
178 };
179
180 let header_lsn = BigEndian::read_u64(&page_data[FIL_PAGE_LSN..]);
182 let trailer_offset = ps - SIZE_FIL_TRAILER;
183 BigEndian::write_u32(
184 &mut page_data[trailer_offset + 4..],
185 (header_lsn & 0xFFFFFFFF) as u32,
186 );
187
188 recalculate_checksum(page_data, page_size, algorithm);
190
191 let new_checksum = match algorithm {
193 ChecksumAlgorithm::MariaDbFullCrc32 => BigEndian::read_u32(&page_data[ps - 4..]),
194 _ => BigEndian::read_u32(&page_data[FIL_PAGE_SPACE_OR_CHKSUM..]),
195 };
196
197 (old_checksum, new_checksum)
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::innodb::checksum::validate_lsn;
204 use crate::innodb::tablespace::Tablespace;
205 use std::io::Write as IoWrite;
206 use tempfile::NamedTempFile;
207
208 const PS: u32 = 16384;
209
210 fn make_test_page(page_num: u32, space_id: u32, lsn: u64) -> Vec<u8> {
211 let ps = PS as usize;
212 let mut page = vec![0u8; ps];
213 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], page_num);
214 BigEndian::write_u32(&mut page[FIL_PAGE_PREV..], FIL_NULL);
215 BigEndian::write_u32(&mut page[FIL_PAGE_NEXT..], FIL_NULL);
216 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], lsn);
217 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855); BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], space_id);
219 let trailer = ps - SIZE_FIL_TRAILER;
220 BigEndian::write_u32(&mut page[trailer + 4..], (lsn & 0xFFFFFFFF) as u32);
221 recalculate_checksum(&mut page, PS, ChecksumAlgorithm::Crc32c);
222 page
223 }
224
225 fn write_temp_tablespace(pages: &[Vec<u8>]) -> NamedTempFile {
226 let mut tmp = NamedTempFile::new().unwrap();
227 for page in pages {
228 tmp.write_all(page).unwrap();
229 }
230 tmp.flush().unwrap();
231 tmp
232 }
233
234 #[test]
235 fn test_create_backup() {
236 let mut tmp = NamedTempFile::new().unwrap();
237 tmp.write_all(b"test data").unwrap();
238 tmp.flush().unwrap();
239
240 let path = tmp.path().to_str().unwrap();
241 let backup = create_backup(path).unwrap();
242 assert!(backup.exists());
243 assert_eq!(fs::read(&backup).unwrap(), b"test data");
244
245 let backup2 = create_backup(path).unwrap();
247 assert!(backup2.exists());
248 assert_ne!(backup, backup2);
249
250 let _ = fs::remove_file(&backup);
252 let _ = fs::remove_file(&backup2);
253 }
254
255 #[test]
256 fn test_read_page_raw_roundtrip() {
257 let page0 = build_fsp_page(42, 2, 0, 1000, PS, ChecksumAlgorithm::Crc32c);
258 let page1 = make_test_page(1, 42, 2000);
259 let tmp = write_temp_tablespace(&[page0.clone(), page1.clone()]);
260 let path = tmp.path().to_str().unwrap();
261
262 let read0 = read_page_raw(path, 0, PS).unwrap();
263 assert_eq!(read0, page0);
264
265 let read1 = read_page_raw(path, 1, PS).unwrap();
266 assert_eq!(read1, page1);
267 }
268
269 #[test]
270 fn test_write_page_modifies_correct_offset() {
271 let page0 = build_fsp_page(42, 2, 0, 1000, PS, ChecksumAlgorithm::Crc32c);
272 let page1 = make_test_page(1, 42, 2000);
273 let tmp = write_temp_tablespace(&[page0, page1]);
274 let path = tmp.path().to_str().unwrap();
275
276 let new_page1 = make_test_page(1, 42, 9999);
278 write_page(path, 1, PS, &new_page1).unwrap();
279
280 let read1 = read_page_raw(path, 1, PS).unwrap();
282 assert_eq!(read1, new_page1);
283
284 let read0 = read_page_raw(path, 0, PS).unwrap();
286 let result = validate_checksum(&read0, PS, None);
287 assert!(result.valid);
288 }
289
290 #[test]
291 fn test_build_fsp_page_valid() {
292 let page = build_fsp_page(42, 5, 0, 1000, PS, ChecksumAlgorithm::Crc32c);
293 assert_eq!(page.len(), PS as usize);
294
295 let result = validate_checksum(&page, PS, None);
297 assert!(result.valid);
298
299 assert!(validate_lsn(&page, PS));
301
302 assert_eq!(BigEndian::read_u32(&page[FIL_PAGE_SPACE_ID..]), 42);
304
305 let fsp = FIL_PAGE_DATA;
307 assert_eq!(BigEndian::read_u32(&page[fsp + FSP_SPACE_ID..]), 42);
308 assert_eq!(BigEndian::read_u32(&page[fsp + FSP_SIZE..]), 5);
309 }
310
311 #[test]
312 fn test_build_fsp_page_opens_as_tablespace() {
313 let page0 = build_fsp_page(42, 3, 0, 1000, PS, ChecksumAlgorithm::Crc32c);
314 let page1 = make_test_page(1, 42, 2000);
315 let page2 = make_test_page(2, 42, 3000);
316 let tmp = write_temp_tablespace(&[page0, page1, page2]);
317
318 let ts = Tablespace::open(tmp.path()).unwrap();
319 assert_eq!(ts.page_size(), PS);
320 assert_eq!(ts.page_count(), 3);
321 }
322
323 #[test]
324 fn test_fix_page_checksum_crc32c() {
325 let mut page = make_test_page(1, 42, 5000);
326
327 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_OR_CHKSUM..], 0xDEAD);
329 assert!(!validate_checksum(&page, PS, None).valid);
330
331 let (old, new) = fix_page_checksum(&mut page, PS, ChecksumAlgorithm::Crc32c);
333 assert_eq!(old, 0xDEAD);
334 assert_ne!(new, 0xDEAD);
335 assert!(validate_checksum(&page, PS, None).valid);
336 assert!(validate_lsn(&page, PS));
337 }
338
339 #[test]
340 fn test_fix_page_checksum_fixes_trailer_lsn() {
341 let ps = PS as usize;
342 let mut page = make_test_page(1, 42, 5000);
343
344 let trailer = ps - SIZE_FIL_TRAILER;
346 BigEndian::write_u32(&mut page[trailer + 4..], 0xAAAA);
347 assert!(!validate_lsn(&page, PS));
348
349 fix_page_checksum(&mut page, PS, ChecksumAlgorithm::Crc32c);
351 assert!(validate_lsn(&page, PS));
352 assert!(validate_checksum(&page, PS, None).valid);
353 }
354
355 #[test]
356 fn test_write_tablespace_creates_file() {
357 let page0 = build_fsp_page(42, 2, 0, 1000, PS, ChecksumAlgorithm::Crc32c);
358 let page1 = make_test_page(1, 42, 2000);
359
360 let tmp = tempfile::NamedTempFile::new().unwrap();
361 let path = tmp.path().to_str().unwrap().to_string();
362 drop(tmp); write_tablespace(&path, &[page0.clone(), page1.clone()]).unwrap();
365
366 let ts = Tablespace::open(&path).unwrap();
368 assert_eq!(ts.page_size(), PS);
369 assert_eq!(ts.page_count(), 2);
370
371 let _ = fs::remove_file(&path);
372 }
373
374 #[test]
375 fn test_detect_algorithm_crc32c() {
376 let page = make_test_page(1, 42, 5000);
377 let algo = detect_algorithm(&page, PS, None);
378 assert_eq!(algo, ChecksumAlgorithm::Crc32c);
379 }
380}