]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/backup/fixed_index.rs
clippy: remove unnecessary closures
[proxmox-backup.git] / src / backup / fixed_index.rs
index 526e72f1a5c5ee9afbcf5f8e56d7e448e40edc49..279537deb12b4a0e4943b90324f4eaf8891f9927 100644 (file)
@@ -1,34 +1,35 @@
-use failure::*;
+use anyhow::{bail, format_err, Error};
 use std::io::{Seek, SeekFrom};
 
-use crate::tools;
-use super::IndexFile;
 use super::chunk_stat::*;
 use super::chunk_store::*;
+use super::{ChunkReadInfo, IndexFile};
+use crate::tools;
 
-use std::sync::Arc;
-use std::io::{Read, Write};
 use std::fs::File;
-use std::path::{Path, PathBuf};
+use std::io::Write;
 use std::os::unix::io::AsRawFd;
-use uuid::Uuid;
-use chrono::{Local, TimeZone};
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
 
 use super::ChunkInfo;
-use super::read_chunk::*;
+
+use proxmox::tools::io::ReadExt;
+use proxmox::tools::Uuid;
 
 /// Header format definition for fixed index files (`.fidx`)
 #[repr(C)]
 pub struct FixedIndexHeader {
     pub magic: [u8; 8],
     pub uuid: [u8; 16],
-    pub ctime: u64,
+    pub ctime: i64,
     /// Sha256 over the index ``SHA256(digest1||digest2||...)``
     pub index_csum: [u8; 32],
     pub size: u64,
     pub chunk_size: u64,
     reserved: [u8; 4016], // overall size is one page (4096 bytes)
 }
+proxmox::static_assert_size!(FixedIndexHeader, 4096);
 
 // split image into fixed size chunks
 
@@ -39,15 +40,15 @@ pub struct FixedIndexReader {
     index_length: usize,
     index: *mut u8,
     pub uuid: [u8; 16],
-    pub ctime: u64,
+    pub ctime: i64,
     pub index_csum: [u8; 32],
 }
 
 // `index` is mmap()ed which cannot be thread-local so should be sendable
 unsafe impl Send for FixedIndexReader {}
+unsafe impl Sync for FixedIndexReader {}
 
 impl Drop for FixedIndexReader {
-
     fn drop(&mut self) {
         if let Err(err) = self.unmap() {
             eprintln!("Unable to unmap file - {}", err);
@@ -56,63 +57,61 @@ impl Drop for FixedIndexReader {
 }
 
 impl FixedIndexReader {
-
     pub fn open(path: &Path) -> Result<Self, Error> {
-
         File::open(path)
             .map_err(Error::from)
-            .and_then(|file| Self::new(file))
+            .and_then(Self::new)
             .map_err(|err| format_err!("Unable to open fixed index {:?} - {}", path, err))
     }
 
     pub fn new(mut file: std::fs::File) -> Result<Self, Error> {
-
-        if let Err(err) = nix::fcntl::flock(file.as_raw_fd(), nix::fcntl::FlockArg::LockSharedNonblock) {
-            bail!("unable to get shared lock - {}", err);
-        }
-
         file.seek(SeekFrom::Start(0))?;
 
         let header_size = std::mem::size_of::<FixedIndexHeader>();
 
-        // todo: use static assertion when available in rust
-        if header_size != 4096 { bail!("got unexpected header size"); }
+        let stat = match nix::sys::stat::fstat(file.as_raw_fd()) {
+            Ok(stat) => stat,
+            Err(err) => bail!("fstat failed - {}", err),
+        };
 
-        let mut buffer = vec![0u8; header_size];
-        file.read_exact(&mut buffer)?;
+        let size = stat.st_size as usize;
 
-        let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
+        if size < header_size {
+            bail!("index too small ({})", stat.st_size);
+        }
+
+        let header: Box<FixedIndexHeader> = unsafe { file.read_host_value_boxed()? };
 
         if header.magic != super::FIXED_SIZED_CHUNK_INDEX_1_0 {
             bail!("got unknown magic number");
         }
 
         let size = u64::from_le(header.size);
-        let ctime = u64::from_le(header.ctime);
+        let ctime = i64::from_le(header.ctime);
         let chunk_size = u64::from_le(header.chunk_size);
 
-        let index_length = ((size + chunk_size - 1)/chunk_size) as usize;
-        let index_size = index_length*32;
-
-        let rawfd = file.as_raw_fd();
-
-        let stat = match nix::sys::stat::fstat(rawfd) {
-            Ok(stat) => stat,
-            Err(err) => bail!("fstat failed - {}", err),
-        };
+        let index_length = ((size + chunk_size - 1) / chunk_size) as usize;
+        let index_size = index_length * 32;
 
         let expected_index_size = (stat.st_size as usize) - header_size;
         if index_size != expected_index_size {
-            bail!("got unexpected file size ({} != {})", index_size, expected_index_size);
+            bail!(
+                "got unexpected file size ({} != {})",
+                index_size,
+                expected_index_size
+            );
         }
 
-        let data = unsafe { nix::sys::mman::mmap(
-            std::ptr::null_mut(),
-            index_size,
-            nix::sys::mman::ProtFlags::PROT_READ,
-            nix::sys::mman::MapFlags::MAP_PRIVATE,
-            file.as_raw_fd(),
-            header_size as i64) }? as *mut u8;
+        let data = unsafe {
+            nix::sys::mman::mmap(
+                std::ptr::null_mut(),
+                index_size,
+                nix::sys::mman::ProtFlags::PROT_READ,
+                nix::sys::mman::MapFlags::MAP_PRIVATE,
+                file.as_raw_fd(),
+                header_size as i64,
+            )
+        }? as *mut u8;
 
         Ok(Self {
             _file: file,
@@ -127,12 +126,15 @@ impl FixedIndexReader {
     }
 
     fn unmap(&mut self) -> Result<(), Error> {
+        if self.index == std::ptr::null_mut() {
+            return Ok(());
+        }
 
-        if self.index == std::ptr::null_mut() { return Ok(()); }
-
-        let index_size = self.index_length*32;
+        let index_size = self.index_length * 32;
 
-        if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
+        if let Err(err) =
+            unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
+        {
             bail!("unmap file failed - {}", err);
         }
 
@@ -141,48 +143,16 @@ impl FixedIndexReader {
         Ok(())
     }
 
-    pub fn chunk_info(&self, pos: usize) -> Result<(u64, u64, [u8; 32]), Error> {
-
-        if pos >= self.index_length {
-            bail!("chunk index out of range");
-        }
-        let start = (pos * self.chunk_size) as u64;
-        let mut end = start + self.chunk_size as u64;
-
-        if end > self.size {
-            end = self.size;
-        }
-
-        let mut digest = std::mem::MaybeUninit::<[u8; 32]>::uninit();
-        unsafe {
-            std::ptr::copy_nonoverlapping(
-                self.index.add(pos*32),
-                (*digest.as_mut_ptr()).as_mut_ptr(),
-                32,
-            );
-        }
-
-        Ok((start, end, unsafe { digest.assume_init() }))
-    }
-
-    #[inline]
-    fn chunk_end(&self, pos: usize) -> u64 {
-        if pos >= self.index_length {
-            panic!("chunk index out of range");
-        }
-
-        let end = ((pos+1) * self.chunk_size) as u64;
-        if end > self.size {
-            self.size
-        } else {
-            end
-        }
-    }
-
     pub fn print_info(&self) {
         println!("Size: {}", self.size);
         println!("ChunkSize: {}", self.chunk_size);
-        println!("CTime: {}", Local.timestamp(self.ctime as i64, 0).format("%c"));
+
+        let mut ctime_str = self.ctime.to_string();
+        if let Ok(s) = proxmox::tools::time::strftime_local("%c", self.ctime) {
+            ctime_str = s;
+        }
+
+        println!("CTime: {}", ctime_str);
         println!("UUID: {:?}", self.uuid);
     }
 }
@@ -196,13 +166,56 @@ impl IndexFile for FixedIndexReader {
         if pos >= self.index_length {
             None
         } else {
-            Some(unsafe { std::mem::transmute(self.index.add(pos*32)) })
+            Some(unsafe { std::mem::transmute(self.index.add(pos * 32)) })
         }
     }
 
     fn index_bytes(&self) -> u64 {
         self.size
     }
+
+    fn chunk_info(&self, pos: usize) -> Option<ChunkReadInfo> {
+        if pos >= self.index_length {
+            return None;
+        }
+
+        let start = (pos * self.chunk_size) as u64;
+        let mut end = start + self.chunk_size as u64;
+
+        if end > self.size {
+            end = self.size;
+        }
+
+        let digest = self.index_digest(pos).unwrap();
+        Some(ChunkReadInfo {
+            range: start..end,
+            digest: *digest,
+        })
+    }
+
+    fn compute_csum(&self) -> ([u8; 32], u64) {
+        let mut csum = openssl::sha::Sha256::new();
+        let mut chunk_end = 0;
+        for pos in 0..self.index_count() {
+            let info = self.chunk_info(pos).unwrap();
+            chunk_end = info.range.end;
+            csum.update(&info.digest);
+        }
+        let csum = csum.finish();
+
+        (csum, chunk_end)
+    }
+
+    fn chunk_from_offset(&self, offset: u64) -> Option<(usize, u64)> {
+        if offset >= self.size {
+            return None;
+        }
+
+        Some((
+            (offset / self.chunk_size as u64) as usize,
+            offset & (self.chunk_size - 1) as u64, // fast modulo, valid for 2^x chunk_size
+        ))
+    }
 }
 
 pub struct FixedIndexWriter {
@@ -216,14 +229,13 @@ pub struct FixedIndexWriter {
     index_length: usize,
     index: *mut u8,
     pub uuid: [u8; 16],
-    pub ctime: u64,
+    pub ctime: i64,
 }
 
 // `index` is mmap()ed which cannot be thread-local so should be sendable
 unsafe impl Send for FixedIndexWriter {}
 
 impl Drop for FixedIndexWriter {
-
     fn drop(&mut self) {
         let _ = std::fs::remove_file(&self.tmp_filename); // ignore errors
         if let Err(err) = self.unmap() {
@@ -233,9 +245,13 @@ impl Drop for FixedIndexWriter {
 }
 
 impl FixedIndexWriter {
-
-    pub fn create(store: Arc<ChunkStore>, path: &Path, size: usize, chunk_size: usize) -> Result<Self, Error> {
-
+    #[allow(clippy::cast_ptr_alignment)]
+    pub fn create(
+        store: Arc<ChunkStore>,
+        path: &Path,
+        size: usize,
+        chunk_size: usize,
+    ) -> Result<Self, Error> {
         let shared_lock = store.try_shared_lock()?;
 
         let full_path = store.relative_path(path);
@@ -243,7 +259,8 @@ impl FixedIndexWriter {
         tmp_path.set_extension("tmp_fidx");
 
         let mut file = std::fs::OpenOptions::new()
-            .create(true).truncate(true)
+            .create(true)
+            .truncate(true)
             .read(true)
             .write(true)
             .open(&tmp_path)?;
@@ -251,18 +268,19 @@ impl FixedIndexWriter {
         let header_size = std::mem::size_of::<FixedIndexHeader>();
 
         // todo: use static assertion when available in rust
-        if header_size != 4096 { panic!("got unexpected header size"); }
+        if header_size != 4096 {
+            panic!("got unexpected header size");
+        }
 
-        let ctime = std::time::SystemTime::now().duration_since(
-            std::time::SystemTime::UNIX_EPOCH)?.as_secs();
+        let ctime = proxmox::tools::time::epoch_i64();
 
-        let uuid = Uuid::new_v4();
+        let uuid = Uuid::generate();
 
         let buffer = vec![0u8; header_size];
-        let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
+        let header = unsafe { &mut *(buffer.as_ptr() as *mut FixedIndexHeader) };
 
         header.magic = super::FIXED_SIZED_CHUNK_INDEX_1_0;
-        header.ctime = u64::to_le(ctime);
+        header.ctime = i64::to_le(ctime);
         header.size = u64::to_le(size as u64);
         header.chunk_size = u64::to_le(chunk_size as u64);
         header.uuid = *uuid.as_bytes();
@@ -271,17 +289,20 @@ impl FixedIndexWriter {
 
         file.write_all(&buffer)?;
 
-        let index_length = (size + chunk_size - 1)/chunk_size;
-        let index_size = index_length*32;
+        let index_length = (size + chunk_size - 1) / chunk_size;
+        let index_size = index_length * 32;
         nix::unistd::ftruncate(file.as_raw_fd(), (header_size + index_size) as i64)?;
 
-        let data = unsafe { nix::sys::mman::mmap(
-            std::ptr::null_mut(),
-            index_size,
-            nix::sys::mman::ProtFlags::PROT_READ | nix::sys::mman::ProtFlags::PROT_WRITE,
-            nix::sys::mman::MapFlags::MAP_SHARED,
-            file.as_raw_fd(),
-            header_size as i64) }? as *mut u8;
+        let data = unsafe {
+            nix::sys::mman::mmap(
+                std::ptr::null_mut(),
+                index_size,
+                nix::sys::mman::ProtFlags::PROT_READ | nix::sys::mman::ProtFlags::PROT_WRITE,
+                nix::sys::mman::MapFlags::MAP_SHARED,
+                file.as_raw_fd(),
+                header_size as i64,
+            )
+        }? as *mut u8;
 
         Ok(Self {
             store,
@@ -303,12 +324,15 @@ impl FixedIndexWriter {
     }
 
     fn unmap(&mut self) -> Result<(), Error> {
+        if self.index == std::ptr::null_mut() {
+            return Ok(());
+        }
 
-        if self.index == std::ptr::null_mut() { return Ok(()); }
-
-        let index_size = self.index_length*32;
+        let index_size = self.index_length * 32;
 
-        if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
+        if let Err(err) =
+            unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
+        {
             bail!("unmap file {:?} failed - {}", self.tmp_filename, err);
         }
 
@@ -317,17 +341,18 @@ impl FixedIndexWriter {
         Ok(())
     }
 
-    pub fn close(&mut self)  -> Result<[u8; 32], Error> {
-
-        if self.index == std::ptr::null_mut() { bail!("cannot close already closed index file."); }
+    pub fn close(&mut self) -> Result<[u8; 32], Error> {
+        if self.index == std::ptr::null_mut() {
+            bail!("cannot close already closed index file.");
+        }
 
-        let index_size = self.index_length*32;
+        let index_size = self.index_length * 32;
         let data = unsafe { std::slice::from_raw_parts(self.index, index_size) };
-        let index_csum =  openssl::sha::sha256(data);
+        let index_csum = openssl::sha::sha256(data);
 
         self.unmap()?;
 
-        let csum_offset = proxmox::tools::offsetof!(FixedIndexHeader, index_csum);
+        let csum_offset = proxmox::offsetof!(FixedIndexHeader, index_csum);
         self.file.seek(SeekFrom::Start(csum_offset as u64))?;
         self.file.write_all(&index_csum)?;
         self.file.flush()?;
@@ -340,7 +365,6 @@ impl FixedIndexWriter {
     }
 
     pub fn check_chunk_alignment(&self, offset: usize, chunk_len: usize) -> Result<usize, Error> {
-
         if offset < chunk_len {
             bail!("got chunk with small offset ({} < {}", offset, chunk_len);
         }
@@ -352,12 +376,18 @@ impl FixedIndexWriter {
         }
 
         // last chunk can be smaller
-        if ((offset != self.size) && (chunk_len != self.chunk_size)) ||
-            (chunk_len > self.chunk_size) || (chunk_len == 0) {
-                bail!("chunk with unexpected length ({} != {}", chunk_len, self.chunk_size);
-            }
+        if ((offset != self.size) && (chunk_len != self.chunk_size))
+            || (chunk_len > self.chunk_size)
+            || (chunk_len == 0)
+        {
+            bail!(
+                "chunk with unexpected length ({} != {}",
+                chunk_len,
+                self.chunk_size
+            );
+        }
 
-        if pos & (self.chunk_size-1) != 0 {
+        if pos & (self.chunk_size - 1) != 0 {
             bail!("got unaligned chunk (pos = {})", pos);
         }
 
@@ -366,21 +396,28 @@ impl FixedIndexWriter {
 
     // Note: We want to add data out of order, so do not assume any order here.
     pub fn add_chunk(&mut self, chunk_info: &ChunkInfo, stat: &mut ChunkStat) -> Result<(), Error> {
-
         let chunk_len = chunk_info.chunk_len as usize;
         let offset = chunk_info.offset as usize; // end of chunk
 
         let idx = self.check_chunk_alignment(offset, chunk_len)?;
 
-        let (is_duplicate, compressed_size) = self.store.insert_chunk(&chunk_info.chunk)?;
+        let (is_duplicate, compressed_size) = self
+            .store
+            .insert_chunk(&chunk_info.chunk, &chunk_info.digest)?;
 
         stat.chunk_count += 1;
         stat.compressed_size += compressed_size;
 
-        let digest = chunk_info.chunk.digest();
+        let digest = &chunk_info.digest;
 
-        println!("ADD CHUNK {} {} {}% {} {}", idx, chunk_len,
-                 (compressed_size*100)/(chunk_len as u64), is_duplicate, proxmox::tools::digest_to_hex(digest));
+        println!(
+            "ADD CHUNK {} {} {}% {} {}",
+            idx,
+            chunk_len,
+            (compressed_size * 100) / (chunk_len as u64),
+            is_duplicate,
+            proxmox::tools::digest_to_hex(digest)
+        );
 
         if is_duplicate {
             stat.duplicate_chunks += 1;
@@ -392,14 +429,19 @@ impl FixedIndexWriter {
     }
 
     pub fn add_digest(&mut self, index: usize, digest: &[u8; 32]) -> Result<(), Error> {
-
         if index >= self.index_length {
-            bail!("add digest failed - index out of range ({} >= {})", index, self.index_length);
+            bail!(
+                "add digest failed - index out of range ({} >= {})",
+                index,
+                self.index_length
+            );
         }
 
-        if self.index == std::ptr::null_mut() { bail!("cannot write to closed index file."); }
+        if self.index == std::ptr::null_mut() {
+            bail!("cannot write to closed index file.");
+        }
 
-        let index_pos = index*32;
+        let index_pos = index * 32;
         unsafe {
             let dst = self.index.add(index_pos);
             dst.copy_from_nonoverlapping(digest.as_ptr(), 32);
@@ -407,136 +449,16 @@ impl FixedIndexWriter {
 
         Ok(())
     }
-}
-
-pub struct BufferedFixedReader<S> {
-    store: S,
-    index: FixedIndexReader,
-    archive_size: u64,
-    read_buffer: Vec<u8>,
-    buffered_chunk_idx: usize,
-    buffered_chunk_start: u64,
-    read_offset: u64,
-}
-
-impl <S: ReadChunk> BufferedFixedReader<S> {
 
-    pub fn new(index: FixedIndexReader, store: S) -> Self {
-
-        let archive_size = index.size;
-        Self {
-            store,
-            index: index,
-            archive_size: archive_size,
-            read_buffer: Vec::with_capacity(1024*1024),
-            buffered_chunk_idx: 0,
-            buffered_chunk_start: 0,
-            read_offset: 0,
+    pub fn clone_data_from(&mut self, reader: &FixedIndexReader) -> Result<(), Error> {
+        if self.index_length != reader.index_count() {
+            bail!("clone_data_from failed - index sizes not equal");
         }
-    }
-
-    pub fn archive_size(&self) -> u64 { self.archive_size }
-
-    fn buffer_chunk(&mut self, idx: usize) -> Result<(), Error> {
-
-        let index = &self.index;
-        let (start, end, digest) = index.chunk_info(idx)?;
-
-        // fixme: avoid copy
-
-        let data = self.store.read_chunk(&digest)?;
 
-        if (end - start) != data.len() as u64  {
-            bail!("read chunk with wrong size ({} != {}", (end - start), data.len());
+        for i in 0..self.index_length {
+            self.add_digest(i, reader.index_digest(i).unwrap())?;
         }
 
-        self.read_buffer.clear();
-        self.read_buffer.extend_from_slice(&data);
-
-        self.buffered_chunk_idx = idx;
-
-        self.buffered_chunk_start = start as u64;
-        //println!("BUFFER {} {}",  self.buffered_chunk_start, end);
         Ok(())
     }
 }
-
-impl <S: ReadChunk> crate::tools::BufferedRead for BufferedFixedReader<S> {
-
-    fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error> {
-
-        if offset == self.archive_size { return Ok(&self.read_buffer[0..0]); }
-
-        let buffer_len = self.read_buffer.len();
-        let index = &self.index;
-
-        // optimization for sequential read
-        if buffer_len > 0 &&
-            ((self.buffered_chunk_idx + 1) < index.index_length) &&
-            (offset >= (self.buffered_chunk_start + (self.read_buffer.len() as u64)))
-        {
-            let next_idx = self.buffered_chunk_idx + 1;
-            let next_end = index.chunk_end(next_idx);
-            if offset < next_end {
-                self.buffer_chunk(next_idx)?;
-                let buffer_offset = (offset - self.buffered_chunk_start) as usize;
-                return Ok(&self.read_buffer[buffer_offset..]);
-            }
-        }
-
-        if (buffer_len == 0) ||
-            (offset < self.buffered_chunk_start) ||
-            (offset >= (self.buffered_chunk_start + (self.read_buffer.len() as u64)))
-        {
-            let idx = (offset / index.chunk_size as u64) as usize;
-            self.buffer_chunk(idx)?;
-         }
-
-        let buffer_offset = (offset - self.buffered_chunk_start) as usize;
-        Ok(&self.read_buffer[buffer_offset..])
-    }
-}
-
-impl <S: ReadChunk> std::io::Read for  BufferedFixedReader<S> {
-
-    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
-
-        use std::io::{Error, ErrorKind};
-        use crate::tools::BufferedRead;
-
-        let data = match self.buffered_read(self.read_offset) {
-            Ok(v) => v,
-            Err(err) => return Err(Error::new(ErrorKind::Other, err.to_string())),
-        };
-
-        let n = if data.len() > buf.len() { buf.len() } else { data.len() };
-
-        unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), buf.as_mut_ptr(), n); }
-
-        self.read_offset += n as u64;
-
-        return Ok(n);
-    }
-}
-
-impl <S: ReadChunk> Seek for  BufferedFixedReader<S> {
-
-    fn seek(&mut self, pos: SeekFrom) -> Result<u64, std::io::Error> {
-
-        let new_offset = match pos {
-            SeekFrom::Start(start_offset) =>  start_offset as i64,
-            SeekFrom::End(end_offset) => (self.archive_size as i64)+ end_offset,
-            SeekFrom::Current(offset) => (self.read_offset as i64) + offset,
-        };
-
-        use std::io::{Error, ErrorKind};
-        if (new_offset < 0) || (new_offset > (self.archive_size as i64)) {
-            return Err(Error::new(
-                ErrorKind::Other,
-                format!("seek is out of range {} ([0..{}])", new_offset, self.archive_size)));
-        }
-        self.read_offset = new_offset as u64;
-
-        Ok(self.read_offset)
-    }
-}