# The registry

---

# Registry overview

_Peios / Developing for Peios / The registry_

> How LCS — the layered registry — works from a developer's seat: keys, values, layers and precedence, transactions, and watches.

LCS — the Layered Configuration Subsystem — is Peios's registry: a kernel-mediated, hierarchical, secured configuration store. If you have used the Windows registry it will feel familiar — a tree of **keys** holding typed **values** — but LCS adds one defining idea: **layers**. This section teaches the client API; [`registry.h`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md) is the exhaustive reference.

## Keys and values

A **key** is a node in the tree. It has an immutable GUID identity, a KACS [security descriptor](/peios/developing-for-peios/sdk-reference/sdk-security/security-h-security-descriptors.md) that governs who can read or change it, and it holds values and child keys. You address a key by path and open it for specific `KEY_*` rights, getting back a key fd whose granted access is fixed for its lifetime.

A **value** is a named, typed piece of data on a key (`REG_SZ`, `REG_DWORD`, `REG_BINARY`, and the rest). An empty name is the key's *default* value.

## Layers and precedence — the LCS idea

Here is what makes LCS more than a key/value store. Every value write is tagged with a **layer**, and layers have a fixed precedence order. When you read a value, LCS resolves the **effective** entry — the one from the highest-precedence layer that has something to say — and that's what you get back.

This is what lets configuration compose cleanly:

- A base layer ships default configuration.
- A site or policy layer overlays organisation-wide settings.
- A machine-local layer overrides per-host.

They all coexist on the same key. Reading gives you the winner; writing targets a *specific* layer (or the base layer by default). And because it's layers rather than destructive overwrites, removing a higher layer's entry lets the lower one **re-emerge** — you can override and then un-override without losing the original.

**Tombstones** are the tool for "hide, don't delete": a per-value tombstone masks lower layers for one value, and a blanket tombstone masks all lower values of a key on a layer at once. Keys have the same idea via [hide](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#deleting-and-hiding-keys).

## Reads report where the answer came from

Because a value can come from any layer, the read tells you *which* layer won and gives you a **sequence number** for the effective entry. That sequence number is the basis of safe updates: pass it back as a compare-and-swap guard on a write, and the write only lands if nothing changed underneath you.

## Transactions

Mutating operations — creating keys, setting and deleting values — can be grouped into a **transaction** and committed atomically, so a multi-step configuration change either fully applies or not at all. Transactions are abort-by-default: close the transaction fd without committing and nothing happens. See [watching and transactions](/peios/developing-for-peios/sdk-registry/watching-and-transactions.md).

## Watches

You can ask a key to notify you when it changes — values, subkeys, or its security — optionally across its whole subtree. The elegant part: once armed, **the key fd itself becomes pollable**, so a registry watch drops straight into an `epoll` loop with no side channel.

## The client, not the source

This SDK is the registry **client** — it reads and writes the store. It does not implement a registry **source** (a storage backend); that's a separate library, [**librsi**](/peios/developing-for-peios/registry-sources/overview.md). As a client you speak only the calls in [`registry.h`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md).

## Where to go in this section

- **[Reading and writing](/peios/developing-for-peios/sdk-registry/reading-and-writing.md)** — open a key, read effective values, write to layers, do safe updates.
- **[Watching and transactions](/peios/developing-for-peios/sdk-registry/watching-and-transactions.md)** — react to changes and apply atomic multi-step edits.
- **[`registry.h` reference](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md)** — every call in full.

---

# Reading and writing

_Peios / Developing for Peios / The registry_

> Open a registry key, read its effective values, write into a layer, enumerate, and do lost-update-safe updates with compare-and-swap.

This guide covers the bread-and-butter registry operations. The full call signatures and error sets are in [`registry.h`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md); here we string them together. Recall the registry's [descriptor-struct buffer convention](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#the-buffer-convention-here): reads fill `*_cap`/`*_len` fields and a zero-capacity buffer probes.

The fragments below elide some error checks for brevity — every call returns `-1` with `errno` on failure, and real code must check each one (see [Library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md)).

## Opening a key

Open a key for the rights you need:

```c
int key = peios_reg_open_key(-1, "/System/MyApp", KEY_READ, 0);
if (key < 0) {
    if (errno == ENOENT) { /* not there */ }
    else if (errno == EACCES) { /* not allowed */ }
    return -1;
}
```

`parent_fd == -1` means `path` is absolute. To open relative to a key you already hold, pass that key fd as the parent. Use [`peios_reg_create_key`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#opening-and-creating-keys) instead when the key might not exist yet — it opens-or-creates and reports which happened.

## Reading the effective value

Reading resolves layer precedence for you and hands back the winning value, its type, and which layer it came from:

```c
struct peios_reg_value v = {0};
unsigned char data[256];
v.data = data;  v.data_cap = sizeof data;
/* leave v.layer NULL if you don't care which layer won */

if (peios_reg_query_value(key, "Timeout", 7, -1, &v) == 0) {
    /* v.type is REG_*, v.data_len bytes valid in `data`, v.sequence is
       the effective entry's sequence number. */
} else if (errno == ENOENT) {
    /* no effective value (or a tombstone masks it) */
} else if (errno == ERANGE) {
    /* data buffer too small; v.data_len holds the required size — grow & retry */
}
```

The `name_len` is explicit (value names are length-counted; `0` reads the key's default value). Pass a transaction fd as the fourth argument to read within a transaction, or `-1` for none.

To read *every* value at once, use [`peios_reg_query_values_batch`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#enumerating-values) — one call fills a buffer with all effective values in a packed record format. To walk them one at a time, loop `peios_reg_enum_value` from index `0` until `ENOENT`.

## Writing a value

Writes target a specific layer. Pass `NULL`/`0` for the layer to write the base layer:

```c
uint32_t timeout = 30;
int rc = peios_reg_set_value(key, "Timeout", 7, REG_DWORD,
                             &timeout, sizeof timeout,
                             NULL, 0,      /* base layer */
                             -1,           /* auto-commit (no transaction) */
                             0);           /* no CAS guard */
```

Writing to a higher-precedence layer overrides lower ones without destroying them; [deleting](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#writing-deleting-tombstoning) that layer's entry later lets the lower value re-emerge.

## Safe updates with compare-and-swap

To read-modify-write without clobbering a concurrent change, feed the `sequence` you read back as the `expected_seq` guard on the write. The write only lands if nothing changed underneath you; otherwise it fails with `EAGAIN` and you retry:

```c
for (;;) {
    struct peios_reg_value v = {0};
    unsigned char buf[64]; v.data = buf; v.data_cap = sizeof buf;
    if (peios_reg_query_value(key, "Counter", 7, -1, &v) != 0)
        break;                                     /* real error — don't spin */

    uint32_t n; memcpy(&n, buf, sizeof n); n++;

    int rc = peios_reg_set_value(key, "Counter", 7, REG_DWORD,
                                 &n, sizeof n, NULL, 0, -1,
                                 v.sequence);          /* CAS on the sequence */
    if (rc == 0) break;                                /* success */
    if (errno != EAGAIN) { /* real error */ break; }   /* else: retry */
}
```

Passing `expected_seq == 0` disables the guard (an unconditional write).

## Cleaning up

Close key fds with `close()` when done. Values you wrote with auto-commit (`txn_fd == -1`) are already durable to the layer; to force the source to persist a hive's pending writes at a known point, call [`peios_reg_flush`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#watching-for-changes).

## Next

- **[Watching and transactions](/peios/developing-for-peios/sdk-registry/watching-and-transactions.md)** — react to changes and batch atomic edits.
- **[`registry.h` reference](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md)** — every call, field, and error.

---

# Watching and transactions

_Peios / Developing for Peios / The registry_

> React to registry changes through a pollable key fd, and apply multi-step edits atomically with transactions.

Beyond simple reads and writes, LCS gives you two higher-order tools: **watches** to react to change, and **transactions** to apply several edits atomically. Both are in [`registry.h`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md); this guide shows the shape of using them. To keep that shape visible, the fragments elide most error checks — every call returns `-1` with `errno` on failure, and real code must check each one (see [Library conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md)).

## Watching for changes

Ask a key to notify you when it changes, then poll its fd. Because the armed key fd is itself pollable, a watch integrates directly into whatever event loop you already run:

```c
int key = peios_reg_open_key(-1, "/System/MyApp", KEY_READ | KEY_NOTIFY, 0);

/* Watch values and subkeys, across the whole subtree. */
peios_reg_notify(key, REG_NOTIFY_VALUE | REG_NOTIFY_SUBKEY, 1 /* subtree */);

struct pollfd pfd = { .fd = key, .events = POLLIN };
for (;;) {
    poll(&pfd, 1, -1);
    if (pfd.revents & POLLIN) {
        /* read() the key fd to drain the change records, then re-read
           whatever you care about. */
        char records[512];
        ssize_t n = read(key, records, sizeof records);
        /* ... process change records ... */
    }
}
```

`filter` is a mask of `REG_NOTIFY_VALUE`, `REG_NOTIFY_SUBKEY`, and `REG_NOTIFY_SD`; `REG_NOTIFY_ALL` covers all three. The `subtree` flag extends the watch to descendants. Arming needs `KEY_NOTIFY` on the key. Call `peios_reg_notify(key, 0, 0)` to disarm.

Each `read()` drains as many complete change records as fit — every record starts `[total_len: u32][event_type: u16][name_len: u16][name]`, so you step through the buffer by `total_len`. The full layout, the `REG_WATCH_*` event types, and the extra path fields a subtree watch appends are in [the reference](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#watching-for-changes). Two practical notes: a buffer too small for even one record fails `EINVAL`, so size it generously rather than exactly; and a `REG_WATCH_OVERFLOW` record means events were dropped — re-read the key's state instead of trusting the stream.

This is how a service picks up configuration changes live — no polling loop re-reading values on a timer, just a blocking `poll` that wakes when something actually changed.

## Transactions

When a configuration change spans several operations — create a key, set a few values, delete a stale one — you usually want it to be all-or-nothing. That's a transaction.

Begin one, pass its fd as the `txn_fd` argument to each operation you want enlisted, then commit:

```c
int txn = peios_reg_begin_transaction();

int key = peios_reg_create_key(-1, "/System/MyApp/v2", KEY_WRITE, 0,
                               NULL /* base layer */, txn, NULL);
uint32_t one = 1;
peios_reg_set_value(key, "Enabled", 7, REG_DWORD, &one, sizeof one,
                    NULL, 0, txn, 0);
peios_reg_delete_value(key, "LegacyFlag", 10, NULL, 0, txn);

int rc = peios_reg_commit(txn);
if (rc == 0) {
    /* everything applied atomically; txn is terminal — close it */
} else if (errno == EBUSY || errno == EIO) {
    /* transient: the transaction is still active — retry the commit */
}
close(txn);
close(key);
```

Two things to keep in mind:

- **Abort is the default.** If you close the transaction fd without committing — including on any early-return error path — nothing is applied. So you don't need explicit rollback logic; just don't commit.
- **Commit can be retried.** `EBUSY` (write-lock contention) and `EIO` (source failure) leave the transaction **active**, so you can retry `peios_reg_commit`. Only `0` (committed — the fd is now terminal) and `EINVAL` (already committed or never bound) are final. Check state at any point with [`peios_reg_txn_status`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#transactions).

## Backup and restore

To snapshot a key and its whole subtree, or replace one from a snapshot, use [`peios_reg_backup`](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md#backup-and-restore) and `peios_reg_restore`. They stream to and from an fd and are gated by `SeBackupPrivilege` / `SeRestorePrivilege`; restore applies in a single transaction.

## Next

- **[`registry.h` reference](/peios/developing-for-peios/sdk-reference/sdk-registry-api/registry-h-the-registry-lcs.md)** — the notify filters, transaction states, and every error in full.
- **[Reading and writing](/peios/developing-for-peios/sdk-registry/reading-and-writing.md)** — the value operations you enlist in a transaction.
