fix(vfs): Z-04 — implement webdav quota report
This commit is contained in:
@@ -885,6 +885,22 @@ impl DavFileSystem for CarrierFs {
|
||||
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.
|
||||
|
||||
@@ -349,6 +349,38 @@ impl Database {
|
||||
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).
|
||||
pub fn with_session(&self, slot_id: u32, dek: Zeroizing<[u8; 32]>) -> Self {
|
||||
Self {
|
||||
|
||||
+21
-3
@@ -1127,10 +1127,28 @@ impl DavFileSystem for SanctumFs {
|
||||
}
|
||||
|
||||
fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> {
|
||||
if let Some(ref cfs) = self.carrier_fs {
|
||||
return cfs.get_quota();
|
||||
}
|
||||
|
||||
Box::pin(async move {
|
||||
// Virtueller Speicherplatz für Explorer: 1 TB
|
||||
let total_capacity: u64 = 1024 * 1024 * 1024 * 1024;
|
||||
Ok((0, Some(total_capacity)))
|
||||
self.touch();
|
||||
// Z-04: Echte Containergröße (physisch auf Disk) und freier Host-Speicher
|
||||
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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -242,4 +242,73 @@ fn test_z01_decoy_password_zeroize_memory() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user