public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Robert Obkircher <r.obkircher@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH v2 proxmox-backup 1/5] fix #3847: datastore: support writing fidx files of unknown size
Date: Fri, 19 Dec 2025 17:18:28 +0100	[thread overview]
Message-ID: <20251219161850.244154-2-r.obkircher@proxmox.com> (raw)
In-Reply-To: <20251219161850.244154-1-r.obkircher@proxmox.com>

Use mremap and ftruncate to support growable FixedIndexWriters. Grow
exponentially from a small initial index size for efficiency. Truncate
excessive capacity after encountering a non-full block or on close.

Signed-off-by: Robert Obkircher <r.obkircher@proxmox.com>
---
 pbs-datastore/src/datastore.rs   |  2 +-
 pbs-datastore/src/fixed_index.rs | 98 ++++++++++++++++++++++++++++++--
 2 files changed, 93 insertions(+), 7 deletions(-)

diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
index 9c57aaac..af712726 100644
--- a/pbs-datastore/src/datastore.rs
+++ b/pbs-datastore/src/datastore.rs
@@ -591,7 +591,7 @@ impl DataStore {
     pub fn create_fixed_writer<P: AsRef<Path>>(
         &self,
         filename: P,
-        size: usize,
+        size: Option<usize>,
         chunk_size: usize,
     ) -> Result<FixedIndexWriter, Error> {
         let index = FixedIndexWriter::create(
diff --git a/pbs-datastore/src/fixed_index.rs b/pbs-datastore/src/fixed_index.rs
index 6c3be2d4..42b97464 100644
--- a/pbs-datastore/src/fixed_index.rs
+++ b/pbs-datastore/src/fixed_index.rs
@@ -1,6 +1,7 @@
 use std::fs::File;
 use std::io::Write;
 use std::io::{Seek, SeekFrom};
+use std::os::unix::fs::FileExt;
 use std::os::unix::io::AsRawFd;
 use std::path::{Path, PathBuf};
 use std::ptr::NonNull;
@@ -222,6 +223,8 @@ pub struct FixedIndexWriter {
     index: *mut u8,
     pub uuid: [u8; 16],
     pub ctime: i64,
+    growable_size: bool,
+    write_size_on_close: bool,
 }
 
 // `index` is mmap()ed which cannot be thread-local so should be sendable
@@ -237,12 +240,15 @@ impl Drop for FixedIndexWriter {
 }
 
 impl FixedIndexWriter {
+    // TODO: this is deliberately small at the moment to test resizing
+    const INITIAL_CHUNKS_IF_UNKNOWN: usize = 4;
+
     #[allow(clippy::cast_ptr_alignment)]
     // Requires obtaining a shared chunk store lock beforehand
     pub fn create(
         store: Arc<ChunkStore>,
         path: &Path,
-        size: usize,
+        known_size: Option<usize>,
         chunk_size: usize,
     ) -> Result<Self, Error> {
         let full_path = store.relative_path(path);
@@ -264,6 +270,7 @@ impl FixedIndexWriter {
         }
 
         let ctime = proxmox_time::epoch_i64();
+        let size = known_size.unwrap_or(0);
 
         let uuid = Uuid::generate();
 
@@ -280,7 +287,9 @@ impl FixedIndexWriter {
 
         file.write_all(&buffer)?;
 
-        let index_length = size.div_ceil(chunk_size);
+        let index_length = known_size
+            .map(|s| s.div_ceil(chunk_size))
+            .unwrap_or(Self::INITIAL_CHUNKS_IF_UNKNOWN);
         let index_size = index_length * 32;
         nix::unistd::ftruncate(&file, (header_size + index_size) as i64)?;
 
@@ -308,11 +317,69 @@ impl FixedIndexWriter {
             index: data,
             ctime,
             uuid: *uuid.as_bytes(),
+            growable_size: known_size.is_none(),
+            write_size_on_close: known_size.is_none(),
         })
     }
 
+    fn resize_index(&mut self, new_index_length: usize) -> Result<(), Error> {
+        let old_index_size = self.index_length * 32;
+
+        let header_size = std::mem::size_of::<FixedIndexHeader>();
+        let new_index_size = new_index_length * 32;
+        let new_file_size = (header_size + new_index_size) as i64;
+
+        let index_addr = NonNull::new(self.index as *mut std::ffi::c_void).ok_or_else(|| {
+            format_err!("Can't resize FixedIndexWriter index because the mmap pointer is null.")
+        })?;
+
+        nix::unistd::ftruncate(&self.file, new_file_size)?;
+
+        let new_index = unsafe {
+            nix::sys::mman::mremap(
+                index_addr,
+                old_index_size,
+                new_index_size,
+                nix::sys::mman::MRemapFlags::MREMAP_MAYMOVE,
+                None,
+            )
+        }?
+        .as_ptr()
+        .cast::<u8>();
+
+        self.index = new_index;
+        self.index_length = new_index_length;
+
+        Ok(())
+    }
+
+    pub fn grow_to_size(&mut self, requested: usize) -> Result<(), Error> {
+        if self.size < requested {
+            if !self.growable_size {
+                bail!("refusing to resize from {} to {}", self.size, requested);
+            }
+            let len = requested.div_ceil(self.chunk_size);
+            if len * self.chunk_size != requested {
+                self.growable_size = false; // ensures only the last chunk can be smaller
+                self.resize_index(len)?;
+            } else {
+                // grow by 1.5x
+                let mut new_len = self.index_length.max(2);
+                while new_len < len {
+                    new_len += new_len / 2;
+                }
+                debug_assert!(new_len * self.chunk_size >= requested);
+                self.resize_index(new_len)?;
+            }
+            self.size = requested;
+        }
+        Ok(())
+    }
+
     pub fn index_length(&self) -> usize {
-        self.index_length
+        let len = self.size.div_ceil(self.chunk_size);
+        assert!((self.write_size_on_close && len <= self.index_length) || len == self.index_length);
+        len
     }
 
     fn unmap(&mut self) -> Result<(), Error> {
@@ -336,15 +403,26 @@ impl FixedIndexWriter {
             bail!("cannot close already closed index file.");
         }
 
-        let index_size = self.index_length * 32;
+        let used_index_length = self.index_length();
+        let index_size = used_index_length * 32;
         let data = unsafe { std::slice::from_raw_parts(self.index, index_size) };
         let index_csum = openssl::sha::sha256(data);
 
         self.unmap()?;
 
+        if used_index_length < self.index_length {
+            let header_size = std::mem::size_of::<FixedIndexHeader>();
+            nix::unistd::ftruncate(&self.file, (header_size + index_size) as i64)?;
+            self.index_length = used_index_length;
+        }
+
         let csum_offset = std::mem::offset_of!(FixedIndexHeader, index_csum);
-        self.file.seek(SeekFrom::Start(csum_offset as u64))?;
-        self.file.write_all(&index_csum)?;
+        self.file.write_all_at(&index_csum, csum_offset as u64)?;
+        if self.write_size_on_close {
+            let size_offset = std::mem::offset_of!(FixedIndexHeader, size);
+            self.file
+                .write_all_at(&(self.size as u64).to_le_bytes(), size_offset as u64)?;
+        }
         self.file.flush()?;
 
         if let Err(err) = std::fs::rename(&self.tmp_filename, &self.filename) {
@@ -407,6 +485,14 @@ impl FixedIndexWriter {
     }
 
     pub fn clone_data_from(&mut self, reader: &FixedIndexReader) -> Result<(), Error> {
+        if self.growable_size {
+            bail!("reusing the index is only supported with a fixed size");
+        }
+
+        if self.chunk_size != reader.chunk_size {
+            bail!("chunk size mismatch");
+        }
+
         if self.index_length != reader.index_count() {
             bail!("clone_data_from failed - index sizes not equal");
         }
-- 
2.47.3



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel


  reply	other threads:[~2025-12-19 16:18 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-19 16:18 [pbs-devel] [PATCH v2 proxmox-backup 0/5] fix: #3847 pipe from STDIN to proxmox-backup-client Robert Obkircher
2025-12-19 16:18 ` Robert Obkircher [this message]
2025-12-19 16:18 ` [pbs-devel] [PATCH v2 proxmox-backup 2/5] fix #3847: api: backup: make fixed index file size optional Robert Obkircher
2025-12-19 16:18 ` [pbs-devel] [PATCH v2 proxmox-backup 3/5] fix #3847: client: support fifo pipe inputs for images Robert Obkircher
2025-12-19 16:18 ` [pbs-devel] [PATCH v2 proxmox-backup 4/5] fix #3847: client: treat minus sign as stdin Robert Obkircher
2025-12-19 16:18 ` [pbs-devel] [PATCH v2 proxmox-backup 5/5] DO NOT MERGE: test script for reference Robert Obkircher

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251219161850.244154-2-r.obkircher@proxmox.com \
    --to=r.obkircher@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal