]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/fixed_index.rs
cleanup: remove dead code
[proxmox-backup.git] / src / backup / fixed_index.rs
CommitLineData
606ce64b 1use failure::*;
afb4cd28 2use std::io::{Seek, SeekFrom};
0a51fe00 3use std::convert::TryInto;
606ce64b 4
22968600 5use crate::tools;
7bc1d727 6use super::IndexFile;
7e336555 7use super::chunk_stat::*;
606ce64b
DM
8use super::chunk_store::*;
9
150f1bd8 10use std::sync::Arc;
b526bd14 11use std::io::Write;
10eea49d 12use std::fs::File;
606ce64b
DM
13use std::path::{Path, PathBuf};
14use std::os::unix::io::AsRawFd;
d13e3745 15use uuid::Uuid;
4818c8b6 16use chrono::{Local, TimeZone};
afb4cd28 17
f98ac774 18use super::ChunkInfo;
afb4cd28 19use super::read_chunk::*;
606ce64b 20
4dc79bb1
WB
21use proxmox::tools::io::ReadExt;
22
8e39232a 23/// Header format definition for fixed index files (`.fidx`)
d13e3745 24#[repr(C)]
91a905b6 25pub struct FixedIndexHeader {
a7dd4830 26 pub magic: [u8; 8],
d13e3745 27 pub uuid: [u8; 16],
5e5b7f1c 28 pub ctime: u64,
9335d74e
DM
29 /// Sha256 over the index ``SHA256(digest1||digest2||...)``
30 pub index_csum: [u8; 32],
a7dd4830
DM
31 pub size: u64,
32 pub chunk_size: u64,
33 reserved: [u8; 4016], // overall size is one page (4096 bytes)
d13e3745 34}
ccdf3ad1 35proxmox::tools::static_assert_size!(FixedIndexHeader, 4096);
606ce64b
DM
36
37// split image into fixed size chunks
38
91a905b6 39pub struct FixedIndexReader {
10eea49d 40 _file: File,
29ae5c86 41 pub chunk_size: usize,
b46c3fad 42 pub size: u64,
e1225de4 43 index_length: usize,
4818c8b6 44 index: *mut u8,
9f49fe1d
DM
45 pub uuid: [u8; 16],
46 pub ctime: u64,
9335d74e 47 pub index_csum: [u8; 32],
4818c8b6
DM
48}
49
5be4065b
WB
50// `index` is mmap()ed which cannot be thread-local so should be sendable
51unsafe impl Send for FixedIndexReader {}
5c1130df 52unsafe impl Sync for FixedIndexReader {}
5be4065b 53
91a905b6 54impl Drop for FixedIndexReader {
4818c8b6
DM
55
56 fn drop(&mut self) {
57 if let Err(err) = self.unmap() {
a7c72ad9 58 eprintln!("Unable to unmap file - {}", err);
4818c8b6
DM
59 }
60 }
61}
62
91a905b6 63impl FixedIndexReader {
4818c8b6 64
a7c72ad9 65 pub fn open(path: &Path) -> Result<Self, Error> {
4818c8b6 66
a7c72ad9
DM
67 File::open(path)
68 .map_err(Error::from)
69 .and_then(|file| Self::new(file))
70 .map_err(|err| format_err!("Unable to open fixed index {:?} - {}", path, err))
71 }
4818c8b6 72
a7c72ad9 73 pub fn new(mut file: std::fs::File) -> Result<Self, Error> {
4818c8b6 74
c597a92c 75 if let Err(err) = nix::fcntl::flock(file.as_raw_fd(), nix::fcntl::FlockArg::LockSharedNonblock) {
a7c72ad9 76 bail!("unable to get shared lock - {}", err);
c597a92c
DM
77 }
78
afb4cd28
DM
79 file.seek(SeekFrom::Start(0))?;
80
91a905b6 81 let header_size = std::mem::size_of::<FixedIndexHeader>();
4dc79bb1 82 let header: Box<FixedIndexHeader> = unsafe { file.read_host_value_boxed()? };
4818c8b6 83
a7dd4830 84 if header.magic != super::FIXED_SIZED_CHUNK_INDEX_1_0 {
a7c72ad9 85 bail!("got unknown magic number");
a360f6fa
DM
86 }
87
b46c3fad 88 let size = u64::from_le(header.size);
48d0d356 89 let ctime = u64::from_le(header.ctime);
b46c3fad 90 let chunk_size = u64::from_le(header.chunk_size);
4818c8b6 91
b46c3fad 92 let index_length = ((size + chunk_size - 1)/chunk_size) as usize;
e1225de4 93 let index_size = index_length*32;
4818c8b6 94
0b8e75ed
DM
95 let rawfd = file.as_raw_fd();
96
97 let stat = match nix::sys::stat::fstat(rawfd) {
98 Ok(stat) => stat,
a7c72ad9 99 Err(err) => bail!("fstat failed - {}", err),
0b8e75ed
DM
100 };
101
ddbdf80d 102 let expected_index_size = (stat.st_size as usize) - header_size;
0b8e75ed 103 if index_size != expected_index_size {
a7c72ad9 104 bail!("got unexpected file size ({} != {})", index_size, expected_index_size);
0b8e75ed 105 }
4818c8b6
DM
106
107 let data = unsafe { nix::sys::mman::mmap(
108 std::ptr::null_mut(),
109 index_size,
110 nix::sys::mman::ProtFlags::PROT_READ,
111 nix::sys::mman::MapFlags::MAP_PRIVATE,
112 file.as_raw_fd(),
113 header_size as i64) }? as *mut u8;
114
115 Ok(Self {
10eea49d 116 _file: file,
b46c3fad 117 chunk_size: chunk_size as usize,
4818c8b6 118 size,
e1225de4 119 index_length,
4818c8b6
DM
120 index: data,
121 ctime,
122 uuid: header.uuid,
9335d74e 123 index_csum: header.index_csum,
4818c8b6
DM
124 })
125 }
126
127 fn unmap(&mut self) -> Result<(), Error> {
128
129 if self.index == std::ptr::null_mut() { return Ok(()); }
130
e1225de4 131 let index_size = self.index_length*32;
4818c8b6
DM
132
133 if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
a7c72ad9 134 bail!("unmap file failed - {}", err);
4818c8b6
DM
135 }
136
137 self.index = std::ptr::null_mut();
138
139 Ok(())
140 }
141
afb4cd28
DM
142 pub fn chunk_info(&self, pos: usize) -> Result<(u64, u64, [u8; 32]), Error> {
143
144 if pos >= self.index_length {
145 bail!("chunk index out of range");
146 }
147 let start = (pos * self.chunk_size) as u64;
148 let mut end = start + self.chunk_size as u64;
149
150 if end > self.size {
151 end = self.size;
152 }
153
5e58e1bb
WB
154 let mut digest = std::mem::MaybeUninit::<[u8; 32]>::uninit();
155 unsafe {
156 std::ptr::copy_nonoverlapping(
157 self.index.add(pos*32),
158 (*digest.as_mut_ptr()).as_mut_ptr(),
159 32,
160 );
161 }
afb4cd28 162
5e58e1bb 163 Ok((start, end, unsafe { digest.assume_init() }))
afb4cd28
DM
164 }
165
0a51fe00
DM
166 #[inline]
167 fn chunk_digest(&self, pos: usize) -> &[u8; 32] {
168 if pos >= self.index_length {
169 panic!("chunk index out of range");
170 }
171 let slice = unsafe { std::slice::from_raw_parts(self.index.add(pos*32), 32) };
172 slice.try_into().unwrap()
173 }
174
afb4cd28
DM
175 #[inline]
176 fn chunk_end(&self, pos: usize) -> u64 {
177 if pos >= self.index_length {
178 panic!("chunk index out of range");
179 }
180
181 let end = ((pos+1) * self.chunk_size) as u64;
182 if end > self.size {
183 self.size
184 } else {
185 end
186 }
187 }
188
0a51fe00
DM
189 /// Compute checksum and data size
190 pub fn compute_csum(&self) -> ([u8; 32], u64) {
191
192 let mut csum = openssl::sha::Sha256::new();
193 let mut chunk_end = 0;
194 for pos in 0..self.index_length {
195 chunk_end = ((pos+1) * self.chunk_size) as u64;
196 let digest = self.chunk_digest(pos);
0a51fe00
DM
197 csum.update(digest);
198 }
199 let csum = csum.finish();
200
201 (csum, chunk_end)
202 }
203
4818c8b6 204 pub fn print_info(&self) {
4818c8b6
DM
205 println!("Size: {}", self.size);
206 println!("ChunkSize: {}", self.chunk_size);
207 println!("CTime: {}", Local.timestamp(self.ctime as i64, 0).format("%c"));
208 println!("UUID: {:?}", self.uuid);
209 }
210}
211
7bc1d727
WB
212impl IndexFile for FixedIndexReader {
213 fn index_count(&self) -> usize {
e1225de4 214 self.index_length
7bc1d727
WB
215 }
216
217 fn index_digest(&self, pos: usize) -> Option<&[u8; 32]> {
e1225de4 218 if pos >= self.index_length {
7bc1d727
WB
219 None
220 } else {
221 Some(unsafe { std::mem::transmute(self.index.add(pos*32)) })
222 }
223 }
a660978c
DM
224
225 fn index_bytes(&self) -> u64 {
b46c3fad 226 self.size
a660978c 227 }
7bc1d727
WB
228}
229
91a905b6 230pub struct FixedIndexWriter {
150f1bd8 231 store: Arc<ChunkStore>,
9335d74e 232 file: File,
43b13033 233 _lock: tools::ProcessLockSharedGuard,
4fbb72a8
DM
234 filename: PathBuf,
235 tmp_filename: PathBuf,
606ce64b
DM
236 chunk_size: usize,
237 size: usize,
e1225de4 238 index_length: usize,
606ce64b 239 index: *mut u8,
9f49fe1d
DM
240 pub uuid: [u8; 16],
241 pub ctime: u64,
606ce64b
DM
242}
243
c3bb97e5
WB
244// `index` is mmap()ed which cannot be thread-local so should be sendable
245unsafe impl Send for FixedIndexWriter {}
246
91a905b6 247impl Drop for FixedIndexWriter {
4fbb72a8
DM
248
249 fn drop(&mut self) {
250 let _ = std::fs::remove_file(&self.tmp_filename); // ignore errors
251 if let Err(err) = self.unmap() {
0cd9d420 252 eprintln!("Unable to unmap file {:?} - {}", self.tmp_filename, err);
4fbb72a8
DM
253 }
254 }
255}
256
91a905b6 257impl FixedIndexWriter {
606ce64b 258
150f1bd8 259 pub fn create(store: Arc<ChunkStore>, path: &Path, size: usize, chunk_size: usize) -> Result<Self, Error> {
606ce64b 260
43b13033
DM
261 let shared_lock = store.try_shared_lock()?;
262
606ce64b 263 let full_path = store.relative_path(path);
4fbb72a8 264 let mut tmp_path = full_path.clone();
91a905b6 265 tmp_path.set_extension("tmp_fidx");
606ce64b
DM
266
267 let mut file = std::fs::OpenOptions::new()
d13e3745 268 .create(true).truncate(true)
606ce64b
DM
269 .read(true)
270 .write(true)
4fbb72a8 271 .open(&tmp_path)?;
606ce64b 272
91a905b6 273 let header_size = std::mem::size_of::<FixedIndexHeader>();
d13e3745
DM
274
275 // todo: use static assertion when available in rust
276 if header_size != 4096 { panic!("got unexpected header size"); }
277
278 let ctime = std::time::SystemTime::now().duration_since(
5e5b7f1c 279 std::time::SystemTime::UNIX_EPOCH)?.as_secs();
d13e3745
DM
280
281 let uuid = Uuid::new_v4();
282
0cd9d420 283 let buffer = vec![0u8; header_size];
91a905b6 284 let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
d13e3745 285
a7dd4830 286 header.magic = super::FIXED_SIZED_CHUNK_INDEX_1_0;
48d0d356
DM
287 header.ctime = u64::to_le(ctime);
288 header.size = u64::to_le(size as u64);
289 header.chunk_size = u64::to_le(chunk_size as u64);
d13e3745
DM
290 header.uuid = *uuid.as_bytes();
291
9335d74e
DM
292 header.index_csum = [0u8; 32];
293
5e5b7f1c 294 file.write_all(&buffer)?;
d13e3745 295
e1225de4
DM
296 let index_length = (size + chunk_size - 1)/chunk_size;
297 let index_size = index_length*32;
d13e3745
DM
298 nix::unistd::ftruncate(file.as_raw_fd(), (header_size + index_size) as i64)?;
299
606ce64b
DM
300 let data = unsafe { nix::sys::mman::mmap(
301 std::ptr::null_mut(),
302 index_size,
303 nix::sys::mman::ProtFlags::PROT_READ | nix::sys::mman::ProtFlags::PROT_WRITE,
304 nix::sys::mman::MapFlags::MAP_SHARED,
305 file.as_raw_fd(),
d13e3745
DM
306 header_size as i64) }? as *mut u8;
307
606ce64b
DM
308 Ok(Self {
309 store,
9335d74e 310 file,
43b13033 311 _lock: shared_lock,
4fbb72a8
DM
312 filename: full_path,
313 tmp_filename: tmp_path,
606ce64b
DM
314 chunk_size,
315 size,
e1225de4 316 index_length,
606ce64b 317 index: data,
d13e3745
DM
318 ctime,
319 uuid: *uuid.as_bytes(),
606ce64b
DM
320 })
321 }
322
006f3ff4
DM
323 pub fn index_length(&self) -> usize {
324 self.index_length
325 }
326
4fbb72a8
DM
327 fn unmap(&mut self) -> Result<(), Error> {
328
329 if self.index == std::ptr::null_mut() { return Ok(()); }
330
e1225de4 331 let index_size = self.index_length*32;
4fbb72a8
DM
332
333 if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
0cd9d420 334 bail!("unmap file {:?} failed - {}", self.tmp_filename, err);
4fbb72a8
DM
335 }
336
337 self.index = std::ptr::null_mut();
338
339 Ok(())
340 }
341
9335d74e 342 pub fn close(&mut self) -> Result<[u8; 32], Error> {
4fbb72a8
DM
343
344 if self.index == std::ptr::null_mut() { bail!("cannot close already closed index file."); }
345
9335d74e
DM
346 let index_size = self.index_length*32;
347 let data = unsafe { std::slice::from_raw_parts(self.index, index_size) };
348 let index_csum = openssl::sha::sha256(data);
349
4fbb72a8
DM
350 self.unmap()?;
351
9335d74e 352 let csum_offset = proxmox::tools::offsetof!(FixedIndexHeader, index_csum);
afb4cd28 353 self.file.seek(SeekFrom::Start(csum_offset as u64))?;
9335d74e
DM
354 self.file.write_all(&index_csum)?;
355 self.file.flush()?;
356
4fbb72a8
DM
357 if let Err(err) = std::fs::rename(&self.tmp_filename, &self.filename) {
358 bail!("Atomic rename file {:?} failed - {}", self.filename, err);
359 }
360
9335d74e 361 Ok(index_csum)
4fbb72a8
DM
362 }
363
5e04ec70 364 pub fn check_chunk_alignment(&self, offset: usize, chunk_len: usize) -> Result<usize, Error> {
f98ac774 365
5e04ec70
DM
366 if offset < chunk_len {
367 bail!("got chunk with small offset ({} < {}", offset, chunk_len);
f98ac774
DM
368 }
369
5e04ec70 370 let pos = offset - chunk_len;
606ce64b 371
5e04ec70
DM
372 if offset > self.size {
373 bail!("chunk data exceeds size ({} >= {})", offset, self.size);
606ce64b
DM
374 }
375
376 // last chunk can be smaller
5e04ec70 377 if ((offset != self.size) && (chunk_len != self.chunk_size)) ||
f98ac774 378 (chunk_len > self.chunk_size) || (chunk_len == 0) {
5e04ec70 379 bail!("chunk with unexpected length ({} != {}", chunk_len, self.chunk_size);
606ce64b
DM
380 }
381
5e04ec70
DM
382 if pos & (self.chunk_size-1) != 0 {
383 bail!("got unaligned chunk (pos = {})", pos);
f98ac774
DM
384 }
385
5e04ec70
DM
386 Ok(pos / self.chunk_size)
387 }
388
389 // Note: We want to add data out of order, so do not assume any order here.
390 pub fn add_chunk(&mut self, chunk_info: &ChunkInfo, stat: &mut ChunkStat) -> Result<(), Error> {
391
392 let chunk_len = chunk_info.chunk_len as usize;
393 let offset = chunk_info.offset as usize; // end of chunk
394
395 let idx = self.check_chunk_alignment(offset, chunk_len)?;
396
f98ac774 397 let (is_duplicate, compressed_size) = self.store.insert_chunk(&chunk_info.chunk)?;
798f7fa0 398
cb0708dd
DM
399 stat.chunk_count += 1;
400 stat.compressed_size += compressed_size;
606ce64b 401
f98ac774
DM
402 let digest = chunk_info.chunk.digest();
403
5e04ec70 404 println!("ADD CHUNK {} {} {}% {} {}", idx, chunk_len,
bffd40d6 405 (compressed_size*100)/(chunk_len as u64), is_duplicate, proxmox::tools::digest_to_hex(digest));
798f7fa0
DM
406
407 if is_duplicate {
cb0708dd 408 stat.duplicate_chunks += 1;
798f7fa0 409 } else {
cb0708dd 410 stat.disk_size += compressed_size;
798f7fa0 411 }
606ce64b 412
5e04ec70 413 self.add_digest(idx, digest)
e3062f87
WB
414 }
415
416 pub fn add_digest(&mut self, index: usize, digest: &[u8; 32]) -> Result<(), Error> {
01af11f3 417
fc14b849
DM
418 if index >= self.index_length {
419 bail!("add digest failed - index out of range ({} >= {})", index, self.index_length);
420 }
421
01af11f3
DM
422 if self.index == std::ptr::null_mut() { bail!("cannot write to closed index file."); }
423
e3062f87 424 let index_pos = index*32;
606ce64b
DM
425 unsafe {
426 let dst = self.index.add(index_pos);
427 dst.copy_from_nonoverlapping(digest.as_ptr(), 32);
428 }
429
430 Ok(())
431 }
432}
afb4cd28
DM
433
434pub struct BufferedFixedReader<S> {
435 store: S,
436 index: FixedIndexReader,
437 archive_size: u64,
438 read_buffer: Vec<u8>,
439 buffered_chunk_idx: usize,
440 buffered_chunk_start: u64,
441 read_offset: u64,
442}
443
444impl <S: ReadChunk> BufferedFixedReader<S> {
445
446 pub fn new(index: FixedIndexReader, store: S) -> Self {
447
448 let archive_size = index.size;
449 Self {
450 store,
653b1ca1
WB
451 index,
452 archive_size,
afb4cd28
DM
453 read_buffer: Vec::with_capacity(1024*1024),
454 buffered_chunk_idx: 0,
455 buffered_chunk_start: 0,
456 read_offset: 0,
457 }
458 }
459
460 pub fn archive_size(&self) -> u64 { self.archive_size }
461
462 fn buffer_chunk(&mut self, idx: usize) -> Result<(), Error> {
463
464 let index = &self.index;
465 let (start, end, digest) = index.chunk_info(idx)?;
466
467 // fixme: avoid copy
468
469 let data = self.store.read_chunk(&digest)?;
470
471 if (end - start) != data.len() as u64 {
472 bail!("read chunk with wrong size ({} != {}", (end - start), data.len());
473 }
474
475 self.read_buffer.clear();
476 self.read_buffer.extend_from_slice(&data);
477
478 self.buffered_chunk_idx = idx;
479
480 self.buffered_chunk_start = start as u64;
481 //println!("BUFFER {} {}", self.buffered_chunk_start, end);
482 Ok(())
483 }
484}
485
486impl <S: ReadChunk> crate::tools::BufferedRead for BufferedFixedReader<S> {
487
488 fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error> {
489
490 if offset == self.archive_size { return Ok(&self.read_buffer[0..0]); }
491
492 let buffer_len = self.read_buffer.len();
493 let index = &self.index;
494
495 // optimization for sequential read
496 if buffer_len > 0 &&
497 ((self.buffered_chunk_idx + 1) < index.index_length) &&
498 (offset >= (self.buffered_chunk_start + (self.read_buffer.len() as u64)))
499 {
500 let next_idx = self.buffered_chunk_idx + 1;
501 let next_end = index.chunk_end(next_idx);
502 if offset < next_end {
503 self.buffer_chunk(next_idx)?;
504 let buffer_offset = (offset - self.buffered_chunk_start) as usize;
505 return Ok(&self.read_buffer[buffer_offset..]);
506 }
507 }
508
509 if (buffer_len == 0) ||
510 (offset < self.buffered_chunk_start) ||
511 (offset >= (self.buffered_chunk_start + (self.read_buffer.len() as u64)))
512 {
513 let idx = (offset / index.chunk_size as u64) as usize;
514 self.buffer_chunk(idx)?;
515 }
516
517 let buffer_offset = (offset - self.buffered_chunk_start) as usize;
518 Ok(&self.read_buffer[buffer_offset..])
519 }
520}
521
522impl <S: ReadChunk> std::io::Read for BufferedFixedReader<S> {
523
524 fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
525
526 use std::io::{Error, ErrorKind};
527 use crate::tools::BufferedRead;
528
529 let data = match self.buffered_read(self.read_offset) {
530 Ok(v) => v,
531 Err(err) => return Err(Error::new(ErrorKind::Other, err.to_string())),
532 };
533
534 let n = if data.len() > buf.len() { buf.len() } else { data.len() };
535
536 unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), buf.as_mut_ptr(), n); }
537
538 self.read_offset += n as u64;
539
540 return Ok(n);
541 }
542}
543
544impl <S: ReadChunk> Seek for BufferedFixedReader<S> {
545
546 fn seek(&mut self, pos: SeekFrom) -> Result<u64, std::io::Error> {
547
548 let new_offset = match pos {
549 SeekFrom::Start(start_offset) => start_offset as i64,
550 SeekFrom::End(end_offset) => (self.archive_size as i64)+ end_offset,
551 SeekFrom::Current(offset) => (self.read_offset as i64) + offset,
552 };
553
554 use std::io::{Error, ErrorKind};
555 if (new_offset < 0) || (new_offset > (self.archive_size as i64)) {
556 return Err(Error::new(
557 ErrorKind::Other,
558 format!("seek is out of range {} ([0..{}])", new_offset, self.archive_size)));
559 }
560 self.read_offset = new_offset as u64;
561
562 Ok(self.read_offset)
563 }
564}