]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/fixed_index.rs
clippy: remove unnecessary clones
[proxmox-backup.git] / src / backup / fixed_index.rs
1 use anyhow::{bail, format_err, Error};
2 use std::io::{Seek, SeekFrom};
3
4 use super::chunk_stat::*;
5 use super::chunk_store::*;
6 use super::{ChunkReadInfo, IndexFile};
7 use crate::tools;
8
9 use std::fs::File;
10 use std::io::Write;
11 use std::os::unix::io::AsRawFd;
12 use std::path::{Path, PathBuf};
13 use std::sync::Arc;
14
15 use super::ChunkInfo;
16
17 use proxmox::tools::io::ReadExt;
18 use proxmox::tools::Uuid;
19
20 /// Header format definition for fixed index files (`.fidx`)
21 #[repr(C)]
22 pub struct FixedIndexHeader {
23 pub magic: [u8; 8],
24 pub uuid: [u8; 16],
25 pub ctime: i64,
26 /// Sha256 over the index ``SHA256(digest1||digest2||...)``
27 pub index_csum: [u8; 32],
28 pub size: u64,
29 pub chunk_size: u64,
30 reserved: [u8; 4016], // overall size is one page (4096 bytes)
31 }
32 proxmox::static_assert_size!(FixedIndexHeader, 4096);
33
34 // split image into fixed size chunks
35
36 pub struct FixedIndexReader {
37 _file: File,
38 pub chunk_size: usize,
39 pub size: u64,
40 index_length: usize,
41 index: *mut u8,
42 pub uuid: [u8; 16],
43 pub ctime: i64,
44 pub index_csum: [u8; 32],
45 }
46
47 // `index` is mmap()ed which cannot be thread-local so should be sendable
48 unsafe impl Send for FixedIndexReader {}
49 unsafe impl Sync for FixedIndexReader {}
50
51 impl Drop for FixedIndexReader {
52 fn drop(&mut self) {
53 if let Err(err) = self.unmap() {
54 eprintln!("Unable to unmap file - {}", err);
55 }
56 }
57 }
58
59 impl FixedIndexReader {
60 pub fn open(path: &Path) -> Result<Self, Error> {
61 File::open(path)
62 .map_err(Error::from)
63 .and_then(|file| Self::new(file))
64 .map_err(|err| format_err!("Unable to open fixed index {:?} - {}", path, err))
65 }
66
67 pub fn new(mut file: std::fs::File) -> Result<Self, Error> {
68 file.seek(SeekFrom::Start(0))?;
69
70 let header_size = std::mem::size_of::<FixedIndexHeader>();
71
72 let stat = match nix::sys::stat::fstat(file.as_raw_fd()) {
73 Ok(stat) => stat,
74 Err(err) => bail!("fstat failed - {}", err),
75 };
76
77 let size = stat.st_size as usize;
78
79 if size < header_size {
80 bail!("index too small ({})", stat.st_size);
81 }
82
83 let header: Box<FixedIndexHeader> = unsafe { file.read_host_value_boxed()? };
84
85 if header.magic != super::FIXED_SIZED_CHUNK_INDEX_1_0 {
86 bail!("got unknown magic number");
87 }
88
89 let size = u64::from_le(header.size);
90 let ctime = i64::from_le(header.ctime);
91 let chunk_size = u64::from_le(header.chunk_size);
92
93 let index_length = ((size + chunk_size - 1) / chunk_size) as usize;
94 let index_size = index_length * 32;
95
96 let expected_index_size = (stat.st_size as usize) - header_size;
97 if index_size != expected_index_size {
98 bail!(
99 "got unexpected file size ({} != {})",
100 index_size,
101 expected_index_size
102 );
103 }
104
105 let data = unsafe {
106 nix::sys::mman::mmap(
107 std::ptr::null_mut(),
108 index_size,
109 nix::sys::mman::ProtFlags::PROT_READ,
110 nix::sys::mman::MapFlags::MAP_PRIVATE,
111 file.as_raw_fd(),
112 header_size as i64,
113 )
114 }? as *mut u8;
115
116 Ok(Self {
117 _file: file,
118 chunk_size: chunk_size as usize,
119 size,
120 index_length,
121 index: data,
122 ctime,
123 uuid: header.uuid,
124 index_csum: header.index_csum,
125 })
126 }
127
128 fn unmap(&mut self) -> Result<(), Error> {
129 if self.index == std::ptr::null_mut() {
130 return Ok(());
131 }
132
133 let index_size = self.index_length * 32;
134
135 if let Err(err) =
136 unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
137 {
138 bail!("unmap file failed - {}", err);
139 }
140
141 self.index = std::ptr::null_mut();
142
143 Ok(())
144 }
145
146 pub fn print_info(&self) {
147 println!("Size: {}", self.size);
148 println!("ChunkSize: {}", self.chunk_size);
149
150 let mut ctime_str = self.ctime.to_string();
151 if let Ok(s) = proxmox::tools::time::strftime_local("%c", self.ctime) {
152 ctime_str = s;
153 }
154
155 println!("CTime: {}", ctime_str);
156 println!("UUID: {:?}", self.uuid);
157 }
158 }
159
160 impl IndexFile for FixedIndexReader {
161 fn index_count(&self) -> usize {
162 self.index_length
163 }
164
165 fn index_digest(&self, pos: usize) -> Option<&[u8; 32]> {
166 if pos >= self.index_length {
167 None
168 } else {
169 Some(unsafe { std::mem::transmute(self.index.add(pos * 32)) })
170 }
171 }
172
173 fn index_bytes(&self) -> u64 {
174 self.size
175 }
176
177 fn chunk_info(&self, pos: usize) -> Option<ChunkReadInfo> {
178 if pos >= self.index_length {
179 return None;
180 }
181
182 let start = (pos * self.chunk_size) as u64;
183 let mut end = start + self.chunk_size as u64;
184
185 if end > self.size {
186 end = self.size;
187 }
188
189 let digest = self.index_digest(pos).unwrap();
190 Some(ChunkReadInfo {
191 range: start..end,
192 digest: *digest,
193 })
194 }
195
196 fn compute_csum(&self) -> ([u8; 32], u64) {
197 let mut csum = openssl::sha::Sha256::new();
198 let mut chunk_end = 0;
199 for pos in 0..self.index_count() {
200 let info = self.chunk_info(pos).unwrap();
201 chunk_end = info.range.end;
202 csum.update(&info.digest);
203 }
204 let csum = csum.finish();
205
206 (csum, chunk_end)
207 }
208
209 fn chunk_from_offset(&self, offset: u64) -> Option<(usize, u64)> {
210 if offset >= self.size {
211 return None;
212 }
213
214 Some((
215 (offset / self.chunk_size as u64) as usize,
216 offset & (self.chunk_size - 1) as u64, // fast modulo, valid for 2^x chunk_size
217 ))
218 }
219 }
220
221 pub struct FixedIndexWriter {
222 store: Arc<ChunkStore>,
223 file: File,
224 _lock: tools::ProcessLockSharedGuard,
225 filename: PathBuf,
226 tmp_filename: PathBuf,
227 chunk_size: usize,
228 size: usize,
229 index_length: usize,
230 index: *mut u8,
231 pub uuid: [u8; 16],
232 pub ctime: i64,
233 }
234
235 // `index` is mmap()ed which cannot be thread-local so should be sendable
236 unsafe impl Send for FixedIndexWriter {}
237
238 impl Drop for FixedIndexWriter {
239 fn drop(&mut self) {
240 let _ = std::fs::remove_file(&self.tmp_filename); // ignore errors
241 if let Err(err) = self.unmap() {
242 eprintln!("Unable to unmap file {:?} - {}", self.tmp_filename, err);
243 }
244 }
245 }
246
247 impl FixedIndexWriter {
248 #[allow(clippy::cast_ptr_alignment)]
249 pub fn create(
250 store: Arc<ChunkStore>,
251 path: &Path,
252 size: usize,
253 chunk_size: usize,
254 ) -> Result<Self, Error> {
255 let shared_lock = store.try_shared_lock()?;
256
257 let full_path = store.relative_path(path);
258 let mut tmp_path = full_path.clone();
259 tmp_path.set_extension("tmp_fidx");
260
261 let mut file = std::fs::OpenOptions::new()
262 .create(true)
263 .truncate(true)
264 .read(true)
265 .write(true)
266 .open(&tmp_path)?;
267
268 let header_size = std::mem::size_of::<FixedIndexHeader>();
269
270 // todo: use static assertion when available in rust
271 if header_size != 4096 {
272 panic!("got unexpected header size");
273 }
274
275 let ctime = proxmox::tools::time::epoch_i64();
276
277 let uuid = Uuid::generate();
278
279 let buffer = vec![0u8; header_size];
280 let header = unsafe { &mut *(buffer.as_ptr() as *mut FixedIndexHeader) };
281
282 header.magic = super::FIXED_SIZED_CHUNK_INDEX_1_0;
283 header.ctime = i64::to_le(ctime);
284 header.size = u64::to_le(size as u64);
285 header.chunk_size = u64::to_le(chunk_size as u64);
286 header.uuid = *uuid.as_bytes();
287
288 header.index_csum = [0u8; 32];
289
290 file.write_all(&buffer)?;
291
292 let index_length = (size + chunk_size - 1) / chunk_size;
293 let index_size = index_length * 32;
294 nix::unistd::ftruncate(file.as_raw_fd(), (header_size + index_size) as i64)?;
295
296 let data = unsafe {
297 nix::sys::mman::mmap(
298 std::ptr::null_mut(),
299 index_size,
300 nix::sys::mman::ProtFlags::PROT_READ | nix::sys::mman::ProtFlags::PROT_WRITE,
301 nix::sys::mman::MapFlags::MAP_SHARED,
302 file.as_raw_fd(),
303 header_size as i64,
304 )
305 }? as *mut u8;
306
307 Ok(Self {
308 store,
309 file,
310 _lock: shared_lock,
311 filename: full_path,
312 tmp_filename: tmp_path,
313 chunk_size,
314 size,
315 index_length,
316 index: data,
317 ctime,
318 uuid: *uuid.as_bytes(),
319 })
320 }
321
322 pub fn index_length(&self) -> usize {
323 self.index_length
324 }
325
326 fn unmap(&mut self) -> Result<(), Error> {
327 if self.index == std::ptr::null_mut() {
328 return Ok(());
329 }
330
331 let index_size = self.index_length * 32;
332
333 if let Err(err) =
334 unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
335 {
336 bail!("unmap file {:?} failed - {}", self.tmp_filename, err);
337 }
338
339 self.index = std::ptr::null_mut();
340
341 Ok(())
342 }
343
344 pub fn close(&mut self) -> Result<[u8; 32], Error> {
345 if self.index == std::ptr::null_mut() {
346 bail!("cannot close already closed index file.");
347 }
348
349 let index_size = self.index_length * 32;
350 let data = unsafe { std::slice::from_raw_parts(self.index, index_size) };
351 let index_csum = openssl::sha::sha256(data);
352
353 self.unmap()?;
354
355 let csum_offset = proxmox::offsetof!(FixedIndexHeader, index_csum);
356 self.file.seek(SeekFrom::Start(csum_offset as u64))?;
357 self.file.write_all(&index_csum)?;
358 self.file.flush()?;
359
360 if let Err(err) = std::fs::rename(&self.tmp_filename, &self.filename) {
361 bail!("Atomic rename file {:?} failed - {}", self.filename, err);
362 }
363
364 Ok(index_csum)
365 }
366
367 pub fn check_chunk_alignment(&self, offset: usize, chunk_len: usize) -> Result<usize, Error> {
368 if offset < chunk_len {
369 bail!("got chunk with small offset ({} < {}", offset, chunk_len);
370 }
371
372 let pos = offset - chunk_len;
373
374 if offset > self.size {
375 bail!("chunk data exceeds size ({} >= {})", offset, self.size);
376 }
377
378 // last chunk can be smaller
379 if ((offset != self.size) && (chunk_len != self.chunk_size))
380 || (chunk_len > self.chunk_size)
381 || (chunk_len == 0)
382 {
383 bail!(
384 "chunk with unexpected length ({} != {}",
385 chunk_len,
386 self.chunk_size
387 );
388 }
389
390 if pos & (self.chunk_size - 1) != 0 {
391 bail!("got unaligned chunk (pos = {})", pos);
392 }
393
394 Ok(pos / self.chunk_size)
395 }
396
397 // Note: We want to add data out of order, so do not assume any order here.
398 pub fn add_chunk(&mut self, chunk_info: &ChunkInfo, stat: &mut ChunkStat) -> Result<(), Error> {
399 let chunk_len = chunk_info.chunk_len as usize;
400 let offset = chunk_info.offset as usize; // end of chunk
401
402 let idx = self.check_chunk_alignment(offset, chunk_len)?;
403
404 let (is_duplicate, compressed_size) = self
405 .store
406 .insert_chunk(&chunk_info.chunk, &chunk_info.digest)?;
407
408 stat.chunk_count += 1;
409 stat.compressed_size += compressed_size;
410
411 let digest = &chunk_info.digest;
412
413 println!(
414 "ADD CHUNK {} {} {}% {} {}",
415 idx,
416 chunk_len,
417 (compressed_size * 100) / (chunk_len as u64),
418 is_duplicate,
419 proxmox::tools::digest_to_hex(digest)
420 );
421
422 if is_duplicate {
423 stat.duplicate_chunks += 1;
424 } else {
425 stat.disk_size += compressed_size;
426 }
427
428 self.add_digest(idx, digest)
429 }
430
431 pub fn add_digest(&mut self, index: usize, digest: &[u8; 32]) -> Result<(), Error> {
432 if index >= self.index_length {
433 bail!(
434 "add digest failed - index out of range ({} >= {})",
435 index,
436 self.index_length
437 );
438 }
439
440 if self.index == std::ptr::null_mut() {
441 bail!("cannot write to closed index file.");
442 }
443
444 let index_pos = index * 32;
445 unsafe {
446 let dst = self.index.add(index_pos);
447 dst.copy_from_nonoverlapping(digest.as_ptr(), 32);
448 }
449
450 Ok(())
451 }
452
453 pub fn clone_data_from(&mut self, reader: &FixedIndexReader) -> Result<(), Error> {
454 if self.index_length != reader.index_count() {
455 bail!("clone_data_from failed - index sizes not equal");
456 }
457
458 for i in 0..self.index_length {
459 self.add_digest(i, reader.index_digest(i).unwrap())?;
460 }
461
462 Ok(())
463 }
464 }