diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2026-05-10 02:27:08 -0300 |
|---|---|---|
| committer | Marcos Magueta <maguetamarcos@gmail.com> | 2026-05-10 02:27:08 -0300 |
| commit | 7ffd4d99bbb8e358c6694c27c7ff859967872ece (patch) | |
| tree | 8af04f4032651de7e49d1cac01e6f20b2315b6cb | |
| parent | 5ee324f03f8a7e6f09dad7b8580a2d32172694f8 (diff) | |
| download | RNT-7ffd4d99bbb8e358c6694c27c7ff859967872ece.tar.gz RNT-7ffd4d99bbb8e358c6694c27c7ff859967872ece.zip | |
comparison to nt ob
Diffstat (limited to '')
| -rw-r--r-- | docs/mainpage.dox | 17 | ||||
| -rw-r--r-- | docs/nt-architecture-mapping.md | 489 | ||||
| -rw-r--r-- | docs/reactos-ob-comparison.md | 281 | ||||
| -rw-r--r-- | include/HandlerManager.h | 26 | ||||
| -rw-r--r-- | include/IdentityManager.h | 24 | ||||
| -rw-r--r-- | include/LifecycleManager.h | 93 | ||||
| -rw-r--r-- | include/NamespaceReferenceManager.h | 54 | ||||
| -rw-r--r-- | include/ObjectManager.h | 77 | ||||
| -rw-r--r-- | include/PermissionsManager.h | 16 |
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 { |
