]> 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 bb3197bac85aa409a0a9dd254fe04423c95680bc..279537deb12b4a0e4943b90324f4eaf8891f9927 100644 (file)
-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::path::{Path, PathBuf};
+use std::fs::File;
+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;
 
-/// Header format definition for fixed index files (`.fixd`)
+use proxmox::tools::io::ReadExt;
+use proxmox::tools::Uuid;
+
+/// Header format definition for fixed index files (`.fidx`)
 #[repr(C)]
 pub struct FixedIndexHeader {
-    /// The string `PROXMOX-FIDX`
-    pub magic: [u8; 12],
-    pub version: u32,
+    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; 4040], // overall size is one page (4096 bytes)
+    reserved: [u8; 4016], // overall size is one page (4096 bytes)
 }
+proxmox::static_assert_size!(FixedIndexHeader, 4096);
 
 // split image into fixed size chunks
 
 pub struct FixedIndexReader {
-    store: Arc<ChunkStore>,
-    filename: PathBuf,
-    chunk_size: usize,
-    pub size: usize,
+    _file: File,
+    pub chunk_size: usize,
+    pub size: u64,
+    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 {:?} - {}", self.filename, err);
+            eprintln!("Unable to unmap file - {}", err);
         }
     }
 }
 
 impl FixedIndexReader {
+    pub fn open(path: &Path) -> Result<Self, Error> {
+        File::open(path)
+            .map_err(Error::from)
+            .and_then(Self::new)
+            .map_err(|err| format_err!("Unable to open fixed index {:?} - {}", path, err))
+    }
 
-    pub fn open(store: Arc<ChunkStore>, path: &Path) -> Result<Self, Error> {
-
-        let full_path = store.relative_path(path);
-
-        let mut file = std::fs::File::open(&full_path)?;
+    pub fn new(mut file: std::fs::File) -> Result<Self, Error> {
+        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 for {:?}", path); }
-
-        let mut buffer = vec![0u8; header_size];
-        file.read_exact(&mut buffer)?;
-
-        let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
+        let stat = match nix::sys::stat::fstat(file.as_raw_fd()) {
+            Ok(stat) => stat,
+            Err(err) => bail!("fstat failed - {}", err),
+        };
 
-        if header.magic != *b"PROXMOX-FIDX" {
-            bail!("got unknown magic number for {:?}", path);
-        }
+        let size = stat.st_size as usize;
 
-        let version = u32::from_le(header.version);
-        if  version != 1 {
-            bail!("got unsupported version number ({})", version);
+        if size < header_size {
+            bail!("index too small ({})", stat.st_size);
         }
 
-        let size = u64::from_le(header.size) as usize;
-        let ctime = u64::from_le(header.ctime);
-        let chunk_size = u64::from_le(header.chunk_size) as usize;
+        let header: Box<FixedIndexHeader> = unsafe { file.read_host_value_boxed()? };
 
-        let index_size = ((size + chunk_size - 1)/chunk_size)*32;
+        if header.magic != super::FIXED_SIZED_CHUNK_INDEX_1_0 {
+            bail!("got unknown magic number");
+        }
 
-        let rawfd = file.as_raw_fd();
+        let size = u64::from_le(header.size);
+        let ctime = i64::from_le(header.ctime);
+        let chunk_size = u64::from_le(header.chunk_size);
 
-        let stat = match nix::sys::stat::fstat(rawfd) {
-            Ok(stat) => stat,
-            Err(err) => bail!("fstat {:?} failed - {}", path, 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 for {:?} ({} != {})",
-                  path, 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 {
-            store,
-            filename: full_path,
-            chunk_size,
+            _file: file,
+            chunk_size: chunk_size as usize,
             size,
+            index_length,
             index: data,
             ctime,
             uuid: header.uuid,
+            index_csum: header.index_csum,
         })
     }
 
     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.size + self.chunk_size - 1)/self.chunk_size)*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) } {
-            bail!("unmap file {:?} failed - {}", self.filename, err);
+        if let Err(err) =
+            unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) }
+        {
+            bail!("unmap file failed - {}", err);
         }
 
         self.index = std::ptr::null_mut();
@@ -129,66 +143,99 @@ impl FixedIndexReader {
         Ok(())
     }
 
-    pub fn mark_used_chunks(&self, status: &mut GarbageCollectionStatus) -> Result<(), Error> {
-
-        if self.index == std::ptr::null_mut() { bail!("detected closed index file."); }
-
-        let index_count = self.index_count();
-
-        status.used_bytes += index_count * self.chunk_size;
-        status.used_chunks += index_count;
-
-        for pos in 0..index_count {
-
-            let digest = self.index_digest(pos).unwrap();
-            if let Err(err) = self.store.touch_chunk(digest) {
-                bail!("unable to access chunk {}, required by {:?} - {}",
-                      tools::digest_to_hex(digest), self.filename, err);
-            }
-        }
-
-        Ok(())
-    }
-
     pub fn print_info(&self) {
-        println!("Filename: {:?}", self.filename);
         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);
     }
 }
 
 impl IndexFile for FixedIndexReader {
     fn index_count(&self) -> usize {
-        (self.size + self.chunk_size - 1)/self.chunk_size
+        self.index_length
     }
 
     fn index_digest(&self, pos: usize) -> Option<&[u8; 32]> {
-        if pos >= self.index_count() {
+        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 {
     store: Arc<ChunkStore>,
+    file: File,
+    _lock: tools::ProcessLockSharedGuard,
     filename: PathBuf,
     tmp_filename: PathBuf,
     chunk_size: usize,
-
-    stat: ChunkStat,
-
     size: usize,
+    index_length: usize,
     index: *mut u8,
     pub uuid: [u8; 16],
-    pub ctime: u64,
+    pub ctime: i64,
 }
 
-impl Drop for FixedIndexWriter {
+// `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() {
@@ -198,15 +245,22 @@ 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);
         let mut tmp_path = full_path.clone();
         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)?;
@@ -214,123 +268,180 @@ 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 = *b"PROXMOX-FIDX";
-        header.version = u32::to_le(1);
-        header.ctime = u64::to_le(ctime);
+        header.magic = super::FIXED_SIZED_CHUNK_INDEX_1_0;
+        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();
 
+        header.index_csum = [0u8; 32];
+
         file.write_all(&buffer)?;
 
-        let index_size = ((size + chunk_size - 1)/chunk_size)*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,
+            file,
+            _lock: shared_lock,
             filename: full_path,
             tmp_filename: tmp_path,
             chunk_size,
             size,
-            stat: ChunkStat::new(size as u64),
+            index_length,
             index: data,
             ctime,
             uuid: *uuid.as_bytes(),
         })
     }
 
-    fn unmap(&mut self) -> Result<(), Error> {
+    pub fn index_length(&self) -> usize {
+        self.index_length
+    }
 
-        if self.index == std::ptr::null_mut() { return Ok(()); }
+    fn unmap(&mut self) -> Result<(), Error> {
+        if self.index == std::ptr::null_mut() {
+            return Ok(());
+        }
 
-        let index_size = ((self.size + self.chunk_size - 1)/self.chunk_size)*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);
         }
 
         self.index = std::ptr::null_mut();
 
-        self.stat.disk_size += index_size as u64;
-
-        println!("STAT: {:?}", self.stat);
-
         Ok(())
     }
 
-    pub fn close(&mut self)  -> Result<(), Error> {
+    pub fn close(&mut self) -> Result<[u8; 32], Error> {
+        if self.index == std::ptr::null_mut() {
+            bail!("cannot close already closed index file.");
+        }
 
-        if self.index == std::ptr::null_mut() { bail!("cannot close already closed index file."); }
+        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);
 
         self.unmap()?;
 
+        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()?;
+
         if let Err(err) = std::fs::rename(&self.tmp_filename, &self.filename) {
             bail!("Atomic rename file {:?} failed - {}", self.filename, err);
         }
 
-        Ok(())
+        Ok(index_csum)
     }
 
-    pub fn stat(&self) -> &ChunkStat {
-        &self.stat
-    }
+    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);
+        }
 
-    // Note: We want to add data out of order, so do not assume and order here.
-    pub fn add_chunk(&mut self, pos: usize, chunk: &[u8]) -> Result<(), Error> {
+        let pos = offset - chunk_len;
 
-        if self.index == std::ptr::null_mut() { bail!("cannot write to closed index file."); }
+        if offset > self.size {
+            bail!("chunk data exceeds size ({} >= {})", offset, self.size);
+        }
 
-        let end = pos + chunk.len();
+        // 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 end > self.size {
-            bail!("write chunk data exceeds size ({} >= {})", end, self.size);
+        if pos & (self.chunk_size - 1) != 0 {
+            bail!("got unaligned chunk (pos = {})", pos);
         }
 
-        // last chunk can be smaller
-        if ((end != self.size) && (chunk.len() != self.chunk_size)) ||
-            (chunk.len() > self.chunk_size) || (chunk.len() == 0) {
-                bail!("got chunk with wrong length ({} != {}", chunk.len(), self.chunk_size);
-            }
+        Ok(pos / self.chunk_size)
+    }
 
-        if pos >= self.size { bail!("add chunk after end ({} >= {})", pos, self.size); }
+    // 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
 
-        if pos & (self.chunk_size-1) != 0 { bail!("add unaligned chunk (pos = {})", pos); }
+        let idx = self.check_chunk_alignment(offset, chunk_len)?;
 
+        let (is_duplicate, compressed_size) = self
+            .store
+            .insert_chunk(&chunk_info.chunk, &chunk_info.digest)?;
 
-        let (is_duplicate, digest, compressed_size) = self.store.insert_chunk(chunk)?;
+        stat.chunk_count += 1;
+        stat.compressed_size += compressed_size;
 
-        self.stat.chunk_count += 1;
-        self.stat.compressed_size += compressed_size;
+        let digest = &chunk_info.digest;
 
-        println!("ADD CHUNK {} {} {}% {} {}", pos, chunk.len(),
-                 (compressed_size*100)/(chunk.len() as u64), is_duplicate, 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 {
-            self.stat.duplicate_chunks += 1;
+            stat.duplicate_chunks += 1;
         } else {
-            self.stat.disk_size += compressed_size;
+            stat.disk_size += compressed_size;
         }
 
-        let index_pos = (pos/self.chunk_size)*32;
+        self.add_digest(idx, digest)
+    }
+
+    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
+            );
+        }
+
+        if self.index == std::ptr::null_mut() {
+            bail!("cannot write to closed index file.");
+        }
+
+        let index_pos = index * 32;
         unsafe {
             let dst = self.index.add(index_pos);
             dst.copy_from_nonoverlapping(digest.as_ptr(), 32);
@@ -338,4 +449,16 @@ impl FixedIndexWriter {
 
         Ok(())
     }
+
+    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");
+        }
+
+        for i in 0..self.index_length {
+            self.add_digest(i, reader.index_digest(i).unwrap())?;
+        }
+
+        Ok(())
+    }
 }