fix(vfs): V-06 — sparse writes zero-fill gaps between previous end of file and seek offset
This commit is contained in:
@@ -1057,6 +1057,34 @@ impl DavFile for CarrierFile {
|
||||
fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()> {
|
||||
self.touch();
|
||||
Box::pin(async move {
|
||||
// V-06: Sparse Writes — Lücke zwischen bisherigem Dateiende und Cursor mit Nullen füllen
|
||||
if self.cursor > self.file_size {
|
||||
let target = self.cursor;
|
||||
while self.file_size < target {
|
||||
let block_idx = (self.file_size / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize;
|
||||
let offset_in_block = (self.file_size % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize;
|
||||
let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block;
|
||||
let to_pad = ((target - self.file_size) as usize).min(space_in_block);
|
||||
|
||||
let block_data = self.ensure_block_loaded(block_idx)?;
|
||||
if block_data.len() < offset_in_block + to_pad {
|
||||
block_data.resize(offset_in_block + to_pad, 0);
|
||||
}
|
||||
|
||||
let mut is_full = false;
|
||||
if let Some((_, ref d, ref mut dirty)) = self.cached_block {
|
||||
*dirty = true;
|
||||
is_full = d.len() >= CARRIER_BLOCK_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
self.file_size += to_pad as u64;
|
||||
|
||||
if is_full {
|
||||
self.flush_cached_block()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut src = &buf[..];
|
||||
|
||||
while !src.is_empty() {
|
||||
|
||||
+80
@@ -396,6 +396,34 @@ impl DavFile for SanctumFile {
|
||||
fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()> {
|
||||
self.touch();
|
||||
Box::pin(async move {
|
||||
// V-06: Sparse Writes — Lücke zwischen bisherigem Dateiende und Cursor mit Nullen füllen
|
||||
if self.cursor > self.file_size {
|
||||
let target = self.cursor;
|
||||
while self.file_size < target {
|
||||
let chunk_idx = (self.file_size / CHUNK_SIZE as u64) as u32;
|
||||
let offset_in_chunk = (self.file_size % CHUNK_SIZE as u64) as usize;
|
||||
let space_in_chunk = CHUNK_SIZE - offset_in_chunk;
|
||||
let to_pad = ((target - self.file_size) as usize).min(space_in_chunk);
|
||||
|
||||
let chunk_data = self.ensure_chunk_loaded(chunk_idx)?;
|
||||
if chunk_data.len() < offset_in_chunk + to_pad {
|
||||
chunk_data.resize(offset_in_chunk + to_pad, 0);
|
||||
}
|
||||
|
||||
let mut is_full = false;
|
||||
if let Some((_, ref d, ref mut dirty)) = self.cached_chunk {
|
||||
*dirty = true;
|
||||
is_full = d.len() >= CHUNK_SIZE;
|
||||
}
|
||||
|
||||
self.file_size += to_pad as u64;
|
||||
|
||||
if is_full {
|
||||
self.flush_cached_chunk_and_size()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut src = &buf[..];
|
||||
|
||||
while !src.is_empty() {
|
||||
@@ -1488,4 +1516,56 @@ mod tests {
|
||||
let read_bytes = read_file.read_bytes(test_data.len()).await.unwrap();
|
||||
assert_eq!(&read_bytes[..], test_data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_v06_sparse_write_zero_fills_gap() {
|
||||
use std::io::SeekFrom;
|
||||
|
||||
let (fs, _dir) = create_test_fs(true);
|
||||
let path = DavPath::new("/sparse_test.bin").unwrap();
|
||||
|
||||
let mut opts = OpenOptions::default();
|
||||
opts.write = true;
|
||||
opts.create_new = true;
|
||||
let mut file = fs.open(&path, opts).await.unwrap();
|
||||
|
||||
// 1. Schreibe 5 Bytes am Anfang (Chunk 0)
|
||||
file.write_bytes(Bytes::copy_from_slice(b"START"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 2. Springe weit über das Dateiende hinaus in Chunk 2 (2,5 MB)
|
||||
let seek_pos = 2 * CHUNK_SIZE as u64 + 500;
|
||||
file.seek(SeekFrom::Start(seek_pos)).await.unwrap();
|
||||
|
||||
// 3. Schreibe 3 Bytes an Position seek_pos
|
||||
file.write_bytes(Bytes::copy_from_slice(b"END"))
|
||||
.await
|
||||
.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
drop(file);
|
||||
|
||||
// 4. Verifiziere Dateigröße
|
||||
let node = fs.resolve_path("/sparse_test.bin").unwrap().unwrap();
|
||||
assert_eq!(node.size, seek_pos + 3);
|
||||
|
||||
// 5. Lese Datei vollständig ein und prüfe Nullen in der Lücke
|
||||
let mut read_opts = OpenOptions::default();
|
||||
read_opts.read = true;
|
||||
let mut read_file = fs.open(&path, read_opts).await.unwrap();
|
||||
|
||||
// Anfang lesen
|
||||
let start_bytes = read_file.read_bytes(5).await.unwrap();
|
||||
assert_eq!(&start_bytes[..], b"START");
|
||||
|
||||
// Lücke in Chunk 0 und 1 muss mit Nullen gefüllt sein
|
||||
let zero_gap = read_file.read_bytes(1000).await.unwrap();
|
||||
assert_eq!(zero_gap.len(), 1000);
|
||||
assert!(zero_gap.iter().all(|&b| b == 0));
|
||||
|
||||
// Springe zum Ende und lies die geschriebenen Daten
|
||||
read_file.seek(SeekFrom::Start(seek_pos)).await.unwrap();
|
||||
let end_bytes = read_file.read_bytes(3).await.unwrap();
|
||||
assert_eq!(&end_bytes[..], b"END");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user