fix(vfs): Z-04 — implement webdav quota report

This commit is contained in:
2026-09-19 09:52:56 +02:00
parent 487f999a24
commit 17908a64fe
5 changed files with 187 additions and 3 deletions
+16
View File
@@ -885,6 +885,22 @@ impl DavFileSystem for CarrierFs {
Ok(()) Ok(())
}) })
} }
fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> {
Box::pin(async move {
self.touch();
let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
let total_capacity = inner.manifest.total_blocks as u64 * crate::crypto::CHUNK_SIZE as u64;
let used_bytes = inner
.manifest
.inodes
.values()
.filter(|i| !i.is_dir)
.map(|i| i.size)
.sum::<u64>();
Ok((used_bytes, Some(total_capacity)))
})
}
} }
/// Datei-Handle für Dateien innerhalb des Carrier-Dateisystems mit Streaming und Chunk-Pufferung. /// Datei-Handle für Dateien innerhalb des Carrier-Dateisystems mit Streaming und Chunk-Pufferung.
+32
View File
@@ -349,6 +349,38 @@ impl Database {
self.conn() self.conn()
} }
/// Ermittelt den physischen Dateipfad des geöffneten Containers aus SQLite (sofern nicht in-memory).
pub fn container_path(&self) -> Option<std::path::PathBuf> {
let conn = self.conn();
let path_str: String = conn
.query_row("PRAGMA database_list;", [], |r| r.get(2))
.ok()?;
if path_str.is_empty() {
None
} else {
Some(std::path::PathBuf::from(path_str))
}
}
/// Ermittelt die physische Dateigröße des Containers über die SQLite-Page-Statistik (Z-04).
pub fn get_container_file_size(&self) -> Result<u64> {
let conn = self.conn();
let page_count: i64 = conn.query_row("PRAGMA page_count;", [], |r| r.get(0))?;
let page_size: i64 = conn.query_row("PRAGMA page_size;", [], |r| r.get(0))?;
Ok((page_count.saturating_mul(page_size)).max(0) as u64)
}
/// Ermittelt die kumulierte Dateigröße aller im Container gespeicherten Nutzdateien (Z-04).
pub fn get_total_used_size(&self) -> Result<u64> {
let conn = self.conn();
let total: i64 = conn.query_row(
"SELECT COALESCE(SUM(size), 0) FROM nodes WHERE is_dir = 0",
[],
|r| r.get(0),
)?;
Ok(total.max(0) as u64)
}
/// Erzeugt eine geklonte Instanz mit einer isolierten aktiven Session (Slot & DEK). /// Erzeugt eine geklonte Instanz mit einer isolierten aktiven Session (Slot & DEK).
pub fn with_session(&self, slot_id: u32, dek: Zeroizing<[u8; 32]>) -> Self { pub fn with_session(&self, slot_id: u32, dek: Zeroizing<[u8; 32]>) -> Self {
Self { Self {
+21 -3
View File
@@ -1127,10 +1127,28 @@ impl DavFileSystem for SanctumFs {
} }
fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> { fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> {
if let Some(ref cfs) = self.carrier_fs {
return cfs.get_quota();
}
Box::pin(async move { Box::pin(async move {
// Virtueller Speicherplatz für Explorer: 1 TB self.touch();
let total_capacity: u64 = 1024 * 1024 * 1024 * 1024; // Z-04: Echte Containergröße (physisch auf Disk) und freier Host-Speicher
Ok((0, Some(total_capacity))) let container_size = self.db.get_container_file_size().unwrap_or(0);
let used_bytes = if container_size > 0 {
container_size
} else {
self.db.get_total_used_size().unwrap_or(0)
};
let free_host_space = self
.db
.container_path()
.and_then(|p| crate::windows::get_available_disk_space(&p))
.unwrap_or(1024 * 1024 * 1024 * 1024); // Fallback: 1 TB
let total_capacity = used_bytes.saturating_add(free_host_space);
Ok((used_bytes, Some(total_capacity)))
}) })
} }
} }
+49
View File
@@ -846,6 +846,55 @@ pub fn is_process_alive(pid: u32) -> bool {
} }
} }
/// Ermittelt den verfügbaren freien Speicherplatz auf dem Dateisystem des angegebenen Pfads (Z-04).
pub fn get_available_disk_space<P: AsRef<std::path::Path>>(path: P) -> Option<u64> {
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let p = path.as_ref();
let dir = if p.is_dir() {
p.to_path_buf()
} else {
p.parent().map(|parent| parent.to_path_buf()).unwrap_or_else(|| p.to_path_buf())
};
let mut wide: Vec<u16> = dir.as_os_str().encode_wide().collect();
wide.push(0);
let mut free_bytes_available = 0u64;
let mut total_number_of_bytes = 0u64;
let mut total_number_of_free_bytes = 0u64;
extern "system" {
fn GetDiskFreeSpaceExW(
lpDirectoryName: *const u16,
lpFreeBytesAvailableToCaller: *mut u64,
lpTotalNumberOfBytes: *mut u64,
lpTotalNumberOfFreeBytes: *mut u64,
) -> i32;
}
let success = unsafe {
GetDiskFreeSpaceExW(
wide.as_ptr(),
&mut free_bytes_available,
&mut total_number_of_bytes,
&mut total_number_of_free_bytes,
)
};
if success != 0 {
Some(free_bytes_available)
} else {
None
}
}
#[cfg(not(windows))]
{
let _ = path;
Some(1024 * 1024 * 1024 * 1024)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+69
View File
@@ -242,4 +242,73 @@ fn test_z01_decoy_password_zeroize_memory() {
drop(ephemeral); drop(ephemeral);
} }
#[tokio::test]
async fn test_z04_webdav_quota_report() {
use dav_server::fs::DavFileSystem;
use sanctum::vfs::SanctumFs;
let temp_dir = std::env::temp_dir();
let container_path: PathBuf =
temp_dir.join(format!("test_z04_quota_{}.sanctum", std::process::id()));
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let salt = generate_salt();
let kdf_params = KdfParams {
memory_cost: MIN_MEMORY_COST_KIB,
time_cost: MIN_TIME_COST,
parallelism: 1,
};
let kek = derive_kek("TestPassZ04!", &salt, &kdf_params).unwrap();
let dek = generate_dek();
let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap();
let db = Database::open(&container_path).unwrap();
db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag)
.unwrap();
// Erstelle einen Testknoten mit Chunks
let node = db.create_node(1, "payload.bin", false).unwrap();
let dummy_chunk = vec![0xAAu8; 1024 * 1024]; // 1 MiB
db.write_chunk_and_update_size(
node.id,
0,
0,
&[0u8; 12],
&[0u8; 16],
&dummy_chunk,
1024 * 1024,
123456,
)
.unwrap();
let fs = SanctumFs::new(db.clone(), dek, sanctum::crypto::FORMAT_VERSION);
// Rufe WebDAV get_quota ab
let (used, total_opt) = fs.get_quota().await.unwrap();
assert!(
used > 0,
"Z-04: Verwendeter Speicherplatz muss > 0 sein (gemeldet: {} Bytes)",
used
);
assert!(
total_opt.is_some(),
"Z-04: Gesamtkapazität muss gemeldet werden"
);
let total = total_opt.unwrap();
assert!(
total >= used,
"Z-04: Gesamtkapazität ({}) muss mindestens dem belegten Speicher ({}) entsprechen",
total,
used
);
drop(fs);
drop(db);
let _ = std::fs::remove_file(&container_path);
}