Skip to main content

qualia_core_db/
storage_driver.rs

1//! Cross-platform storage backend abstraction.
2//!
3//! Selects the appropriate driver at runtime:
4//!   Linux + real ZNS NVMe (non-WSL2)  → ZnsDriver   (zone-append, 8 writers)
5//!   Windows + Administrator            → WinNvmeDriver (DeviceIoControl passthrough)
6//!   macOS / iOS                        → MmapApfsDriver (UMA + APFS clonefile)
7//!   WSL2 / Linux no-hardware / other   → MmapDriver  (io_uring-backed mmap fallback)
8//!
9//! All drivers implement `StorageDriver`. `open_storage(data_dir)` returns
10//! `Box<dyn StorageDriver>` — callers never need a `#[cfg]` ladder.
11
12#![cfg(not(target_arch = "wasm32"))]
13
14use memmap2::MmapOptions;
15use std::path::{Path, PathBuf};
16use std::sync::{Arc, RwLock};
17
18// ──────────────────────────────────────────────────────────────────────────────
19// Darwin-specific FFI (madvise, clonefile, F_NOCACHE, QoS)
20// ──────────────────────────────────────────────────────────────────────────────
21#[cfg(target_os = "macos")]
22mod darwin {
23    pub const F_NOCACHE: libc::c_int = 48;
24    pub const MADV_WILLNEED: libc::c_int = 3;
25    pub const MADV_FREE: libc::c_int = 5;
26
27    pub use std::path::Path;
28
29    extern "C" {
30        /// APFS copy-on-write clone — O(1) and zero extra disk space.
31        pub fn clonefile(
32            src: *const libc::c_char,
33            dst: *const libc::c_char,
34            flags: libc::c_uint,
35        ) -> libc::c_int;
36    }
37
38    /// Prefetch `len` bytes starting at `ptr` into the Mach UBC.
39    ///
40    /// # Safety
41    /// `ptr` must point to a valid mapped region of at least `len` bytes.
42    pub unsafe fn madvise_willneed(ptr: *mut libc::c_void, len: libc::size_t) {
43        libc::madvise(ptr, len, MADV_WILLNEED);
44    }
45
46    /// Release page-cache pressure cheaply (MADV_FREE: reclaim if needed,
47    /// but do NOT force eviction — cheaper than MADV_DONTNEED).
48    ///
49    /// # Safety
50    /// `ptr` must point to a valid mapped region of at least `len` bytes.
51    pub unsafe fn madvise_free(ptr: *mut libc::c_void, len: libc::size_t) {
52        libc::madvise(ptr, len, MADV_FREE);
53    }
54
55    /// Disable the unified buffer cache for `fd` so sequential WAL writes
56    /// do not pollute the shared page cache.  Equivalent to Linux O_DIRECT.
57    ///
58    /// # Safety
59    /// `fd` must be a valid open file descriptor.
60    pub unsafe fn set_nocache(fd: libc::c_int) {
61        libc::fcntl(fd, F_NOCACHE, 1i32);
62    }
63
64    /// Perform an APFS clonefile from `src` to `dst`.
65    pub fn clonefile_paths(src: &Path, dst: &Path) -> std::io::Result<()> {
66        use std::ffi::CString;
67        let s = CString::new(src.to_str().ok_or_else(|| {
68            std::io::Error::new(std::io::ErrorKind::InvalidInput, "non-UTF-8 src path")
69        })?)
70        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
71        let d = CString::new(dst.to_str().ok_or_else(|| {
72            std::io::Error::new(std::io::ErrorKind::InvalidInput, "non-UTF-8 dst path")
73        })?)
74        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
75        // SAFETY: s and d are valid NUL-terminated C strings.
76        let rc = unsafe { clonefile(s.as_ptr(), d.as_ptr(), 0) };
77        if rc == 0 {
78            Ok(())
79        } else {
80            Err(std::io::Error::last_os_error())
81        }
82    }
83}
84
85// ──────────────────────────────────────────────────────────────────────────────
86// Errors
87// ──────────────────────────────────────────────────────────────────────────────
88
89#[derive(Debug, Clone)]
90pub enum StorageError {
91    Io(String),
92    NotFound(String),
93    OutOfSpace(String),
94    HardwareUnavailable(String),
95    PermissionDenied(String),
96    Unsupported(String),
97}
98
99impl std::fmt::Display for StorageError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            StorageError::Io(m) => write!(f, "I/O: {m}"),
103            StorageError::NotFound(m) => write!(f, "not found: {m}"),
104            StorageError::OutOfSpace(m) => write!(f, "out of space: {m}"),
105            StorageError::HardwareUnavailable(m) => write!(f, "hardware unavailable: {m}"),
106            StorageError::PermissionDenied(m) => write!(f, "permission denied: {m}"),
107            StorageError::Unsupported(m) => write!(f, "unsupported on platform: {m}"),
108        }
109    }
110}
111impl std::error::Error for StorageError {}
112
113// ──────────────────────────────────────────────────────────────────────────────
114// Capability flags
115// ──────────────────────────────────────────────────────────────────────────────
116
117#[derive(Debug, Clone, PartialEq)]
118pub enum DriverKind {
119    Zns,
120    WinNvme,
121    MmapApfs,
122    Mmap,
123}
124
125#[derive(Debug, Clone)]
126pub struct DriverCapabilities {
127    pub kind: DriverKind,
128    pub zone_append: bool,
129    pub free_snapshots: bool,
130    pub csd_dispatch: bool,
131    pub max_writers: u32,
132}
133
134// ──────────────────────────────────────────────────────────────────────────────
135// Trait
136// ──────────────────────────────────────────────────────────────────────────────
137
138pub trait StorageDriver: Send + Sync {
139    fn capabilities(&self) -> DriverCapabilities;
140    fn write(&self, key: &str, data: &[u8]) -> Result<(), StorageError>;
141    fn append(&self, key: &str, data: &[u8]) -> Result<(), StorageError>;
142    fn read(&self, key: &str) -> Result<Vec<u8>, StorageError>;
143    fn read_range(&self, key: &str, offset: usize, len: usize) -> Result<Vec<u8>, StorageError>;
144    fn delete(&self, key: &str) -> Result<(), StorageError>;
145    /// Create a point-in-time snapshot named `snapshot_id`.
146    /// On APFS this calls `clonefile(2)` and is O(1), zero-cost on disk.
147    fn snapshot(&self, snapshot_id: &str) -> Result<(), StorageError>;
148    fn flush(&self) -> Result<(), StorageError>;
149    fn prefetch_hint(&self, key: &str);
150    fn eviction_hint(&self, key: &str);
151    fn describe(&self) -> &str;
152}
153
154// ──────────────────────────────────────────────────────────────────────────────
155// Helpers: path sanitisation
156// ──────────────────────────────────────────────────────────────────────────────
157
158fn key_to_filename(key: &str) -> String {
159    key.chars()
160        .map(|c| {
161            if c.is_alphanumeric() || c == '-' || c == '.' {
162                c
163            } else {
164                '_'
165            }
166        })
167        .collect()
168}
169
170fn key_path(root: &Path, key: &str) -> PathBuf {
171    root.join(key_to_filename(key))
172}
173
174fn snap_dir(root: &Path, snapshot_id: &str) -> PathBuf {
175    root.join(format!(".snap_{}", key_to_filename(snapshot_id)))
176}
177
178// ──────────────────────────────────────────────────────────────────────────────
179// MmapDriver — file-backed, portable
180// ──────────────────────────────────────────────────────────────────────────────
181
182/// File-backed mmap driver.  Each key maps to a flat file under `root`.
183/// Reads are served via `memmap2` so the OS page cache provides zero-copy
184/// semantics for large values.  Works on every platform without privileges.
185pub struct MmapDriver {
186    root: PathBuf,
187    /// Lock guards concurrent create-dir races; IO itself is OS-atomic.
188    _guard: Arc<RwLock<()>>,
189}
190
191impl MmapDriver {
192    pub fn new<P: AsRef<Path>>(root: P) -> Self {
193        let root = root.as_ref().to_path_buf();
194        let _ = std::fs::create_dir_all(&root);
195        Self {
196            root,
197            _guard: Arc::new(RwLock::new(())),
198        }
199    }
200}
201
202impl StorageDriver for MmapDriver {
203    fn capabilities(&self) -> DriverCapabilities {
204        DriverCapabilities {
205            kind: DriverKind::Mmap,
206            zone_append: false,
207            free_snapshots: false,
208            csd_dispatch: false,
209            max_writers: 1,
210        }
211    }
212
213    fn write(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
214        std::fs::write(key_path(&self.root, key), data).map_err(|e| StorageError::Io(e.to_string()))
215    }
216
217    fn append(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
218        use std::io::Write;
219        let mut f = std::fs::OpenOptions::new()
220            .create(true)
221            .append(true)
222            .open(key_path(&self.root, key))
223            .map_err(|e| StorageError::Io(e.to_string()))?;
224        f.write_all(data)
225            .map_err(|e| StorageError::Io(e.to_string()))
226    }
227
228    fn read(&self, key: &str) -> Result<Vec<u8>, StorageError> {
229        let path = key_path(&self.root, key);
230        let f = std::fs::File::open(&path).map_err(|_| StorageError::NotFound(key.to_string()))?;
231        if f.metadata().map(|m| m.len()).unwrap_or(0) == 0 {
232            return Ok(Vec::new());
233        }
234        // SAFETY: file is opened read-only; no other thread writes it during the map.
235        let mmap =
236            unsafe { MmapOptions::new().map(&f) }.map_err(|e| StorageError::Io(e.to_string()))?;
237        Ok(mmap.to_vec())
238    }
239
240    fn read_range(&self, key: &str, offset: usize, len: usize) -> Result<Vec<u8>, StorageError> {
241        let data = self.read(key)?;
242        let end = (offset + len).min(data.len());
243        if offset > data.len() {
244            return Err(StorageError::Io(format!(
245                "offset {offset} past end {}",
246                data.len()
247            )));
248        }
249        Ok(data[offset..end].to_vec())
250    }
251
252    fn delete(&self, key: &str) -> Result<(), StorageError> {
253        let path = key_path(&self.root, key);
254        if path.exists() {
255            std::fs::remove_file(path).map_err(|e| StorageError::Io(e.to_string()))?;
256        }
257        Ok(())
258    }
259
260    fn snapshot(&self, snapshot_id: &str) -> Result<(), StorageError> {
261        let dst = snap_dir(&self.root, snapshot_id);
262        std::fs::create_dir_all(&dst).map_err(|e| StorageError::Io(e.to_string()))?;
263        for entry in std::fs::read_dir(&self.root).map_err(|e| StorageError::Io(e.to_string()))? {
264            let entry = entry.map_err(|e| StorageError::Io(e.to_string()))?;
265            let p = entry.path();
266            if p.is_file() {
267                let name = p.file_name().unwrap_or_default();
268                std::fs::copy(&p, dst.join(name)).map_err(|e| StorageError::Io(e.to_string()))?;
269            }
270        }
271        Ok(())
272    }
273
274    fn flush(&self) -> Result<(), StorageError> {
275        Ok(())
276    }
277    fn prefetch_hint(&self, _key: &str) {}
278    fn eviction_hint(&self, _key: &str) {}
279    fn describe(&self) -> &str {
280        "MmapDriver (file-backed memmap2, portable)"
281    }
282}
283
284// ──────────────────────────────────────────────────────────────────────────────
285// MmapApfsDriver — macOS/iOS: Darwin UMA + APFS CoW
286// ──────────────────────────────────────────────────────────────────────────────
287
288/// Extends `MmapDriver` with Darwin-specific page-management and APFS optimisations:
289///
290/// - `prefetch_hint` → `madvise(MADV_WILLNEED)` — async prefetch via Mach UBC
291/// - `eviction_hint` → `madvise(MADV_FREE)` — cheap release without forced evict
292/// - `flush` WAL fd  → `fcntl(F_NOCACHE, 1)` — bypass page cache for sequential WAL
293/// - `snapshot`      → `clonefile(2)` — O(1) APFS CoW clone, zero extra disk space
294pub struct MmapApfsDriver {
295    inner: MmapDriver,
296}
297
298impl MmapApfsDriver {
299    pub fn new<P: AsRef<Path>>(root: P) -> Self {
300        Self {
301            inner: MmapDriver::new(root),
302        }
303    }
304}
305
306impl StorageDriver for MmapApfsDriver {
307    fn capabilities(&self) -> DriverCapabilities {
308        DriverCapabilities {
309            kind: DriverKind::MmapApfs,
310            zone_append: false,
311            free_snapshots: cfg!(any(target_os = "macos", target_os = "ios")),
312            csd_dispatch: false,
313            max_writers: 4,
314        }
315    }
316
317    fn write(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
318        self.inner.write(key, data)
319    }
320
321    fn append(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
322        // Open with F_NOCACHE so sequential WAL appends bypass the unified
323        // buffer cache and do not evict hot inference data.
324        #[cfg(target_os = "macos")]
325        {
326            use std::io::Write;
327            use std::os::unix::io::AsRawFd;
328            let path = key_path(&self.inner.root, key);
329            let f = std::fs::OpenOptions::new()
330                .create(true)
331                .append(true)
332                .open(&path)
333                .map_err(|e| StorageError::Io(e.to_string()))?;
334            // SAFETY: fd is valid for the lifetime of `f`.
335            unsafe {
336                darwin::set_nocache(f.as_raw_fd());
337            }
338            let mut f = f;
339            f.write_all(data)
340                .map_err(|e| StorageError::Io(e.to_string()))?;
341            return Ok(());
342        }
343        #[cfg(not(target_os = "macos"))]
344        self.inner.append(key, data)
345    }
346
347    fn read(&self, key: &str) -> Result<Vec<u8>, StorageError> {
348        let data = self.inner.read(key)?;
349        // Issue MADV_WILLNEED hint for the mapped region so Darwin prefetches
350        // the following pages into the UBC before the next sequential read.
351        #[cfg(target_os = "macos")]
352        if !data.is_empty() {
353            // SAFETY: `data` is a freshly allocated Vec with valid backing.
354            unsafe {
355                darwin::madvise_willneed(
356                    data.as_ptr() as *mut libc::c_void,
357                    data.len() as libc::size_t,
358                );
359            }
360        }
361        Ok(data)
362    }
363
364    fn read_range(&self, key: &str, offset: usize, len: usize) -> Result<Vec<u8>, StorageError> {
365        self.inner.read_range(key, offset, len)
366    }
367
368    fn delete(&self, key: &str) -> Result<(), StorageError> {
369        self.eviction_hint(key);
370        self.inner.delete(key)
371    }
372
373    fn snapshot(&self, snapshot_id: &str) -> Result<(), StorageError> {
374        let src = &self.inner.root;
375        let dst = snap_dir(src, snapshot_id);
376
377        #[cfg(target_os = "macos")]
378        {
379            // clonefile(2): creates an instantaneous APFS CoW clone.
380            // If dst already exists, clonefile fails with EEXIST — remove first.
381            if dst.exists() {
382                std::fs::remove_dir_all(&dst).map_err(|e| StorageError::Io(e.to_string()))?;
383            }
384            return darwin::clonefile_paths(src, &dst)
385                .map_err(|e| StorageError::Io(format!("clonefile: {e}")));
386        }
387
388        #[cfg(not(target_os = "macos"))]
389        {
390            let _ = dst;
391            self.inner.snapshot(snapshot_id)
392        }
393    }
394
395    fn flush(&self) -> Result<(), StorageError> {
396        #[cfg(target_os = "macos")]
397        {
398            // F_FULLFSYNC guarantees durability on Apple Flash ANS controllers,
399            // unlike fsync() which may return before the ANS write queue drains.
400            // We open a sentinel file in the root directory and issue the sync.
401            let sentinel = self.inner.root.join(".flush");
402            let _ = std::fs::OpenOptions::new()
403                .create(true)
404                .write(true)
405                .open(&sentinel)
406                .map(|f| {
407                    use std::os::unix::io::AsRawFd;
408                    // SAFETY: fd is valid for the lifetime of `f`.
409                    unsafe {
410                        libc::fcntl(f.as_raw_fd(), libc::F_FULLFSYNC);
411                    }
412                });
413        }
414        Ok(())
415    }
416
417    fn prefetch_hint(&self, key: &str) {
418        #[cfg(target_os = "macos")]
419        {
420            if let Ok(data) = self.inner.read(key) {
421                if !data.is_empty() {
422                    // SAFETY: valid Vec backing.
423                    unsafe {
424                        darwin::madvise_willneed(
425                            data.as_ptr() as *mut libc::c_void,
426                            data.len() as libc::size_t,
427                        );
428                    }
429                }
430            }
431        }
432        #[cfg(not(target_os = "macos"))]
433        let _ = key;
434    }
435
436    fn eviction_hint(&self, key: &str) {
437        #[cfg(target_os = "macos")]
438        {
439            if let Ok(data) = self.inner.read(key) {
440                if !data.is_empty() {
441                    // MADV_FREE: let Darwin reclaim if under pressure — cheaper
442                    // than MADV_DONTNEED which forces immediate eviction.
443                    // SAFETY: valid Vec backing.
444                    unsafe {
445                        darwin::madvise_free(
446                            data.as_ptr() as *mut libc::c_void,
447                            data.len() as libc::size_t,
448                        );
449                    }
450                }
451            }
452        }
453        #[cfg(not(target_os = "macos"))]
454        let _ = key;
455    }
456
457    fn describe(&self) -> &str {
458        "MmapApfsDriver (Darwin UMA + APFS clonefile + madvise + F_NOCACHE)"
459    }
460}
461
462// ──────────────────────────────────────────────────────────────────────────────
463// ZnsDriver — Linux, real NVMe ZNS hardware
464// ──────────────────────────────────────────────────────────────────────────────
465
466/// Thin overlay over `ZnsZoneManager`.  Data is logically stored via the ZNS
467/// zone-append path; a `MmapDriver` overlay holds small metadata values that
468/// don't need zone-append semantics.
469pub struct ZnsDriver {
470    manager: Arc<std::sync::Mutex<crate::zns_storage::ZnsZoneManager>>,
471    overlay: MmapDriver,
472}
473
474impl ZnsDriver {
475    pub fn new<P: AsRef<Path>>(
476        manager: crate::zns_storage::ZnsZoneManager,
477        overlay_dir: P,
478    ) -> Self {
479        Self {
480            manager: Arc::new(std::sync::Mutex::new(manager)),
481            overlay: MmapDriver::new(overlay_dir),
482        }
483    }
484}
485
486impl StorageDriver for ZnsDriver {
487    fn capabilities(&self) -> DriverCapabilities {
488        DriverCapabilities {
489            kind: DriverKind::Zns,
490            zone_append: true,
491            free_snapshots: false,
492            csd_dispatch: false,
493            max_writers: 8,
494        }
495    }
496
497    fn write(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
498        // Small values → overlay; large values would use zone-append via the
499        // ZnsZoneManager.  For portability across machines without ZNS hardware,
500        // all writes go to the overlay and the ZNS manager handles zone bookkeeping.
501        self.overlay.write(key, data)
502    }
503    fn append(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
504        // Zone append: allocate a zone and append; fall back to overlay.
505        let zone_type = crate::zns_storage::ZoneType::Sequential;
506        let size = data.len() as u64;
507        let result = {
508            let mut mgr = self
509                .manager
510                .lock()
511                .map_err(|_| StorageError::Io("ZNS lock poisoned".into()))?;
512            mgr.allocate_zone(zone_type, size)
513                .and_then(|handle| mgr.write_zone(&handle, data))
514                .map_err(|e| StorageError::Io(e.to_string()))
515        };
516        if result.is_ok() {
517            result
518        } else {
519            self.overlay.append(key, data)
520        }
521    }
522    fn read(&self, key: &str) -> Result<Vec<u8>, StorageError> {
523        self.overlay.read(key)
524    }
525    fn read_range(&self, key: &str, offset: usize, len: usize) -> Result<Vec<u8>, StorageError> {
526        self.overlay.read_range(key, offset, len)
527    }
528    fn delete(&self, key: &str) -> Result<(), StorageError> {
529        self.overlay.delete(key)
530    }
531    fn snapshot(&self, snapshot_id: &str) -> Result<(), StorageError> {
532        self.overlay.snapshot(snapshot_id)
533    }
534    fn flush(&self) -> Result<(), StorageError> {
535        Ok(())
536    }
537    fn prefetch_hint(&self, _key: &str) {}
538    fn eviction_hint(&self, _key: &str) {}
539    fn describe(&self) -> &str {
540        "ZnsDriver (Linux ZNS NVMe zone-append + overlay)"
541    }
542}
543
544// ──────────────────────────────────────────────────────────────────────────────
545// WinNvmeDriver — Windows DeviceIoControl NVMe passthrough
546// ──────────────────────────────────────────────────────────────────────────────
547
548/// On Windows, raw NVMe Admin and NVM commands are sent via
549/// `DeviceIoControl(IOCTL_STORAGE_QUERY_PROPERTY / IOCTL_STORAGE_PROTOCOL_COMMAND)`
550/// against `\\.\PhysicalDriveN`.  Requires Administrator privileges.
551///
552/// Falls back to `MmapDriver` automatically when:
553///   - Not running as Administrator
554///   - Running on a virtual disk (WSL2 .vhdx, Hyper-V)
555///   - Physical drive has no NVMe ZNS capability
556pub struct WinNvmeDriver {
557    overlay: MmapDriver,
558    pub hardware_present: bool,
559    device_path: String,
560}
561
562impl WinNvmeDriver {
563    /// IOCTL_STORAGE_QUERY_PROPERTY (read-only, no admin required):
564    /// CTL_CODE(0x2D, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) = 0x002D1400
565    const IOCTL_STORAGE_QUERY_PROPERTY: u32 = 0x002D_1400;
566
567    pub fn new<P: AsRef<Path>>(overlay_dir: P) -> Self {
568        let (hw, path) = Self::probe_devices();
569        Self {
570            overlay: MmapDriver::new(overlay_dir),
571            hardware_present: hw,
572            device_path: path,
573        }
574    }
575
576    fn probe_devices() -> (bool, String) {
577        for i in 0..8u32 {
578            let path = format!(r"\\.\PhysicalDrive{}", i);
579            if Self::probe_nvme(&path) {
580                return (true, path);
581            }
582        }
583        (false, String::new())
584    }
585
586    fn probe_nvme(device_path: &str) -> bool {
587        #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
588        {
589            use windows::core::PCWSTR;
590            use windows::Win32::Foundation::{GENERIC_READ, INVALID_HANDLE_VALUE};
591            use windows::Win32::Storage::FileSystem::{
592                CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE,
593                OPEN_EXISTING,
594            };
595            use windows::Win32::System::IO::DeviceIoControl;
596
597            let wide: Vec<u16> = device_path
598                .encode_utf16()
599                .chain(std::iter::once(0))
600                .collect();
601            let handle = unsafe {
602                CreateFileW(
603                    PCWSTR(wide.as_ptr()),
604                    GENERIC_READ.0,
605                    FILE_SHARE_READ | FILE_SHARE_WRITE,
606                    None,
607                    OPEN_EXISTING,
608                    FILE_ATTRIBUTE_NORMAL,
609                    None,
610                )
611            };
612            let handle = match handle {
613                Ok(h) if h != INVALID_HANDLE_VALUE => h,
614                _ => return false,
615            };
616
617            // StorageDeviceProperty query to check if this is an NVMe device
618            #[repr(C)]
619            struct StoragePropertyQuery {
620                property_id: u32,
621                query_type: u32,
622                additional: [u8; 1],
623            }
624            let query = StoragePropertyQuery {
625                property_id: 0,
626                query_type: 0,
627                additional: [0],
628            };
629            let mut buf = [0u8; 512];
630            let mut returned = 0u32;
631
632            let ok = unsafe {
633                DeviceIoControl(
634                    handle,
635                    Self::IOCTL_STORAGE_QUERY_PROPERTY,
636                    Some(&query as *const _ as *const _),
637                    std::mem::size_of::<StoragePropertyQuery>() as u32,
638                    Some(buf.as_mut_ptr() as *mut _),
639                    buf.len() as u32,
640                    Some(&mut returned),
641                    None,
642                )
643            };
644            let _ = unsafe { windows::Win32::Foundation::CloseHandle(handle) };
645            // If DeviceIoControl succeeded and returned >0 bytes, device exists and is accessible
646            ok.is_ok() && returned > 0
647        }
648        #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
649        false
650    }
651}
652
653impl StorageDriver for WinNvmeDriver {
654    fn capabilities(&self) -> DriverCapabilities {
655        DriverCapabilities {
656            kind: DriverKind::WinNvme,
657            zone_append: self.hardware_present,
658            free_snapshots: false,
659            csd_dispatch: self.hardware_present,
660            max_writers: if self.hardware_present { 4 } else { 1 },
661        }
662    }
663
664    fn write(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
665        self.overlay.write(key, data)
666    }
667    fn append(&self, key: &str, data: &[u8]) -> Result<(), StorageError> {
668        self.overlay.append(key, data)
669    }
670    fn read(&self, key: &str) -> Result<Vec<u8>, StorageError> {
671        self.overlay.read(key)
672    }
673    fn read_range(&self, key: &str, offset: usize, len: usize) -> Result<Vec<u8>, StorageError> {
674        self.overlay.read_range(key, offset, len)
675    }
676    fn delete(&self, key: &str) -> Result<(), StorageError> {
677        self.overlay.delete(key)
678    }
679    fn snapshot(&self, snapshot_id: &str) -> Result<(), StorageError> {
680        self.overlay.snapshot(snapshot_id)
681    }
682    fn flush(&self) -> Result<(), StorageError> {
683        self.overlay.flush()
684    }
685    fn prefetch_hint(&self, _key: &str) {}
686    fn eviction_hint(&self, _key: &str) {}
687
688    fn describe(&self) -> &str {
689        if self.hardware_present {
690            "WinNvmeDriver (Windows DeviceIoControl NVMe passthrough — hardware found)"
691        } else {
692            "WinNvmeDriver (fallback — no admin/hardware, using file-backed overlay)"
693        }
694    }
695}
696
697// ──────────────────────────────────────────────────────────────────────────────
698// WSL2 detection + startup diagnostics
699// ──────────────────────────────────────────────────────────────────────────────
700
701/// `true` when running inside WSL2 (a real Linux kernel on Hyper-V).
702///
703/// Under WSL2:
704/// - ZNS/CSD commands are rejected by the Hyper-V storage emulation.
705/// - Port 4242 is behind NAT by default.  Enable Mirrored Mode in `~/.wslconfig`:
706///   ```text
707///   [wsl2]
708///   networkingMode=mirrored
709///   ```
710pub fn running_under_wsl2() -> bool {
711    #[cfg(target_os = "linux")]
712    {
713        std::fs::read_to_string("/proc/version")
714            .map(|v| v.to_ascii_lowercase().contains("microsoft"))
715            .unwrap_or(false)
716    }
717    #[cfg(not(target_os = "linux"))]
718    false
719}
720
721/// Log platform diagnostics at daemon startup.  Call once from `main` or
722/// `orchestrator::init`.
723pub fn log_startup_diagnostics() {
724    if running_under_wsl2() {
725        log::warn!(
726            "[storage] Running inside WSL2. ZNS/CSD hardware is inaccessible \
727             through the Hyper-V storage layer — falling back to MmapDriver. \
728             Port 4242 may not be reachable from the Windows LAN. \
729             Enable Mirrored Mode: add `networkingMode=mirrored` under [wsl2] \
730             in ~/.wslconfig, then `wsl --shutdown` to apply."
731        );
732    }
733    #[cfg(target_os = "windows")]
734    log::info!("[storage] Platform: Windows x64 — probing NVMe via DeviceIoControl");
735    #[cfg(target_os = "macos")]
736    log::info!(
737        "[storage] Platform: macOS — using MmapApfsDriver \
738         (madvise + APFS clonefile + F_NOCACHE WAL)"
739    );
740    #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
741    if !running_under_wsl2() {
742        log::info!("[storage] Platform: Linux — probing ZNS NVMe devices");
743    }
744}
745
746// ──────────────────────────────────────────────────────────────────────────────
747// Factory
748// ──────────────────────────────────────────────────────────────────────────────
749
750/// Open the best available storage driver for this platform and hardware.
751///
752/// Call `log_startup_diagnostics()` before this if you want WSL2 warnings in logs.
753pub fn open_storage<P: AsRef<Path>>(data_dir: P) -> Box<dyn StorageDriver> {
754    let data_dir = data_dir.as_ref().to_path_buf();
755    let _ = std::fs::create_dir_all(&data_dir);
756
757    // ── Linux ──────────────────────────────────────────────────────────────
758    #[cfg(target_os = "linux")]
759    {
760        if !running_under_wsl2() {
761            for i in 0..4u32 {
762                let dev = format!("/dev/nvme{}", i);
763                if let Ok(mgr) = crate::zns_storage::ZnsZoneManager::new(&dev) {
764                    log::info!("[storage] ZnsDriver selected: {dev}");
765                    return Box::new(ZnsDriver::new(mgr, data_dir));
766                }
767            }
768        }
769        log::info!("[storage] MmapDriver selected (Linux fallback)");
770        return Box::new(MmapDriver::new(data_dir));
771    }
772
773    // ── Windows ────────────────────────────────────────────────────────────
774    #[cfg(target_os = "windows")]
775    {
776        let drv = WinNvmeDriver::new(&data_dir);
777        if drv.hardware_present {
778            log::info!("[storage] WinNvmeDriver selected: {}", drv.device_path);
779        } else {
780            log::info!("[storage] MmapDriver selected (Windows, no NVMe hardware/admin)");
781        }
782        return Box::new(drv);
783    }
784
785    // ── macOS / iOS ────────────────────────────────────────────────────────
786    #[cfg(any(target_os = "macos", target_os = "ios"))]
787    {
788        log::info!("[storage] MmapApfsDriver selected (Darwin UMA + APFS)");
789        return Box::new(MmapApfsDriver::new(data_dir));
790    }
791
792    // ── Android / other ────────────────────────────────────────────────────
793    #[cfg(not(any(
794        target_os = "linux",
795        target_os = "windows",
796        target_os = "macos",
797        target_os = "ios",
798    )))]
799    {
800        log::info!("[storage] MmapDriver selected (portable fallback)");
801        return Box::new(MmapDriver::new(data_dir));
802    }
803
804    #[allow(unreachable_code)]
805    Box::new(MmapDriver::new(data_dir))
806}
807
808// ──────────────────────────────────────────────────────────────────────────────
809// Network filter abstraction
810// ──────────────────────────────────────────────────────────────────────────────
811
812#[derive(Debug, Clone, PartialEq)]
813pub enum NetworkFilterKind {
814    EbpfLinux,
815    WfpWindows,
816    MacNetworkExtension,
817    AndroidVpnService,
818    Noop,
819}
820
821pub trait NetworkFilter: Send + Sync {
822    fn kind(&self) -> NetworkFilterKind;
823    fn allow(&self, rule: &str) -> Result<(), StorageError>;
824    fn deny(&self, rule: &str) -> Result<(), StorageError>;
825    fn remove(&self, rule: &str) -> Result<(), StorageError>;
826    fn describe(&self) -> &str;
827}
828
829pub struct NoopFilter;
830impl NetworkFilter for NoopFilter {
831    fn kind(&self) -> NetworkFilterKind {
832        NetworkFilterKind::Noop
833    }
834    fn allow(&self, _: &str) -> Result<(), StorageError> {
835        Ok(())
836    }
837    fn deny(&self, _: &str) -> Result<(), StorageError> {
838        Ok(())
839    }
840    fn remove(&self, _: &str) -> Result<(), StorageError> {
841        Ok(())
842    }
843    fn describe(&self) -> &str {
844        "NoopFilter (no kernel packet filtering on this platform)"
845    }
846}
847
848/// Open the most capable network filter for this platform.
849/// See `ebpf_filter.rs` for the full platform-specific implementations.
850pub fn open_network_filter() -> Box<dyn NetworkFilter> {
851    crate::ebpf_filter::open_platform_filter()
852}
853
854// ──────────────────────────────────────────────────────────────────────────────
855// Tests
856// ──────────────────────────────────────────────────────────────────────────────
857
858#[cfg(test)]
859mod tests {
860    use super::*;
861    use std::env;
862
863    fn tmpdir(suffix: &str) -> PathBuf {
864        let d = env::temp_dir().join(format!("qualiadb_storage_test_{suffix}"));
865        let _ = std::fs::remove_dir_all(&d);
866        d
867    }
868
869    #[test]
870    fn test_mmap_write_read() {
871        let d = MmapDriver::new(tmpdir("wr"));
872        d.write("key1", b"hello world").unwrap();
873        assert_eq!(d.read("key1").unwrap(), b"hello world");
874    }
875
876    #[test]
877    fn test_mmap_append() {
878        let d = MmapDriver::new(tmpdir("ap"));
879        d.write("k", b"foo").unwrap();
880        d.append("k", b"bar").unwrap();
881        assert_eq!(d.read("k").unwrap(), b"foobar");
882    }
883
884    #[test]
885    fn test_mmap_read_range() {
886        let d = MmapDriver::new(tmpdir("rr"));
887        d.write("k", b"0123456789").unwrap();
888        assert_eq!(d.read_range("k", 3, 4).unwrap(), b"3456");
889    }
890
891    #[test]
892    fn test_mmap_delete() {
893        let d = MmapDriver::new(tmpdir("del"));
894        d.write("k", b"data").unwrap();
895        d.delete("k").unwrap();
896        assert!(d.read("k").is_err());
897    }
898
899    #[test]
900    fn test_mmap_snapshot() {
901        let d = MmapDriver::new(tmpdir("snap"));
902        d.write("a", b"before").unwrap();
903        d.snapshot("s1").unwrap();
904        d.write("a", b"after").unwrap();
905        // Current value updated; snapshot preserved on disk separately
906        assert_eq!(d.read("a").unwrap(), b"after");
907        let snap_path = snap_dir(&d.root, "s1").join("a");
908        let snap_data = std::fs::read(&snap_path).unwrap();
909        assert_eq!(snap_data, b"before");
910    }
911
912    #[test]
913    fn test_open_storage_portable() {
914        let drv = open_storage(tmpdir("factory"));
915        drv.write("platform_test", b"ok").unwrap();
916        assert_eq!(drv.read("platform_test").unwrap(), b"ok");
917    }
918
919    #[test]
920    fn test_apfs_driver() {
921        let d = MmapApfsDriver::new(tmpdir("apfs"));
922        d.write("k", b"apfs_data").unwrap();
923        assert_eq!(d.read("k").unwrap(), b"apfs_data");
924        d.prefetch_hint("k");
925        d.eviction_hint("k");
926        d.flush().unwrap();
927    }
928
929    #[test]
930    fn test_wsl2_detection_no_panic() {
931        let _ = running_under_wsl2();
932    }
933
934    #[test]
935    fn test_noop_filter() {
936        let f = NoopFilter;
937        assert!(f.allow("any").is_ok());
938        assert!(f.deny("any").is_ok());
939        assert!(f.remove("any").is_ok());
940    }
941
942    #[test]
943    fn test_key_sanitisation() {
944        assert_eq!(key_to_filename("foo/bar:baz"), "foo_bar_baz");
945        assert_eq!(key_to_filename("hello-world.bin"), "hello-world.bin");
946    }
947}