fix(vfs): Z-04 — implement webdav quota report
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user