1 use anyhow
::{bail, format_err, Error}
;
2 use std
::io
::{Seek, SeekFrom}
;
4 use super::chunk_stat
::*;
5 use super::chunk_store
::*;
6 use super::{IndexFile, ChunkReadInfo}
;
7 use crate::tools
::{self, epoch_now_u64}
;
9 use chrono
::{Local, LocalResult, TimeZone}
;
12 use std
::os
::unix
::io
::AsRawFd
;
13 use std
::path
::{Path, PathBuf}
;
18 use proxmox
::tools
::io
::ReadExt
;
19 use proxmox
::tools
::Uuid
;
21 /// Header format definition for fixed index files (`.fidx`)
23 pub struct FixedIndexHeader
{
27 /// Sha256 over the index ``SHA256(digest1||digest2||...)``
28 pub index_csum
: [u8; 32],
31 reserved
: [u8; 4016], // overall size is one page (4096 bytes)
33 proxmox
::static_assert_size
!(FixedIndexHeader
, 4096);
35 // split image into fixed size chunks
37 pub struct FixedIndexReader
{
39 pub chunk_size
: usize,
45 pub index_csum
: [u8; 32],
48 // `index` is mmap()ed which cannot be thread-local so should be sendable
49 unsafe impl Send
for FixedIndexReader {}
50 unsafe impl Sync
for FixedIndexReader {}
52 impl Drop
for FixedIndexReader
{
54 if let Err(err
) = self.unmap() {
55 eprintln
!("Unable to unmap file - {}", err
);
60 impl FixedIndexReader
{
61 pub fn open(path
: &Path
) -> Result
<Self, Error
> {
64 .and_then(|file
| Self::new(file
))
65 .map_err(|err
| format_err
!("Unable to open fixed index {:?} - {}", path
, err
))
68 pub fn new(mut file
: std
::fs
::File
) -> Result
<Self, Error
> {
70 nix
::fcntl
::flock(file
.as_raw_fd(), nix
::fcntl
::FlockArg
::LockSharedNonblock
)
72 bail
!("unable to get shared lock - {}", err
);
75 file
.seek(SeekFrom
::Start(0))?
;
77 let header_size
= std
::mem
::size_of
::<FixedIndexHeader
>();
78 let header
: Box
<FixedIndexHeader
> = unsafe { file.read_host_value_boxed()? }
;
80 if header
.magic
!= super::FIXED_SIZED_CHUNK_INDEX_1_0
{
81 bail
!("got unknown magic number");
84 let size
= u64::from_le(header
.size
);
85 let ctime
= u64::from_le(header
.ctime
);
86 let chunk_size
= u64::from_le(header
.chunk_size
);
88 let index_length
= ((size
+ chunk_size
- 1) / chunk_size
) as usize;
89 let index_size
= index_length
* 32;
91 let rawfd
= file
.as_raw_fd();
93 let stat
= match nix
::sys
::stat
::fstat(rawfd
) {
95 Err(err
) => bail
!("fstat failed - {}", err
),
98 let expected_index_size
= (stat
.st_size
as usize) - header_size
;
99 if index_size
!= expected_index_size
{
101 "got unexpected file size ({} != {})",
108 nix
::sys
::mman
::mmap(
109 std
::ptr
::null_mut(),
111 nix
::sys
::mman
::ProtFlags
::PROT_READ
,
112 nix
::sys
::mman
::MapFlags
::MAP_PRIVATE
,
120 chunk_size
: chunk_size
as usize,
126 index_csum
: header
.index_csum
,
130 fn unmap(&mut self) -> Result
<(), Error
> {
131 if self.index
== std
::ptr
::null_mut() {
135 let index_size
= self.index_length
* 32;
138 unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
140 bail
!("unmap file failed - {}", err
);
143 self.index
= std
::ptr
::null_mut();
148 pub fn print_info(&self) {
149 println
!("Size: {}", self.size
);
150 println
!("ChunkSize: {}", self.chunk_size
);
153 match Local
.timestamp_opt(self.ctime
as i64, 0) {
154 LocalResult
::Single(ctime
) => ctime
.format("%c").to_string(),
155 _
=> (self.ctime
as i64).to_string(),
158 println
!("UUID: {:?}", self.uuid
);
162 impl IndexFile
for FixedIndexReader
{
163 fn index_count(&self) -> usize {
167 fn index_digest(&self, pos
: usize) -> Option
<&[u8; 32]> {
168 if pos
>= self.index_length
{
171 Some(unsafe { std::mem::transmute(self.index.add(pos * 32)) }
)
175 fn index_bytes(&self) -> u64 {
179 fn chunk_info(&self, pos
: usize) -> Option
<ChunkReadInfo
> {
180 if pos
>= self.index_length
{
184 let start
= (pos
* self.chunk_size
) as u64;
185 let mut end
= start
+ self.chunk_size
as u64;
191 let digest
= self.index_digest(pos
).unwrap();
198 fn compute_csum(&self) -> ([u8; 32], u64) {
199 let mut csum
= openssl
::sha
::Sha256
::new();
200 let mut chunk_end
= 0;
201 for pos
in 0..self.index_count() {
202 let info
= self.chunk_info(pos
).unwrap();
203 chunk_end
= info
.range
.end
;
204 csum
.update(&info
.digest
);
206 let csum
= csum
.finish();
211 fn chunk_from_offset(&self, offset
: u64) -> Option
<(usize, u64)> {
212 if offset
>= self.size
{
217 (offset
/ self.chunk_size
as u64) as usize,
218 offset
& (self.chunk_size
- 1) as u64 // fast modulo, valid for 2^x chunk_size
223 pub struct FixedIndexWriter
{
224 store
: Arc
<ChunkStore
>,
226 _lock
: tools
::ProcessLockSharedGuard
,
228 tmp_filename
: PathBuf
,
237 // `index` is mmap()ed which cannot be thread-local so should be sendable
238 unsafe impl Send
for FixedIndexWriter {}
240 impl Drop
for FixedIndexWriter
{
242 let _
= std
::fs
::remove_file(&self.tmp_filename
); // ignore errors
243 if let Err(err
) = self.unmap() {
244 eprintln
!("Unable to unmap file {:?} - {}", self.tmp_filename
, err
);
249 impl FixedIndexWriter
{
250 #[allow(clippy::cast_ptr_alignment)]
252 store
: Arc
<ChunkStore
>,
256 ) -> Result
<Self, Error
> {
257 let shared_lock
= store
.try_shared_lock()?
;
259 let full_path
= store
.relative_path(path
);
260 let mut tmp_path
= full_path
.clone();
261 tmp_path
.set_extension("tmp_fidx");
263 let mut file
= std
::fs
::OpenOptions
::new()
270 let header_size
= std
::mem
::size_of
::<FixedIndexHeader
>();
272 // todo: use static assertion when available in rust
273 if header_size
!= 4096 {
274 panic
!("got unexpected header size");
277 let ctime
= epoch_now_u64()?
;
279 let uuid
= Uuid
::generate();
281 let buffer
= vec
![0u8; header_size
];
282 let header
= unsafe { &mut *(buffer.as_ptr() as *mut FixedIndexHeader) }
;
284 header
.magic
= super::FIXED_SIZED_CHUNK_INDEX_1_0
;
285 header
.ctime
= u64::to_le(ctime
);
286 header
.size
= u64::to_le(size
as u64);
287 header
.chunk_size
= u64::to_le(chunk_size
as u64);
288 header
.uuid
= *uuid
.as_bytes();
290 header
.index_csum
= [0u8; 32];
292 file
.write_all(&buffer
)?
;
294 let index_length
= (size
+ chunk_size
- 1) / chunk_size
;
295 let index_size
= index_length
* 32;
296 nix
::unistd
::ftruncate(file
.as_raw_fd(), (header_size
+ index_size
) as i64)?
;
299 nix
::sys
::mman
::mmap(
300 std
::ptr
::null_mut(),
302 nix
::sys
::mman
::ProtFlags
::PROT_READ
| nix
::sys
::mman
::ProtFlags
::PROT_WRITE
,
303 nix
::sys
::mman
::MapFlags
::MAP_SHARED
,
314 tmp_filename
: tmp_path
,
320 uuid
: *uuid
.as_bytes(),
324 pub fn index_length(&self) -> usize {
328 fn unmap(&mut self) -> Result
<(), Error
> {
329 if self.index
== std
::ptr
::null_mut() {
333 let index_size
= self.index_length
* 32;
336 unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
338 bail
!("unmap file {:?} failed - {}", self.tmp_filename
, err
);
341 self.index
= std
::ptr
::null_mut();
346 pub fn close(&mut self) -> Result
<[u8; 32], Error
> {
347 if self.index
== std
::ptr
::null_mut() {
348 bail
!("cannot close already closed index file.");
351 let index_size
= self.index_length
* 32;
352 let data
= unsafe { std::slice::from_raw_parts(self.index, index_size) }
;
353 let index_csum
= openssl
::sha
::sha256(data
);
357 let csum_offset
= proxmox
::offsetof!(FixedIndexHeader
, index_csum
);
358 self.file
.seek(SeekFrom
::Start(csum_offset
as u64))?
;
359 self.file
.write_all(&index_csum
)?
;
362 if let Err(err
) = std
::fs
::rename(&self.tmp_filename
, &self.filename
) {
363 bail
!("Atomic rename file {:?} failed - {}", self.filename
, err
);
369 pub fn check_chunk_alignment(&self, offset
: usize, chunk_len
: usize) -> Result
<usize, Error
> {
370 if offset
< chunk_len
{
371 bail
!("got chunk with small offset ({} < {}", offset
, chunk_len
);
374 let pos
= offset
- chunk_len
;
376 if offset
> self.size
{
377 bail
!("chunk data exceeds size ({} >= {})", offset
, self.size
);
380 // last chunk can be smaller
381 if ((offset
!= self.size
) && (chunk_len
!= self.chunk_size
))
382 || (chunk_len
> self.chunk_size
)
386 "chunk with unexpected length ({} != {}",
392 if pos
& (self.chunk_size
- 1) != 0 {
393 bail
!("got unaligned chunk (pos = {})", pos
);
396 Ok(pos
/ self.chunk_size
)
399 // Note: We want to add data out of order, so do not assume any order here.
400 pub fn add_chunk(&mut self, chunk_info
: &ChunkInfo
, stat
: &mut ChunkStat
) -> Result
<(), Error
> {
401 let chunk_len
= chunk_info
.chunk_len
as usize;
402 let offset
= chunk_info
.offset
as usize; // end of chunk
404 let idx
= self.check_chunk_alignment(offset
, chunk_len
)?
;
406 let (is_duplicate
, compressed_size
) = self
408 .insert_chunk(&chunk_info
.chunk
, &chunk_info
.digest
)?
;
410 stat
.chunk_count
+= 1;
411 stat
.compressed_size
+= compressed_size
;
413 let digest
= &chunk_info
.digest
;
416 "ADD CHUNK {} {} {}% {} {}",
419 (compressed_size
* 100) / (chunk_len
as u64),
421 proxmox
::tools
::digest_to_hex(digest
)
425 stat
.duplicate_chunks
+= 1;
427 stat
.disk_size
+= compressed_size
;
430 self.add_digest(idx
, digest
)
433 pub fn add_digest(&mut self, index
: usize, digest
: &[u8; 32]) -> Result
<(), Error
> {
434 if index
>= self.index_length
{
436 "add digest failed - index out of range ({} >= {})",
442 if self.index
== std
::ptr
::null_mut() {
443 bail
!("cannot write to closed index file.");
446 let index_pos
= index
* 32;
448 let dst
= self.index
.add(index_pos
);
449 dst
.copy_from_nonoverlapping(digest
.as_ptr(), 32);
455 pub fn clone_data_from(&mut self, reader
: &FixedIndexReader
) -> Result
<(), Error
> {
456 if self.index_length
!= reader
.index_count() {
457 bail
!("clone_data_from failed - index sizes not equal");
460 for i
in 0..self.index_length
{
461 self.add_digest(i
, reader
.index_digest(i
).unwrap())?
;