Skip to main content

tpt_archon_kernel/
memory.rs

1//! Unified memory management: the kernel page cache *is* the DB buffer pool.
2//!
3//! [`UnifiedMemory`] holds a single [`UnifiedPageCache`] (from
4//! `tpt-archon-bridge`) and exposes it as both the kernel's page cache and the
5//! database's buffer pool. There is deliberately no second allocation: memory
6//! mapping a storage page and buffering it for the database are the same
7//! operation over the same bytes, gated by the same capability system.
8//!
9//! A real Linux/Windows OS-level `mmap` read path now exists too (opt-in
10//! `mmap` feature, see [`tpt_archon_bridge::page_cache::MmapPageSource`]) —
11//! genuine shared virtual memory for reads, not just an in-process reference.
12//! Writes still go through the `UnifiedPageCache`/`BufferPool` path above;
13//! real `mmap`-backed *writes* and bare-metal work remain deferred (Risk 1
14//! mitigation from `spec.txt`) — see `TODO.md` Phase 2b for the rationale.
15
16use tpt_archon_bridge::capability::Capability;
17use tpt_archon_bridge::page_cache::{CacheError, UnifiedPageCache};
18use tpt_archon_core::page::Page;
19
20/// The kernel's memory manager, wrapping a page cache.
21///
22/// Generic over the cache type; the bound lives on the `impl` blocks that
23/// actually need it (not the struct) so `UnifiedMemory<C>` also works over a
24/// cache that only implements the read-only, `mmap`-backed
25/// [`MmapPageSource`](tpt_archon_bridge::page_cache::MmapPageSource) trait
26/// rather than the full read/write [`UnifiedPageCache`].
27pub struct UnifiedMemory<C> {
28    cache: C,
29}
30
31impl<C> UnifiedMemory<C> {
32    /// Wraps a page cache.
33    pub fn new(cache: C) -> Self {
34        Self { cache }
35    }
36
37    /// Consumes the manager, returning the wrapped cache.
38    pub fn into_cache(self) -> C {
39        self.cache
40    }
41}
42
43impl<C: UnifiedPageCache> UnifiedMemory<C> {
44    /// Maps a page for reading (capability-checked). Same bytes the storage
45    /// engine holds.
46    pub fn map_read(&mut self, cap: &Capability, block_id: u64) -> Result<&Page, CacheError> {
47        self.cache.map_read(cap, block_id)
48    }
49
50    /// Maps a page for writing (capability-checked).
51    pub fn map_write(&mut self, cap: &Capability, block_id: u64) -> Result<&mut Page, CacheError> {
52        self.cache.map_write(cap, block_id)
53    }
54
55    /// Releases a mapping.
56    pub fn unmap(&mut self, block_id: u64) {
57        self.cache.unmap(block_id);
58    }
59}
60
61#[cfg(all(feature = "std", feature = "mmap"))]
62impl<C: tpt_archon_bridge::page_cache::MmapPageSource> UnifiedMemory<C> {
63    /// Genuinely zero-copy: the kernel and the storage layer read the same
64    /// OS-mapped bytes, no `BufferPool`, no copy. See
65    /// [`MmapPageSource`](tpt_archon_bridge::page_cache::MmapPageSource)'s
66    /// docs for why this is a separate, read-only path.
67    pub fn map_read_zero_copy(
68        &self,
69        cap: &Capability,
70        block_id: u64,
71    ) -> Result<&[u8; tpt_archon_core::page::PAGE_SIZE], CacheError> {
72        self.cache.map_read_zero_copy(cap, block_id)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use alloc::rc::Rc;
80    use core::cell::RefCell;
81    use tpt_archon_bridge::capability::{CapabilityIssuer, Resource, Right};
82    use tpt_archon_bridge::page_cache::{CacheError, CorePageCache};
83    use tpt_archon_core::block::InMemoryBlockDevice;
84    use tpt_archon_core::page::BufferPool;
85
86    #[test]
87    fn unified_memory_shares_storage_pages() {
88        let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
89        let rw = issuer
90            .borrow_mut()
91            .mint(Resource::Page(1), Right::ReadWrite);
92
93        let cache = CorePageCache::new(BufferPool::new(InMemoryBlockDevice::new(4), 2), issuer);
94        let mut mem = UnifiedMemory::new(cache);
95
96        {
97            let page = mem.map_write(&rw, 1).unwrap();
98            page.as_bytes_mut()[0] = 0x5A;
99        }
100        mem.unmap(1);
101
102        let page = mem.map_read(&rw, 1).unwrap();
103        assert_eq!(page.as_bytes()[0], 0x5A);
104        mem.unmap(1);
105    }
106
107    #[test]
108    fn unified_memory_denies_revoked_capability() {
109        // Regression test for security-audit finding 1: `UnifiedMemory`
110        // delegates straight to the underlying `UnifiedPageCache`, so a
111        // revoked capability must be denied here too, not just when the
112        // issuer is consulted directly.
113        let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
114        let rw = issuer
115            .borrow_mut()
116            .mint(Resource::Page(1), Right::ReadWrite);
117
118        let cache = CorePageCache::new(
119            BufferPool::new(InMemoryBlockDevice::new(4), 2),
120            issuer.clone(),
121        );
122        let mut mem = UnifiedMemory::new(cache);
123
124        mem.map_write(&rw, 1).unwrap();
125        issuer.borrow_mut().revoke(&rw);
126        assert_eq!(mem.map_read(&rw, 1).err(), Some(CacheError::Denied));
127        assert_eq!(mem.map_write(&rw, 1).err(), Some(CacheError::Denied));
128    }
129}
130
131#[cfg(all(test, feature = "std", feature = "mmap"))]
132mod mmap_tests {
133    use super::*;
134    use alloc::rc::Rc;
135    use core::cell::RefCell;
136    use tpt_archon_bridge::capability::{CapabilityIssuer, Resource, Right};
137    use tpt_archon_bridge::page_cache::{CacheError, MmapPageCache};
138    use tpt_archon_core::block::MmapBlockDevice;
139    use tpt_archon_core::page::PAGE_SIZE;
140    use tpt_archon_core::storage::Database;
141
142    fn temp_db(name: &str) -> std::path::PathBuf {
143        let mut p = std::env::temp_dir();
144        p.push(format!(
145            "tpt-archon-kernel-mmap-{}-{}.bin",
146            name,
147            std::process::id()
148        ));
149        let _ = std::fs::remove_file(&p);
150        p
151    }
152
153    #[test]
154    fn unified_memory_zero_copy_read_over_mmap() {
155        let path = temp_db("shared");
156        let mut db = Database::create(&path, 4).unwrap();
157        db.put(1, &[0x5Au8; PAGE_SIZE]).unwrap();
158
159        let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
160        let ro = issuer.borrow_mut().mint(Resource::Page(1), Right::Read);
161        let cache = MmapPageCache::new(MmapBlockDevice::open(&path).unwrap(), issuer);
162        let mem = UnifiedMemory::new(cache);
163
164        assert_eq!(mem.map_read_zero_copy(&ro, 1).unwrap()[0], 0x5A);
165        let _ = std::fs::remove_file(&path);
166    }
167
168    #[test]
169    fn unified_memory_mmap_denies_revoked_capability() {
170        let path = temp_db("revoked");
171        let _ = Database::create(&path, 4).unwrap();
172
173        let issuer = Rc::new(RefCell::new(CapabilityIssuer::new()));
174        let ro = issuer.borrow_mut().mint(Resource::Page(0), Right::Read);
175        let cache = MmapPageCache::new(MmapBlockDevice::open(&path).unwrap(), issuer.clone());
176        let mem = UnifiedMemory::new(cache);
177
178        mem.map_read_zero_copy(&ro, 0).unwrap();
179        issuer.borrow_mut().revoke(&ro);
180        assert_eq!(
181            mem.map_read_zero_copy(&ro, 0).err(),
182            Some(CacheError::Denied)
183        );
184        let _ = std::fs::remove_file(&path);
185    }
186}