aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcos Magueta <maguetamarcos@gmail.com>2026-05-10 02:27:08 -0300
committerMarcos Magueta <maguetamarcos@gmail.com>2026-05-10 02:27:08 -0300
commit7ffd4d99bbb8e358c6694c27c7ff859967872ece (patch)
tree8af04f4032651de7e49d1cac01e6f20b2315b6cb
parent5ee324f03f8a7e6f09dad7b8580a2d32172694f8 (diff)
downloadRNT-7ffd4d99bbb8e358c6694c27c7ff859967872ece.tar.gz
RNT-7ffd4d99bbb8e358c6694c27c7ff859967872ece.zip
comparison to nt ob
-rw-r--r--docs/mainpage.dox17
-rw-r--r--docs/nt-architecture-mapping.md489
-rw-r--r--docs/reactos-ob-comparison.md281
-rw-r--r--include/HandlerManager.h26
-rw-r--r--include/IdentityManager.h24
-rw-r--r--include/LifecycleManager.h93
-rw-r--r--include/NamespaceReferenceManager.h54
-rw-r--r--include/ObjectManager.h77
-rw-r--r--include/PermissionsManager.h16
9 files changed, 1024 insertions, 53 deletions
diff --git a/docs/mainpage.dox b/docs/mainpage.dox
index 0013b0b..76f4eca 100644
--- a/docs/mainpage.dox
+++ b/docs/mainpage.dox
@@ -25,6 +25,23 @@
* - VM separates terminating relational retrieval from higher-order logic
* execution.
*
+ * @section design Design Notes
+ *
+ * The following documents record architectural decisions and comparisons with
+ * the ReactOS NT kernel, which this project draws from:
+ *
+ * - **docs/reactos-ob-comparison.md** — Object manager comparison: dual
+ * reference counting, type callbacks, hash directory, per-handle access
+ * masks, capability security, branch HEAD contention, and the namespace
+ * reparse layer.
+ *
+ * - **docs/nt-architecture-mapping.md** — Holistic mapping of the full NT
+ * kernel to a database kernel: wire protocols as environment subsystems,
+ * the I/O driver stack as the query operator pipeline, drivers as storage
+ * backends, MM as the buffer pool, CC as the WAL flusher, PS as the
+ * session manager, SE as the authorization engine, and LPC as the wire
+ * protocol connection layer.
+ *
* @section building Building
*
* From a Visual Studio/NMake environment:
diff --git a/docs/nt-architecture-mapping.md b/docs/nt-architecture-mapping.md
new file mode 100644
index 0000000..3510f82
--- /dev/null
+++ b/docs/nt-architecture-mapping.md
@@ -0,0 +1,489 @@
+# NT Kernel Architecture — Holistic Mapping to a Database Kernel
+
+A full-system view of the ReactOS NT kernel (`ntoskrnl/`) mapped onto the
+architecture of a database kernel. The object manager comparison is covered
+separately in `reactos-ob-comparison.md`; this document covers the rest of the
+kernel and treats the project as a whole.
+
+The central thesis: **NT is already a database kernel in disguise**. It manages
+named, typed, reference-counted objects; enforces capability-based access control;
+schedules work across a pool of threads; routes requests through a layered
+dispatch stack; and maintains a write-buffered, lazily-flushed cache in front of
+physical storage. Every one of those responsibilities has a direct counterpart in
+a relational database engine.
+
+---
+
+## Subsystem Map
+
+| NT subsystem | DB kernel counterpart |
+|---|---|
+| **KE** — kernel scheduler, IRQL, APC/DPC, synchronization primitives | Query scheduler, latch manager, deferred execution |
+| **PS** — process/thread lifecycle, job objects, quotas | Session manager, worker thread pool, resource groups |
+| **IO** — driver model, IRP dispatch stack, completion ports | Storage engine interface, query operator pipeline, async execution |
+| **MM** — virtual memory, page faults, working set, section objects | Buffer pool, page frame allocator, eviction policy |
+| **CC** — cache manager, lazy writer, VACB, prefetcher | WAL buffer / dirty page flusher, sequential prefetch |
+| **SE** — tokens, ACLs, DACL/SACL, privilege evaluation | User principal, role/privilege set, access control, audit |
+| **EX** — events, work queues, lookaside lists, rundown refs | Synchronization utilities, worker dispatch, buffer allocation |
+| **LPC** — port objects, request/reply messaging | Wire protocol layer, client connection, inter-session RPC |
+| **FSRTL** — fast I/O bypass, MCB extent map, file byte-range locks | Index hot-path, extent/page mapping, record-level locks |
+| **OB** — object manager | Object manager (covered in reactos-ob-comparison.md) |
+| **HAL** — CPU, interrupt, DMA, timer abstraction | Storage media abstraction, CPU/NUMA topology |
+| **KD** — kernel debugger transport | EXPLAIN / trace / diagnostic interface |
+| **CONFIG** — registry hive manager | System catalog, configuration store |
+
+---
+
+## 1. Subsystems as Client Protocols
+
+NT supports multiple *environment subsystems*: Win32 and POSIX both run on top of
+the same kernel. Each subsystem is a user-mode personality layered above a common
+native API (`ntdll.dll`). The kernel itself has no awareness of Win32 or POSIX;
+it exposes only native NT objects (files, processes, events). The subsystem DLLs
+translate environment-specific calls into native system calls.
+
+```
+Win32 application POSIX application
+ ↓ ↓
+ kernel32.dll libc (POSIX layer)
+ ↓ ↓
+ ntdll.dll (native NT API)
+ ↓
+ ntoskrnl (kernel)
+```
+
+**Database analogue: wire protocols as subsystems.**
+
+The database kernel (RelationalNT) is the `ntoskrnl`. Each supported wire
+protocol — PostgreSQL, MySQL, HTTP/JSON — is an environment subsystem. A thin
+native client library is the `ntdll` equivalent: it translates wire-protocol
+messages into kernel-level handle opens, cursor operations, and VM plan
+evaluations. The kernel itself never sees SQL syntax or protocol framing.
+
+```
+psql client MySQL client HTTP client
+ ↓ ↓ ↓
+ PG wire adapter MySQL wire adapter REST adapter
+ ↓ ↓ ↓
+ native client library (ntdll equivalent)
+ ↓
+ RelationalNT kernel
+```
+
+The subsystem boundary is where SQL parsing, result serialization, and
+protocol-specific error codes live. The kernel boundary is where object paths,
+handle allocation, cursor iteration, and capability checks live. Keeping these
+boundaries clean means a new wire protocol never touches the kernel; it only
+implements the native-to-protocol translation layer.
+
+`CSRSS` (the client-server runtime) is the service process that hosts
+session-specific subsystem state on the NT side. The DB equivalent is a per-
+protocol server daemon that owns wire connections and dispatches them to the
+native API.
+
+---
+
+## 2. IO Manager → Query Operator Pipeline
+
+The NT I/O manager's driver stack model is the most directly applicable pattern
+for query execution.
+
+### NT Driver Stack
+
+```
+NtReadFile
+ ↓
+I/O Manager: allocates IRP, fills stack location [0]
+ ↓ IoCallDriver()
+Filesystem driver (NTFS): interprets path, maps to block range
+ ↓ IoCallDriver()
+Disk class driver: translates logical → physical sectors
+ ↓ IoCallDriver()
+Disk port driver: issues SCSI/ATA commands
+ ↓ IoCallDriver()
+Miniport: hardware-specific DMA
+```
+
+Each driver layer processes its stack location and either completes the IRP
+inline (sync fast path) or pends it and calls the next driver. Completion fires
+back up through registered completion routines — bottom to top — until the
+original caller wakes.
+
+### Database Analogue: PlanNode as IRP
+
+The VM's `PlanNode` is an IRP. Each operator in the query plan is a driver layer
+with a dispatch routine (`Next()`). The Volcano pull model is completion-routine
+dispatch in reverse: instead of pushing results down-to-up on completion, the
+caller pulls up-to-down via `Next()`.
+
+```
+VM::Next(root_plan_node)
+ ↓ Next() dispatch
+JOIN operator: requests rows from left and right children
+ ↓ Next()
+FILTER operator: requests rows, discards those failing predicate
+ ↓ Next()
+PROJECT operator: strips unwanted attributes
+ ↓ Next()
+SCAN operator: pulls raw tuples from CursorManager
+ ↓
+CursorManager::Next() → physical storage
+```
+
+NT's `IRP_STACK_LOCATION` (one per driver layer) maps to each `PlanNode` holding
+its own operator-local state (join buffer, filter expression, projection map).
+NT's `IRP_MJ_READ` / `IRP_MJ_WRITE` major function codes map to `Op::SCAN`,
+`Op::JOIN`, `Op::FILTER`, `Op::PROJECT`.
+
+**Filter drivers.** NT allows filter drivers to insert between any two layers
+without modifying either. The DB equivalent is transparent middleware: a
+compression or encryption operator inserted between SCAN and PROJECT without
+touching either. An audit operator inserted at the top of every plan without
+touching the plan itself.
+
+**Async IRP / completion ports.** NT issues an IRP and gets a completion
+notification later. For the DB, a query issued by a session does not block the
+session thread if the operator tree must wait on I/O. A completion-port analogue
+(a result queue per session) allows the session thread to yield and be notified
+when the cursor has rows ready. This is relevant when the physical storage backend
+is remote (etcd, S3) and latency is non-trivial.
+
+---
+
+## 3. Drivers → Storage Backends
+
+A NT device driver is a self-contained module that satisfies read/write/control
+requests for a physical or logical device. The kernel does not know whether the
+device is a disk, a network socket, or a USB key; it only knows the driver
+dispatch table.
+
+`CursorManager` is the equivalent of the I/O manager's dispatch layer. The mock
+store (`unordered_map`) is the miniport. Replacing it with LMDB, RocksDB, etcd,
+or an in-memory append log is equivalent to swapping a miniport driver without
+changing the I/O manager or the filesystems above it.
+
+```
+CursorManager::Open / Next / Close
+ ↓
+Storage backend interface (the "driver dispatch table")
+ ↓
+ ┌─────────────┬──────────────┬─────────────┐
+ │ LMDB backend│ etcd backend │ memory backend│
+ └─────────────┴──────────────┴─────────────┘
+```
+
+The driver model implies that the storage backend interface should be stable and
+narrow: `Open(path, snapshot_id)`, `Next()`, `Close()`. All backend-specific
+concerns (transaction semantics, key encoding, compression) stay inside the
+backend module. `CursorManager` holds snapshot pinning and buffering logic that
+applies regardless of backend, exactly as the disk class driver holds caching
+logic regardless of whether the miniport is SATA or NVMe.
+
+**Filter driver analogue.** A replication backend can be a filter: it intercepts
+`Next()` calls after the underlying backend returns a row and ships a copy to a
+replica. A row-level encryption backend decrypts inline before returning to the
+caller. These compose without the operator pipeline or the caller knowing.
+
+---
+
+## 4. MM → Buffer Pool
+
+NT's Memory Manager manages physical page frames and virtual address mappings. The
+three-tier hierarchy is:
+
+```
+Process working set (recently used pages, trimmed under pressure)
+ ↓
+Cache Manager VACBs (256 KB mapped views of files in kernel VA space)
+ ↓
+Physical storage (disk, via filesystem IRP)
+```
+
+Page faults are demand-load: a page is only brought from disk when first accessed.
+The working set trimmer evicts least-used pages under memory pressure. Section
+objects allow multiple processes to map the same physical pages (shared memory).
+
+**Database buffer pool.** The same three tiers apply:
+
+```
+Query working set (pages referenced by active cursors, pinned)
+ ↓
+Buffer pool frames (fixed-size pages cached in memory)
+ ↓
+Physical storage backend
+```
+
+The NT page fault handler (`MmNotPresentFault`) maps to a buffer pool miss:
+when a cursor requests a page not in the buffer pool, load it from the backend.
+The NT working set trimmer maps to LRU eviction: under memory pressure, unpin
+pages not referenced by any active cursor. NT section objects (shared physical
+pages across processes) map to shared buffer pool pages accessible from multiple
+sessions.
+
+Because this project uses immutable snapshot storage, the eviction policy is
+simplified: a page belonging to a snapshot pinned by at least one cursor
+(`reference_count > 0` on the snapshot registry entry) cannot be evicted. Pages
+belonging to snapshots with no remaining cursors are candidates for eviction
+regardless of recency. This is cleaner than NT's working-set model because there
+is no dirty-page complexity — writes always produce new pages, never mutate
+existing ones.
+
+---
+
+## 5. CC → WAL and Dirty Page Flush
+
+NT's Cache Manager sits between the filesystem and MM. Its `VACB` (Virtual Address
+Control Block) is a 256 KB view of a file in kernel virtual address space. The
+lazy writer (`CcWorkerThread`) periodically scans `DirtyVacbListHead` and writes
+modified pages back to disk. The prefetcher traces access patterns and pre-loads
+pages before they are requested.
+
+**Database analogue: write-ahead log buffer and sequential prefetch.**
+
+Because storage is immutable here, the cache manager's dirty-write concern does
+not apply directly. New snapshot pages are always appended; they are never
+modifications of existing cached pages. However, the structural lessons hold:
+
+- **Append buffer.** New snapshot pages accumulate in an in-memory append buffer
+ before being flushed to the physical backend. The lazy writer pattern (flush
+ when the buffer exceeds a threshold, or periodically) applies directly.
+- **Read-ahead prefetch.** Sequential scans access pages in order. A prefetch
+ mechanism that issues backend reads ahead of the cursor's current position
+ reduces latency. NT's prefetcher learns per-scenario patterns; a database
+ prefetcher can use the query plan to predict access order exactly.
+- **VACBs as buffer frames.** Each VACB is a fixed-size view; the database
+ equivalent is a fixed-size buffer frame. Fixed-size frames simplify allocation
+ and eviction arithmetic.
+
+---
+
+## 6. PS → Sessions and Worker Threads
+
+NT process = database session. NT thread = query worker thread.
+
+```
+PEPROCESS Session
+ ├─ Handle table ├─ Handle table (open cursors, relations)
+ ├─ Token (identity) ├─ Connection context (AUTH_CLAIM set)
+ ├─ Working set ├─ Buffer pool pinned pages
+ ├─ Quota (memory, CPU, I/O) ├─ Resource quota (memory, CPU, I/O)
+ └─ ThreadListHead └─ Worker thread set
+
+PETHREAD Query worker
+ ├─ APC queue ├─ Deferred plan evaluation queue
+ ├─ IRP queue ├─ Pending cursor operations
+ └─ Impersonation context └─ SECURITY DEFINER / SET ROLE context
+```
+
+NT job objects group processes with shared limits: NT's `NtAssignProcessToJobObject`
+enforces memory, CPU time, and I/O quotas across a group. The DB equivalent is a
+*resource group* or *tenant*: a set of sessions sharing a memory limit and a CPU
+share. Multi-tenant databases need this to prevent one tenant's runaway query from
+starving others. Job objects are the right model: assign sessions to resource
+groups at connection time and enforce limits at the job-object level rather than
+per-session.
+
+**Impersonation.** NT allows a thread to temporarily assume a different security
+context (`SeImpersonateClient`). The DB equivalent is `SET ROLE` or `SECURITY
+DEFINER` in stored procedures: the query worker temporarily adopts a different
+access context without requiring the connection to re-authenticate.
+
+---
+
+## 7. SE → Authorization Beyond the Object Manager
+
+NT's security subsystem extends what the object manager does. Where OB attaches a
+security descriptor to an object and checks access at handle-open time, SE
+provides the richer infrastructure underneath:
+
+- **Token structure.** A token carries the caller's SID (identity), group
+ memberships, privilege set, and mandatory integrity level. The DB equivalent
+ is the connection context: user identity, granted AUTH_CLAIMs, session-level
+ role, and a trust level (e.g., a connection from localhost vs. from the public
+ internet might carry different trust).
+- **DACL / SACL split.** The DACL controls access; the SACL controls audit logging
+ (which accesses are recorded, on success or failure). For a database these are
+ different concerns: the permission check decides whether to allow the operation;
+ the audit policy decides whether to log it. Conflating them in a single
+ security descriptor is correct design; it keeps both concerns co-located with
+ the object rather than scattered.
+- **Privilege evaluation.** NT privileges (SE_BACKUP_PRIVILEGE bypasses ACLs;
+ SE_DEBUG_PRIVILEGE allows opening any process) have a DB analogue in superuser
+ privileges: a DBA role that bypasses row-level security, or a replication user
+ that bypasses normal write contention checks. These are orthogonal to the
+ capability tree and should be modelled as privileges on the connection token,
+ not as explicit capabilities on every object.
+- **Mandatory access control (integrity levels).** NT's integrity levels
+ (UNTRUSTED < LOW < MEDIUM < HIGH < SYSTEM) prevent low-integrity processes
+ from writing to high-integrity objects even if the DACL permits it. The DB
+ equivalent is a trust tier on connections: an externally-facing API connection
+ at LOW tier cannot write to audit or system catalog tables regardless of the
+ DACL, because those tables require HIGH tier.
+
+---
+
+## 8. LPC → Wire Protocol and Inter-Session Messaging
+
+NT's Local Procedure Call provides synchronous request-reply messaging between
+processes via named port objects. A server process creates a named connection port;
+clients connect and receive private communication ports. This is the mechanism
+through which Win32 applications communicate with CSRSS (console, shutdown) and
+through which subsystem DLLs synchronize session state with the subsystem server.
+
+**Database analogue: wire protocol connection.**
+
+Each client connection to the database is an LPC communication port. The session
+server daemon listens on a named port (the TCP socket); accepted connections
+become private communication ports (per-client sessions). The session daemon is
+CSRSS.
+
+LPC's fixed message size limit (`LPCP_MAX_MESSAGE_SIZE`) prevents a misbehaving
+client from forcing unbounded allocation. The DB wire protocol layer should apply
+the same discipline: a maximum query length, a maximum result frame size, and a
+maximum number of concurrent open cursors per session.
+
+LPC ports are objects in the NT namespace. Database connection endpoints (the
+listening socket, or a named pipe for local connections) should similarly be
+registered as objects in the RelationalNT namespace, making them subject to the
+same capability checks: a client must have explicit permission to connect to a
+particular endpoint.
+
+---
+
+## 9. FSRTL → Index Extent Management and Record Locks
+
+The File System Runtime Library is a set of utilities that filesystems use. The
+most relevant pieces for a database are:
+
+- **MCB (Map Control Block).** Tracks the logical-to-physical block mapping for a
+ file that may be stored in non-contiguous extents on disk. The DB equivalent is
+ an index page map: a B-tree or extent map translating a logical page number in
+ an index to a physical storage location. The MCB API (`FsRtlAddLargeMcbEntry`,
+ `FsRtlLookupLargeMcbEntry`) is a model for an extent manager that handles
+ fragmentation without surfacing it to callers.
+- **Fast I/O bypass.** `FAST_IO_DISPATCH` provides function pointers for
+ synchronous I/O that skip IRP allocation entirely when the requested data is
+ already in the cache. The DB equivalent is a buffer pool hot-path: if the
+ requested snapshot page is already pinned in the buffer pool, return it without
+ going through the full CursorManager dispatch chain.
+- **Byte-range file locking.** `FILE_LOCK` tracks exclusive/shared locks on
+ sub-file ranges. The DB equivalent is record-level locking within a relation.
+ Since storage here is immutable, this concern is narrowed to locking mutable
+ references (branch HEADs) rather than individual records — but the MCB-style
+ range-tracking structure is still the right data structure for knowing which
+ parts of a namespace are currently locked by which writers.
+
+---
+
+## 10. HAL → Storage Media Abstraction
+
+The Hardware Abstraction Layer isolates the kernel from CPU, interrupt controller,
+and timer differences between x86 and ARM machines. It provides a stable set of
+primitives (`HalAllocateAdapterChannel`, `HalReadDmaCounter`, `KeAcquireSpinLock`)
+that the rest of the kernel calls without knowing the underlying hardware.
+
+**Database analogue: storage media and topology abstraction.**
+
+The equivalent boundary sits between `CursorManager` (the "kernel") and the
+physical storage backend (the "hardware"). The HAL lessons:
+
+- **Platform-neutral primitives.** `CursorManager::Next()` and `Close()` should
+ never contain NVMe-specific or LMDB-specific code. Those specifics live in the
+ backend module, exactly as NVMe DMA specifics live in the miniport, not the HAL.
+- **CPU/NUMA topology.** HAL exposes processor and memory node topology so the
+ scheduler can make NUMA-aware placement decisions. A database on a NUMA machine
+ benefits from binding buffer pool regions and worker threads to the same NUMA
+ node. This is a future concern but the right interface is at the HAL boundary:
+ a `platform_topology` structure that the scheduler queries without knowing
+ whether it is running on x86, ARM, or a cloud VM.
+- **Timer abstraction.** HAL provides a system tick and a high-resolution
+ performance counter. The DB needs both: a coarse timer for transaction
+ timestamps and TTL-based expiry, and a high-resolution counter for query
+ latency measurement.
+
+---
+
+## 11. KD → Diagnostic Interface
+
+NT's kernel debugger (`kd/`) provides a serial transport over which WinDBG can
+inspect kernel state, set breakpoints, and walk data structures live. The
+`!object`, `!handle`, and `!poolused` debugger extensions expose the object
+manager, handle tables, and pool allocator without stopping the machine.
+
+**Database analogue: EXPLAIN and live diagnostic.**
+
+The equivalent is a diagnostic interface that can walk live runtime state: the
+object registry, open handle table, active cursors, and lifecycle counters.
+Implemented as a reserved namespace path (`/system/diagnostics/`), it exposes
+read-only virtual objects whose `Next()` returns runtime state rather than stored
+data. Queries against `/system/diagnostics/cursors` return a row per open cursor;
+queries against `/system/diagnostics/objects` return a row per registry entry with
+its handle and reference counts. This is `EXPLAIN ANALYZE` and
+`pg_stat_activity` unified under the same object model, for free.
+
+---
+
+## 12. Config Manager → System Catalog
+
+NT's configuration manager (`config/`) manages registry hives — persistent
+key-value stores loaded at boot and kept consistent via transaction logs. The
+registry is itself stored as a structured binary file that the kernel reads before
+any filesystem driver is loaded.
+
+**Database analogue: system catalog.**
+
+The system catalog is a set of relations stored in a reserved multigroup
+(`/system/catalog/`) that describes all other objects: their types, paths,
+security descriptors, and capabilities. It is the registry of the database. It
+must be readable before any user-defined relations are accessible, exactly as the
+registry is readable before any device driver is loaded.
+
+Because storage is immutable here, the catalog is itself versioned: each schema
+change produces a new catalog snapshot, and the kernel always reads the latest
+committed catalog version on startup. Old catalog versions remain accessible for
+historical schema reconstruction (e.g., reading data written under a previous
+schema). This is more powerful than the NT registry, which has no built-in
+history.
+
+---
+
+## Architectural Summary
+
+The full NT kernel, mapped to a database, produces this layer diagram:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Wire Protocol Subsystems (LPC / Env. Subsystems) │
+│ PostgreSQL wire MySQL wire HTTP/JSON native client lib │
+├─────────────────────────────────────────────────────────────────┤
+│ Session Manager (PS) │
+│ Sessions · Worker threads · Resource groups · Impersonation │
+├─────────────────────────────────────────────────────────────────┤
+│ Authorization Engine (SE + OB) │
+│ Tokens · Capabilities · DACL/SACL · Privilege evaluation │
+├─────────────────────────────────────────────────────────────────┤
+│ Query Operator Pipeline (IO / VM) │
+│ SCAN · FILTER · PROJECT · JOIN · SORT · AGGREGATE │
+│ Async execution · Filter-driver middleware · Plan nodes │
+├─────────────────────────────────────────────────────────────────┤
+│ Object Manager (OB) Namespace (NamespaceRefMgr) │
+│ Registry · Handles · Types Branch refs · View reparse │
+├─────────────────────────────────────────────────────────────────┤
+│ Buffer Pool (MM + CC) │
+│ Page frames · Working set · Prefetch · Append flusher │
+├─────────────────────────────────────────────────────────────────┤
+│ Storage Backend Interface (IO driver model) │
+│ CursorManager · Backend dispatch table · Filter backends │
+├─────────────────────────────────────────────────────────────────┤
+│ Physical Backends (HAL / miniport drivers) │
+│ LMDB · RocksDB · etcd · in-memory · S3 │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+The current RelationalNT codebase covers the Object Manager, parts of the
+Operator Pipeline (SCAN only), and the authorization skeleton. The layers above
+(session manager, wire protocols) and below (real buffer pool, real storage
+backends) are the next frontier. The NT architecture shows that each layer has a
+well-understood responsibility and a narrow interface to its neighbours — that
+discipline is the reason the NT kernel scales from embedded devices to 1024-core
+servers without architectural changes.
diff --git a/docs/reactos-ob-comparison.md b/docs/reactos-ob-comparison.md
new file mode 100644
index 0000000..94d3081
--- /dev/null
+++ b/docs/reactos-ob-comparison.md
@@ -0,0 +1,281 @@
+# ReactOS Object Manager — Insights for RelationalNT
+
+A comparative study of the ReactOS kernel object manager (`ntoskrnl/ob`) against
+the RelationalNT runtime, identifying structural patterns from the OS kernel that
+translate directly into improvements for a database kernel.
+
+The ReactOS OB is roughly 8,000 lines across `oblife.c`, `obhandle.c`, `obref.c`,
+`obdir.c`, `obname.c`, and `obsecure.c`. The notes below focus on the subset of
+patterns that have a clear database analogue, with adjustments for the
+immutable-storage model this project uses.
+
+---
+
+## 1. Two-Level Reference Counting
+
+**ReactOS pattern.** Every object header carries two independent counters:
+`PointerCount` (kernel-internal references, e.g. a driver holding a pointer) and
+`HandleCount` (external user-visible handles). Object deletion only proceeds when
+*both* reach zero.
+
+**Database analogue.** RelationalNT already has `reference_count` and
+`handle_count` in `registry_head`, but `reference_count` is unimplemented (see
+`ObjectManager.h` TODO). The two counters serve different purposes and must be
+decremented by different paths:
+
+- `handle_count` — open sessions holding a cursor or handle on this object.
+ Managed by `LifecycleManager::Monitor` / `Unmonitor`.
+- `reference_count` — structural dependencies: a view referencing its base
+ relations, an index depending on a relation's schema. Managed by a separate
+ `Pin` / `Unpin` pair.
+
+An object may only be garbage-collected when both counters reach zero.
+`Unmonitor` should trigger GC only after checking `reference_count == 0`.
+Without this, there is no safe way to remove a RELATION that still backs a view.
+
+**Important note on snapshots.** Snapshots in this system are *always immutable*.
+Every insertion or deletion produces a new snapshot version of the multigroup
+rather than mutating the existing one. A snapshot is therefore not a special
+lifecycle category — it is just another registered object. Its `reference_count`
+is non-zero while any cursor is pinned to it, and the snapshot is eligible for
+removal only when that count drops to zero. The object flag classification
+(permanent vs. non-permanent) applies to *schema objects*, not to snapshot
+versions.
+
+**TODOs:** `LifecycleManager` — add `Pin(registry*)` / `Unpin(registry*)`.
+`LifecycleManager::Unmonitor` — check both counters before triggering GC.
+
+---
+
+## 2. Type Callbacks Instead of a Method Set
+
+**ReactOS pattern.** Object types register actual function pointers:
+`OpenProcedure`, `CloseProcedure`, `DeleteProcedure`, `ParseProcedure`,
+`SecurityProcedure`. The kernel calls into the type-specific callback rather than
+branching on a type enum.
+
+**Database analogue.** `object_type::methods` is currently a `std::set<METHOD>`
+that `IdentityManager::CanOpen` checks against but never acts on. Replacing the
+set with typed callbacks eliminates the identity manager as a separate component
+and makes object behaviour self-contained:
+
+| Callback | Database use |
+|---|---|
+| `OpenProcedure` | RELATION — acquire snapshot pin, register with LifecycleManager |
+| `CloseProcedure` | TRANSACTION — auto-rollback if no explicit commit was issued |
+| `DeleteProcedure` | MULTIGROUP — cascade to relations |
+| `ParseProcedure` | View/alias entry — reparse to the physical relation's path (see §8) |
+
+**Debuggability concern.** Function pointers are harder to trace than a named
+path through the identity manager. Mitigation: store the callback alongside a
+`const char* name` label in `object_type` so that logs and assertions can
+identify which callback fired. A `std::function` with a wrapper that records the
+type label before dispatching also works.
+
+**Security constraint on ParseProcedure.** When a view (an ephemeral RELATION
+whose `ParseProcedure` reparses to base relations) is resolved, the capability
+check must be evaluated against the *view's* security descriptor, not the base
+relations'. A caller who holds READ on the view must *not* gain the ability to
+open handles directly on the underlying relations — those require explicit
+permissions of their own. The ParseProcedure reparse mechanism navigates
+internally through the namespace on behalf of the runtime; it does not grant the
+caller a transitive handle on the base object. The resolved handle is bound to the
+view, and the base relations are accessed only via the view's own type callbacks.
+
+**TODOs:** `ObjectManager::object_type` — replace `std::set<METHOD> methods` with
+typed callback fields. `IdentityManager` — migrate to invoke `OpenProcedure` /
+`CloseProcedure`; the manager becomes a thin dispatcher or is absorbed into
+`HandlerManager`.
+
+---
+
+## 3. Hash Directory / Trie for Object Lookup
+
+**ReactOS pattern.** Each `OBJECT_DIRECTORY` holds a 37-bucket hash table (37 is
+prime, chosen to reduce clustering). Name resolution walks path components
+one-by-one, locking each directory, hashing the component, and following
+collision chains. A found entry is moved to the head of its bucket (MRU
+optimisation).
+
+**Current state.** `ObjectManager::Find` is an O(n) linear scan of a linked list.
+The code comment already says "treat as a list for now, but ideally it is a tree."
+
+**Recommended approach.** Replace the flat list with a trie whose nodes are
+directories keyed by path segment. Each directory node holds a hash map from
+segment string to child `registry*`. This matches the ReactOS structure naturally
+because `object_path` is already a `std::vector<std::string>`.
+
+Rather than implementing the trie from scratch, consider pulling a suitable
+container via vcpkg (e.g. a radix tree or a well-tested associative structure).
+The only hard requirement is that each directory level supports O(1) average
+lookup by segment name and ordered iteration for namespace enumeration.
+
+**TODO:** `ObjectManager` — replace `std::unique_ptr<registry> entries` (linked
+list) with a trie or per-level hash map. `ObjectManager::Find` and `Register`
+both need to be updated.
+
+---
+
+## 4. Per-Handle Access Mask
+
+**ReactOS pattern.** The handle table entry stores `GrantedAccess` per handle, not
+per object. A single object can be opened read-only by one session and read-write
+by another simultaneously; each handle's access mask is checked independently.
+
+**Database analogue.** `HandlerManager::handle` currently holds only `object*`
+and `void* connection_context`. Adding `std::set<AUTH_CLAIM> granted_access`
+enables the access mask to be evaluated once at `Open` time and cached in the
+handle for the duration of the session. This resolves the open performance
+question in `PermissionsManager` ("undecided whether to check on every access or
+cache").
+
+**Immutable storage note.** In this system, storage is append-only. A WRITE claim
+does not mean mutating an existing tuple in-place; it means the session is
+authorised to produce a new snapshot version of the relation. The access mask
+semantics should be read accordingly: READ grants access to query an existing
+snapshot; WRITE grants access to commit a new one. Two sessions holding WRITE
+handles on the same relation produce independent snapshots — there is no conflict
+at the storage level.
+
+**TODO:** `HandlerManager::handle` — add `std::set<AUTH_CLAIM> granted_access`.
+`HandlerManager::Open` — populate it from `PermissionsManager::Firewall` /
+`Access` and store in the handle. Subsequent checks read from the handle rather
+than re-evaluating the security descriptor.
+
+---
+
+## 5. Capability-Based Security, No Inheritance
+
+**ReactOS pattern.** ReactOS supports SD inheritance through directory traversal:
+an object can inherit its parent directory's security descriptor if none is
+explicitly set.
+
+**This project does not use SD inheritance.** Permissions here are strictly
+capability-based and must be explicitly granted. There is no implicit flow of
+access rights from a multigroup down to its constituent relations. A caller who
+holds READ on `/multigroups/coffee_shop` does not thereby hold READ on
+`/multigroups/coffee_shop/relations/user` — that is a separate, explicit
+capability.
+
+This is a deliberate departure from the ReactOS model, and it is the correct
+design for a database where row-level and column-level access policies are
+common. The capability tree in `PermissionsManager` is the authoritative
+source of truth; the namespace hierarchy is purely organisational.
+
+**Implication for the SD cache.** When a shared security descriptor cache is
+added (see §7 of the original notes), descriptors must be deduplicated by
+content, not by ancestry. Two unrelated relations that happen to have identical
+ACLs may share a cached SD; that sharing is a performance optimisation, not a
+statement about inheritance.
+
+---
+
+## 6. Mutable Reference Contention (Branch HEAD)
+
+**ReactOS pattern.** `OB_FLAG_EXCLUSIVE` on an object header prevents more than
+one handle from being open simultaneously. The flag is checked during
+`ObpCreateHandle`; a second opener fails immediately.
+
+**Database analogue.** `LifecycleManager::Contention` is a stub that always
+returns `true`. It needs to distinguish:
+
+- **Immutable objects (RELATION snapshots, MULTIGROUP snapshots, TRANSACTION)** —
+ contention is never raised. These are content-addressed and append-only; two
+ sessions opening the same snapshot independently is fine.
+- **Mutable reference states (branch HEAD, namespace entries)** — contention must
+ be enforced. A branch HEAD is the only mutable pointer in the system; it needs
+ single-writer semantics. When a session holds a write handle on a HEAD, a second
+ writer must block or fail.
+
+The contention check belongs on the *reference object* (the HEAD pointer), not on
+the snapshot object it points to. This maps cleanly to ReactOS's exclusive flag:
+add an `exclusive` flag to `object_type` (or a dedicated flag on `registry_head`)
+that Contention() checks.
+
+**Note on session parallelism.** Because storage is immutable, two sessions
+writing to the same relation in parallel always produce separate, independent
+snapshots. There is no write-write conflict at the data level. The only genuine
+contention point is advancing a branch HEAD — the moment where one of the
+competing snapshots must be chosen as the new tip. This is the correct and narrow
+scope for Contention().
+
+**TODOs:** `ObjectManager::registry_head` or `object_type` — add `bool exclusive`
+flag. `LifecycleManager::Contention` — implement: return false immediately for
+non-exclusive objects; for exclusive objects, return false if `handle_count > 0`
+for a write-mode opener.
+
+---
+
+## 7. Deferred Deletion / Asynchronous GC
+
+**ReactOS pattern.** When an object's pointer count reaches zero, ReactOS does not
+delete it inline. It appends to a lock-free `NextToFree` linked list and posts a
+work item to a reaper thread (`ObpReapObject`). This keeps deletion latency off
+the hot path and prevents stack overflow in nested deletes.
+
+**Database relevance.** Snapshot compaction and history pruning are expensive
+operations that should not run inline inside a client-facing `Close()`. The reaper
+pattern maps to a background GC worker: `Unmonitor` enqueues the object when both
+counters reach zero; the GC thread drains the queue and calls the type's
+`DeleteProcedure`.
+
+This is noted for completeness. **Compaction is out of scope for the current
+implementation phase.** Record it here so the architecture accommodates it:
+`LifecycleManager::Unmonitor` should eventually enqueue rather than immediately
+free.
+
+---
+
+## 8. NamespaceReferenceManager as the Reparse Layer
+
+**ReactOS pattern.** `ParseProcedure` on a type can return `STATUS_REPARSE` with
+a new path, causing the name resolver to restart from the new path. Symbolic links
+use this. The resolver caps reparse at 30 iterations to detect cycles.
+
+**This is the right architecture for `NamespaceReferenceManager`.** The manager
+currently has no implementation. It should be built around the same concept:
+
+- **Branch references** — a path like `/refs/branch/main` is a namespace entry
+ whose `ParseProcedure` returns a reparse pointing to the actual snapshot path
+ (e.g. `/snapshots/abc123`). The caller's handle lands on the snapshot, not on
+ the reference object itself, unless the caller explicitly opens the reference
+ object for writing (to advance the HEAD).
+
+- **View resolution** — a view's `ParseProcedure` reparses to its base relations
+ internally, subject to the capability security constraint in §2: the reparse
+ happens inside the runtime; the caller's handle and access mask stay bound to
+ the view.
+
+- **Atomic multi-reference updates** — pushing to both `main` and `audit_log`
+ must be all-or-nothing. The implementation should acquire exclusive locks on all
+ involved namespace entries before updating any of them. If a Contention() check
+ fails on any entry, release all locks and abort the batch. This preserves audit
+ consistency.
+
+- **Cycle guard** — view definitions may reference other views. Name resolution
+ must track reparse depth and fail with an error after a bounded number of
+ iterations (30 is the ReactOS figure; a similar constant is appropriate here).
+
+- **Audit branch** — audit is a write concern that spans all branches. It should
+ be modelled as an independent branch whose HEAD is the only contention point for
+ audit-affecting operations, dissociated from the data branch scope.
+
+**TODOs:** `NamespaceReferenceManager` — implement `Resolve(path)` with reparse
+loop and cycle guard. Define the reference object type with an `exclusive` flag
+and a `ParseProcedure` that rewrites the path. Define the batch-update API with
+all-or-nothing semantics.
+
+---
+
+## Priority Order
+
+| Priority | Item | Header(s) affected |
+|---|---|---|
+| 1 | Dual ref counting — implement `Pin` / `Unpin` | `LifecycleManager.h` |
+| 2 | Type callbacks — replace `methods` set with function pointers | `ObjectManager.h`, `IdentityManager.h` |
+| 3 | Per-handle access mask | `HandlerManager.h` |
+| 4 | Exclusive flag → real `Contention()` for branch HEADs | `ObjectManager.h`, `LifecycleManager.h` |
+| 5 | Trie / hash directory for `ObjectManager::Find` | `ObjectManager.h` |
+| 6 | `NamespaceReferenceManager` reparse layer | `NamespaceReferenceManager.h` |
+| 7 | Shared SD cache (no inheritance, content-addressed) | `PermissionsManager.h` |
+| 8 | Deferred GC queue in `Unmonitor` | `LifecycleManager.h` |
diff --git a/include/HandlerManager.h b/include/HandlerManager.h
index 4f63f17..f3d504e 100644
--- a/include/HandlerManager.h
+++ b/include/HandlerManager.h
@@ -2,7 +2,9 @@
#include "Api.h"
#include "ObjectManager.h"
+#include "Types.h"
+#include <set>
#include <string>
#include <vector>
@@ -36,15 +38,33 @@ namespace nt
/**
* @brief Represents authorized access to an object in the registry.
*
- * This increases the handle count in the object manager. We should
- * consider adding a vector to the object manager with all handles that
- * refer to an object.
+ * The access mask is evaluated once during Open and cached here for the
+ * lifetime of the handle. Subsequent operations check `granted_access`
+ * rather than re-evaluating the security descriptor on every call. This
+ * resolves the performance question in PermissionsManager about when to
+ * check permissions.
+ *
+ * Because storage is append-only, a WRITE claim does not mean mutating an
+ * existing tuple in-place. It means the session is authorized to commit a
+ * new snapshot version of the relation. Two sessions holding WRITE handles
+ * on the same relation always produce independent snapshots — there is no
+ * data-level conflict.
+ *
+ * @todo Populate `granted_access` inside HandlerManager::Open from
+ * PermissionsManager::Firewall / Access. See docs/reactos-ob-comparison.md §4.
*/
struct handle {
/** Object referenced by this handle. */
ObjectManager::registry* object = nullptr;
/** Connection metadata associated with the authorized access. */
void* connection_context = nullptr;
+ /**
+ * Access claims granted to this handle at open time.
+ * Cached here so that per-operation checks read from the handle
+ * rather than re-evaluating the security descriptor.
+ * @todo Populate in HandlerManager::Open.
+ */
+ std::set<AUTH_CLAIM> granted_access;
};
/**
diff --git a/include/IdentityManager.h b/include/IdentityManager.h
index 0e2d08f..5c8dd14 100644
--- a/include/IdentityManager.h
+++ b/include/IdentityManager.h
@@ -10,19 +10,37 @@
namespace nt
{
- /** @brief Checks whether an object supports identity-level operations. */
+ /**
+ * @brief Checks whether an object supports identity-level operations.
+ *
+ * @todo Migrate to type callbacks. Once `object_type` holds `OpenProcedure`
+ * and `CloseProcedure` function pointers, CanOpen / CanClose become
+ * thin dispatchers that invoke the corresponding procedure — or this
+ * manager is absorbed into HandlerManager directly. The function pointer
+ * should be accompanied by a `const char* name` label on `object_type`
+ * so that error logs identify the type without dereferencing the pointer.
+ * See docs/reactos-ob-comparison.md §2.
+ */
class NT_API IdentityManager
{
public:
/**
- * @brief Looks into the methods of the object type and asserts if it can be opened.
+ * @brief Returns true when the object's type supports being opened.
+ *
+ * Currently checks that the object is non-null (stub). Should invoke
+ * `object_type::OpenProcedure` once callbacks are defined.
+ *
* @param object Object to inspect.
* @return True when the object can be opened.
*/
const bool CanOpen(ObjectManager::registry* object);
/**
- * @brief Checks whether an object can be closed.
+ * @brief Returns true when the object's type supports being closed.
+ *
+ * Currently checks that the object is non-null (stub). Should invoke
+ * `object_type::CloseProcedure` once callbacks are defined.
+ *
* @param object Object to inspect.
* @return True when the object can be closed.
*/
diff --git a/include/LifecycleManager.h b/include/LifecycleManager.h
index 914ce0a..8126820 100644
--- a/include/LifecycleManager.h
+++ b/include/LifecycleManager.h
@@ -10,53 +10,92 @@
namespace nt
{
- /** @brief Manages runtime lifecycle concerns for registry objects. */
+ /**
+ * @brief Manages runtime lifecycle concerns for registry objects.
+ *
+ * Object lifetime is governed by two independent counters in registry_head:
+ * `handle_count` (sessions holding an open handle) and `reference_count`
+ * (structural dependencies such as cursors pinned to a snapshot or views
+ * referencing base relations). An object is eligible for GC only when both
+ * reach zero.
+ *
+ * Monitor / Unmonitor manage `handle_count`. Pin / Unpin manage
+ * `reference_count`. See docs/reactos-ob-comparison.md §1.
+ */
class NT_API LifecycleManager
{
public:
/**
* @brief Starts monitoring an object's lifecycle.
*
- * If the object is not being monitored already, monitor the lifecycle
- * by updating the handle count. Maybe also look at the reference count
- * and kill some categories of disposable objects no longer referenced
- * by other objects, like transactions.
- *
- * One use case is preventing compaction/pruning of history while
- * cursors depend on old database snapshots, for example. Think of
- * other use cases later.
+ * Increments `handle_count` on the object's registry_head. Should be
+ * called after all authorization checks pass inside HandlerManager::Open.
*
* @param object Object to monitor.
*/
void Monitor(ObjectManager::registry* object);
/**
- * @brief Serializes contention for changes to mutable references.
+ * @brief Stops monitoring an object.
*
- * This can be used for the HEAD of a branch to serialize access. If the
- * object does not cause contention, such as a relation, then this can
- * approve the operation because relations and multigroups are immutable
- * snapshots. Transactions also do not cause contention.
+ * Decrements `handle_count`. When both `handle_count` and
+ * `reference_count` reach zero, the object becomes eligible for GC.
*
- * The objects that do cause contention are mutable reference states in
- * shared sessions. Examples include the HEAD of a branch, namespace
- * collisions, multi-branch atomic commits (which may damage isolation),
- * and garbage collection of old state when old-enough history is removed
- * to save space.
+ * @todo Gate GC on both counters reaching zero, not just handle_count.
+ * For deferred compaction, enqueue onto a background GC list rather
+ * than freeing inline. See docs/reactos-ob-comparison.md §1 and §7.
*
- * @param object Object to check for contention.
- * @return True when the operation may proceed.
+ * @param object Object to stop monitoring.
*/
- const bool Contention(ObjectManager::registry* object);
+ void Unmonitor(ObjectManager::registry* object);
/**
- * @brief Stops monitoring an object.
+ * @brief Increments the structural dependency count on an object.
*
- * The inverse of Monitor. Maybe garbage collect here also if the
- * counters go to zero.
+ * Call when one object takes a dependency on another — for example when
+ * a cursor pins an immutable snapshot version, or when a view is
+ * registered against its base relations. Prevents GC of the target object
+ * while the dependency is live.
*
- * @param object Object to stop monitoring.
+ * @todo Implement. See docs/reactos-ob-comparison.md §1.
+ *
+ * @param object Object being depended upon.
*/
- void Unmonitor(ObjectManager::registry* object);
+ void Pin(ObjectManager::registry* object);
+
+ /**
+ * @brief Decrements the structural dependency count on an object.
+ *
+ * The inverse of Pin. When both `reference_count` and `handle_count`
+ * reach zero the object becomes eligible for GC.
+ *
+ * @todo Implement. See docs/reactos-ob-comparison.md §1.
+ *
+ * @param object Object whose dependency is being released.
+ */
+ void Unpin(ObjectManager::registry* object);
+
+ /**
+ * @brief Serializes contention for changes to mutable reference states.
+ *
+ * Immutable objects — relation snapshots, multigroup snapshots,
+ * transactions — never raise contention. Two sessions opening the same
+ * immutable snapshot simultaneously is always safe; they will produce
+ * independent results without conflict.
+ *
+ * Contention applies only to mutable reference states: the HEAD of a
+ * branch and namespace entries targeted by atomic multi-reference updates.
+ * These are the sole mutable pointers in the system. When a session holds
+ * a write handle on a HEAD, a second write opener must block or fail.
+ *
+ * @todo Implement: read `object_type::exclusive`. Return false immediately
+ * for non-exclusive objects. For exclusive objects, return false
+ * (contention detected) when `handle_count > 0` for a write-mode
+ * opener. See docs/reactos-ob-comparison.md §6.
+ *
+ * @param object Object to check for contention.
+ * @return True when the operation may proceed.
+ */
+ const bool Contention(ObjectManager::registry* object);
};
}
diff --git a/include/NamespaceReferenceManager.h b/include/NamespaceReferenceManager.h
index 59068f7..0bf00bc 100644
--- a/include/NamespaceReferenceManager.h
+++ b/include/NamespaceReferenceManager.h
@@ -13,20 +13,50 @@ namespace nt
* @class NamespaceReferenceManager
* @brief Handles the logical mapping and isolation of human-readable references.
*
- * Design notes:
- * - Namespace isolation: allow private branch shadowing, such as
- * /user_a/refs/branch/abc. To preserve lookup clarity, protocol-level
- * syntax must explicitly define scope and prevent non-deterministic
- * shadowing during resolution.
+ * This manager is the reparse layer of the namespace. Its design mirrors the
+ * ReactOS ParseProcedure / STATUS_REPARSE mechanism: a namespace entry whose
+ * type carries a ParseProcedure can redirect name resolution to a different
+ * path, allowing branch references and views to appear as first-class objects
+ * in the namespace without exposing their physical storage paths directly.
*
- * - Multi-reference atomic updates: ensure updates to multiple references,
- * such as pushing to master and audit_log simultaneously, are
- * all-or-nothing. If any contention check fails, the whole batch is
- * aborted to prevent partial commits and preserve audit consistency.
+ * **Branch references.**
+ * A path such as `/refs/branch/main` is a namespace entry backed by a
+ * reference object. Its ParseProcedure rewrites the path to the physical
+ * snapshot path (e.g. `/snapshots/abc123`). The caller's handle lands on the
+ * snapshot. To advance the HEAD, the caller must explicitly open the reference
+ * object itself with WRITE access, which is the sole contention point for that
+ * branch.
*
- * Audit needs global access, which means it should be dissociated from a
- * branch scope. Since branches are the only contention points for
- * references, it makes sense to model audit as an independent branch too.
+ * **View resolution.**
+ * A view is an ephemeral RELATION whose ParseProcedure reparses to its base
+ * relations internally on behalf of the runtime. The caller's handle and access
+ * mask remain bound to the view. The base relations are accessed only via the
+ * view's own type callbacks; the caller never receives a handle on them and
+ * does not need explicit permission on them. Capability security is enforced at
+ * the view boundary, not the base relation boundary.
+ *
+ * **Atomic multi-reference updates.**
+ * Updating multiple references simultaneously (e.g. advancing both `main` and
+ * `audit_log`) must be all-or-nothing. The implementation should acquire
+ * exclusive locks on all target namespace entries before modifying any. If
+ * LifecycleManager::Contention fails on any entry, all locks are released and
+ * the batch is aborted. This preserves audit consistency.
+ *
+ * **Cycle guard.**
+ * View definitions may reference other views, and branch aliases may chain.
+ * Name resolution must track reparse depth and return an error after a bounded
+ * number of iterations to prevent infinite cycles.
+ *
+ * **Audit branch.**
+ * Audit requires global access dissociated from any data branch. It should be
+ * modelled as an independent branch; its HEAD is the only contention point for
+ * audit-affecting operations.
+ *
+ * @todo Implement `Resolve(path)` with a reparse loop and cycle depth guard.
+ * @todo Define the reference object type with `object_type::exclusive = true`
+ * and a ParseProcedure that rewrites the resolution path.
+ * @todo Define the batch-update API with all-or-nothing contention semantics.
+ * See docs/reactos-ob-comparison.md §8.
*/
class NT_API NamespaceReferenceManager
{
diff --git a/include/ObjectManager.h b/include/ObjectManager.h
index aab3a6f..4b537f7 100644
--- a/include/ObjectManager.h
+++ b/include/ObjectManager.h
@@ -39,21 +39,75 @@ namespace nt
/** @brief Abstract registry object representing a transaction. */
class Transaction : public IObject {};
- /** @brief Describes the behavior and capabilities of an object category. */
+ /**
+ * @brief Describes the behavior and capabilities of an object category.
+ *
+ * @todo Replace `methods` with typed callback fields: `OpenProcedure`,
+ * `CloseProcedure`, `DeleteProcedure`, and `ParseProcedure`.
+ * Store a `const char* name` label alongside each callback so that
+ * logs and assertions can identify which type fired without having
+ * to dereference a pointer. `IdentityManager::CanOpen` / `CanClose`
+ * then become thin dispatchers that invoke the corresponding
+ * procedure, or are absorbed into `HandlerManager` directly.
+ * See docs/reactos-ob-comparison.md §2.
+ *
+ * @todo Add an `exclusive` flag for mutable reference objects (branch
+ * HEADs, namespace entries). Non-exclusive objects — immutable
+ * snapshots, transactions — must skip contention checks entirely.
+ * See docs/reactos-ob-comparison.md §6.
+ */
struct object_type {
/** Object category label. */
OBJECT_TYPE label;
/** True when the object is not durable. For example: TRANSACTION. */
bool disposable;
- /** Methods supported by this object type. */
+ /**
+ * Methods supported by this object type.
+ * @todo Replace with typed callback function pointers. See class-level todo.
+ */
std::set<METHOD> methods;
+ /**
+ * When true, only one write-mode handle may be open at a time.
+ * Applies to mutable reference states such as branch HEADs.
+ * Immutable snapshot objects must leave this false.
+ * @todo Wire into LifecycleManager::Contention.
+ */
+ bool exclusive = false;
};
- /** @brief Shared metadata stored at the head of a registry entry. */
+ /**
+ * @brief Shared metadata stored at the head of a registry entry.
+ *
+ * Two independent reference counters govern object lifetime.
+ * An object is eligible for garbage collection only when both reach zero:
+ *
+ * - `handle_count` — open sessions holding a cursor or handle on this object.
+ * Managed exclusively by LifecycleManager::Monitor / Unmonitor.
+ * - `reference_count` — structural dependencies such as a view referencing its
+ * base relations, or a cursor pinned to an immutable snapshot version.
+ * Managed by LifecycleManager::Pin / Unpin (not yet implemented).
+ *
+ * All snapshot versions are immutable. An insertion or deletion produces a new
+ * snapshot rather than mutating an existing one. A snapshot's `reference_count`
+ * is non-zero while any cursor is pinned to it and drops to zero only when all
+ * such cursors are released.
+ *
+ * @todo Implement `reference_count` tracking. Add LifecycleManager::Pin /
+ * Unpin. Update LifecycleManager::Unmonitor to gate GC on both counters
+ * reaching zero. See docs/reactos-ob-comparison.md §1.
+ *
+ * @todo Define and attach a security_descriptor. When added, store it as a
+ * pointer into a shared, content-addressed SD cache rather than inline.
+ * Permissions are strictly capability-based and must never be inherited
+ * from a parent namespace entry. See docs/reactos-ob-comparison.md §5.
+ */
struct registry_head {
- // TODO: Increment reference_count when one registry object depends on another
- // (e.g. an ephemeral view that references base relations). Decrement when the
- // dependency is removed. Used to block cleanup of objects still referenced by others.
+ /**
+ * Structural dependency count. Non-zero while another registry object
+ * references this one (e.g. a view depending on a base relation, or a
+ * cursor pinned to a snapshot version).
+ * @todo Implement via LifecycleManager::Pin / Unpin.
+ */
uint32_t reference_count = 0;
/** Number of open handles. Maintained by LifecycleManager::Monitor/Unmonitor. */
uint32_t handle_count = 0;
@@ -61,7 +115,6 @@ namespace nt
std::unique_ptr<object_type> type;
/** Logical object path inside the namespace. */
std::vector<std::string> path;
- /** security_descriptor to be added later, maybe also a set of labels */
};
/** @brief Node in the object registry. */
@@ -70,7 +123,15 @@ namespace nt
std::unique_ptr<registry_head> head;
/** Abstract object payload. Owned by this entry. */
std::unique_ptr<IObject> object;
- /** TODO: Treat as a list for now, but ideally it is a tree. */
+ /**
+ * Next entry in the registry. Currently a flat linked list.
+ * @todo Replace with a trie whose nodes are per-path-segment hash maps.
+ * Each directory level holds a hash map from segment string to
+ * child registry*. Find() and Register() walk the trie
+ * component-by-component instead of scanning a flat list.
+ * Consider pulling a well-tested radix-tree via vcpkg rather than
+ * implementing from scratch. See docs/reactos-ob-comparison.md §3.
+ */
std::unique_ptr<registry> next;
};
diff --git a/include/PermissionsManager.h b/include/PermissionsManager.h
index 65c00e5..2db1784 100644
--- a/include/PermissionsManager.h
+++ b/include/PermissionsManager.h
@@ -20,6 +20,22 @@ namespace nt
* Permission checks are intentionally separated from lookup. The object
* manager can retrieve a candidate object, while this manager decides
* whether the caller is allowed to use it.
+ *
+ * Permissions are strictly capability-based and must be explicitly granted.
+ * There is no implicit flow of access rights from a parent namespace entry to
+ * its children (no SD inheritance). A caller who holds READ on a multigroup
+ * does not thereby hold READ on its constituent relations — that is a separate,
+ * explicit capability.
+ *
+ * @todo Define a security_descriptor structure. Store instances in a shared,
+ * content-addressed cache (keyed by descriptor content, not by namespace
+ * ancestry) so that identical ACLs are not duplicated across relations.
+ * See docs/reactos-ob-comparison.md §5 and §7.
+ *
+ * @todo Cache the result of Access() in HandlerManager::handle::granted_access
+ * at open time. Per-operation checks should then read from the handle
+ * rather than re-evaluating the descriptor on every call.
+ * See docs/reactos-ob-comparison.md §4.
*/
class NT_API PermissionsManager
{