# Registry sources

---

# Registry sources overview

_Peios / Developing for Peios / Registry sources_

> What a registry source is, how the RSI protocol flows, and the shape of a source's serve loop — the provider side of the registry, built with librsi.

A **registry source** is a storage backend for the [LCS registry](/peios/developing-for-peios/sdk-registry/overview.md). It is the other half of the registry story: where a [client](/peios/developing-for-peios/sdk-registry/overview.md) opens keys and reads values, a *source* is what actually holds those keys and values and answers the kernel when it needs them. If you are implementing a place for registry data to live — a file-backed store, a database adapter, an in-memory provider for tests — you are writing a source, and **librsi** is the library for it.

This is a different library from libpeios, for a different audience. libpeios is the registry *client*; librsi is the registry *source*. They share the SDK's [conventions](/peios/developing-for-peios/sdk-reference/sdk-conventions/library-conventions.md) and substrate, but you link `librsi` (`-lrsi`, `<rsi.h>`) to write a source.

## How it works

A source talks to the kernel over the **RSI** protocol — the Registry Source Interface — on a single file descriptor:

```mermaid
flowchart LR
    client[Registry client<br/>libpeios] -->|syscalls| kernel[LCS in the kernel]
    kernel <-->|RSI framed protocol<br/>over the source fd| source[Your source<br/>librsi]
```

The flow is:

1. **Register.** Your process declares which *hives* (registry subtrees) it backs and registers with the kernel, receiving a **source fd**. This needs `SeTcbPrivilege`.
2. **Serve.** The kernel sends your source **requests** — "look up this child", "store this value", "begin this transaction" — as framed messages you `read(2)` from the source fd. You decode each one, do the work against your storage, and `write(2)` back a framed **response**.
3. **Repeat** until the source fd reaches EOF (the source is closing).

When a client reads or writes a key in one of your hives, the kernel turns that into RSI requests to *you*. Your source is the source of truth; the kernel mediates, enforces security, and resolves layer precedence on top of what you report.

## The serve loop

Every source is, at heart, this loop:

```c
for (;;) {
    ssize_t n = rsi_read_request(src_fd, buf, sizeof buf);   /* read a frame */
    if (n == 0) break;                                       /* EOF: closing */
    if (n < 0) { /* errno */ break; }

    struct rsi_request req;
    rsi_parse_request(buf, n, &req);                         /* split header/payload */

    switch (req.op_code) {                                   /* dispatch */
        case RSI_LOOKUP:     /* decode, resolve, rsi_respond_lookup */    break;
        case RSI_SET_VALUE:  /* decode, store,   rsi_respond_status */    break;
        /* … one case per op you support … */
    }
}
```

The three headers map onto the three steps:

- **[`<rsi/source.h>`](/peios/developing-for-peios/sdk-reference/sdk-rsi-source/rsi-source-h-becoming-a-source.md)** — registering and getting the source fd.
- **[`<rsi/request.h>`](/peios/developing-for-peios/sdk-reference/sdk-rsi-request/rsi-request-h-decoding-requests.md)** — reading and decoding requests.
- **[`<rsi/response.h>`](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md)** — building and sending responses.

## What the kernel handles, and what you handle

The division of labour matters, because it keeps a source simple:

- **The kernel** handles security (access checks against key SDs), layer precedence resolution, transaction coordination across sources, and the client-facing syscall surface. It also assigns the global sequence numbers.
- **Your source** handles durable storage: it stores the name→GUID entries, the key metadata records, and the layered values, and it reports them back faithfully when asked. It honours transaction boundaries (buffer, then commit or abort) and compare-and-swap guards on writes.

You do **not** implement precedence, access control, or the client protocol — you store data and answer questions about it. That's why a source's serve loop is mostly a dispatch table over storage operations.

## Requests come in families

The [request reference](/peios/developing-for-peios/sdk-reference/sdk-rsi-request/rsi-request-h-decoding-requests.md) groups the operations, and it helps to hold the shape in mind:

- **Path/entry ops** — the name→GUID hierarchy (`LOOKUP`, `CREATE_ENTRY`, `HIDE_ENTRY`, `DELETE_ENTRY`, `ENUM_CHILDREN`).
- **Key ops** — key metadata records (`CREATE_KEY`, `READ_KEY`, `DROP_KEY`, `WRITE_KEY`).
- **Value ops** — the typed values on a key (`QUERY_VALUES`, `SET_VALUE`, `DELETE_VALUE_ENTRY`, `SET_BLANKET_TOMBSTONE`).
- **Transaction ops** — atomic grouping (`BEGIN`/`COMMIT`/`ABORT_TRANSACTION`).
- **Layer ops** — `DELETE_LAYER`, `FLUSH`.

Most reply with a simple status; five carry data back on success.

## Where to go in this section

- **[Registering a source](/peios/developing-for-peios/registry-sources/registering-a-source.md)** — declare your hives and get the source fd.
- **[Serving requests](/peios/developing-for-peios/registry-sources/serving-requests.md)** — the read/parse/dispatch/decode loop in full.
- **[Building responses](/peios/developing-for-peios/registry-sources/building-responses.md)** — status-only and payload-bearing replies.

---

# Registering a source

_Peios / Developing for Peios / Registry sources_

> Declare the hives your source backs and register with the kernel to obtain the source fd.

Every source starts by registering. You tell the kernel which hives your process backs, and it hands you a source fd to serve on. The full detail is in [`rsi/source.h`](/peios/developing-for-peios/sdk-reference/sdk-rsi-source/rsi-source-h-becoming-a-source.md); this guide walks the decisions.

## Declare your hives

A hive is a registry subtree with its own root key. Fill in one `struct rsi_hive` per hive your source backs:

```c
struct rsi_hive hive = {
    .name     = "MyStore",
    .name_len = 7,
    .flags    = 0,                    /* global hive */
    .root_guid = { /* 16-byte GUID of the hive root key */ },
    /* scope_guid left zero for a global hive */
};
```

The two decisions per hive:

- **Global or private?** A **global** hive (`flags = 0`, `scope_guid` zero) is visible system-wide. A **private** hive (`flags = RSI_HIVE_PRIVATE`, `scope_guid` non-zero) is scoped — only tokens carrying that scope GUID in their [LCS credentials](/peios/developing-for-peios/sdk-reference/sdk-tokens/token-h-tokens-and-sessions.md#lcs-registry-credentials) can resolve it. Use a private hive for per-application or per-tenant state that shouldn't be system-visible.
- **The root GUID.** Every hive is anchored at a root key identified by `root_guid`. This is the GUID paths in the hive resolve from, and it must match the root your storage actually holds.

## Register

Hand the kernel your hive array and the highest sequence number you've already persisted:

```c
int src = rsi_register(&hive, 1, /*max_sequence=*/0);
if (src < 0) {
    perror("rsi_register");   /* EPERM if you lack SeTcbPrivilege */
    return -1;
}
/* `src` is the source fd — serve the RSI protocol on it. */
```

Two things to get right:

- **`SeTcbPrivilege` is required.** Registration is a trusted operation; without the privilege you get `EPERM`. Sources run as trusted system components.
- **`max_sequence` is your durability contract.** It is the highest sequence number this source has *already persisted*. The kernel resumes its global sequence counter past it, so it never hands out a number you've used. A brand-new source with no stored state passes `0`; a source restarting with durable data must scan that data for the highest sequence it ever wrote and pass *that*. Getting this wrong risks sequence reuse, so make it the first thing your restart path computes.

You can register several hives in one call by passing an array and a `count` greater than one; the kernel enforces its configured `MaxHivesPerSource` limit (`ENOSPC` if you exceed it).

## After registration

The returned fd *is* your serve endpoint — you `read(2)` requests and `write(2)` responses on it directly (via the [request](/peios/developing-for-peios/sdk-reference/sdk-rsi-request/rsi-request-h-decoding-requests.md) and [response](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md) helpers). Closing it deregisters the source and signals EOF to the kernel. Keep it open for the life of the source, and move on to the [serve loop](/peios/developing-for-peios/registry-sources/serving-requests.md).

## Next

- **[Serving requests](/peios/developing-for-peios/registry-sources/serving-requests.md)** — the loop that runs on the source fd.
- **[`rsi/source.h` reference](/peios/developing-for-peios/sdk-reference/sdk-rsi-source/rsi-source-h-becoming-a-source.md)** — every field and error.

---

# Serving requests

_Peios / Developing for Peios / Registry sources_

> The source serve loop end to end — read a framed request, parse its header, dispatch on the op-code, and decode the payload with its typed parser.

With a [source fd in hand](/peios/developing-for-peios/registry-sources/registering-a-source.md), a source spends its life in one loop: read a request, decode it, do the work, reply. This guide builds that loop. The exhaustive per-op detail is in [`rsi/request.h`](/peios/developing-for-peios/sdk-reference/sdk-rsi-request/rsi-request-h-decoding-requests.md) and [`rsi/response.h`](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md).

## The loop skeleton

```c
unsigned char buf[64 * 1024];   /* size generously — a short buffer is EMSGSIZE */

for (;;) {
    ssize_t n = rsi_read_request(src_fd, buf, sizeof buf);
    if (n == 0) break;                       /* EOF — the source is closing */
    if (n < 0) { perror("read_request"); break; }

    struct rsi_request req;
    if (rsi_parse_request(buf, n, &req) != 0) continue;   /* EBADMSG — skip */

    switch (req.op_code) {
        case RSI_LOOKUP:    handle_lookup(src_fd, &req);    break;
        case RSI_SET_VALUE: handle_set_value(src_fd, &req); break;
        /* … a case per op you support … */
        default:
            /* Unknown or unsupported op: reply with a non-OK status
             * (RSI_TXN_NOT_SUPPORTED for transaction ops you don't implement). */
            rsi_respond_status(src_fd, &req, RSI_INVALID);
            break;
    }
}
```

Three details in that skeleton matter:

- **`rsi_read_request` blocks** until a request is queued, and returns **`0` at EOF** — that's your exit. Size `buf` generously; a frame larger than `buf` returns `-1`/`EMSGSIZE`.
- **`rsi_parse_request` gives you `req.op_code`** to dispatch on, plus `req.request_id` (which every reply must echo — the helpers do this for you) and `req.txn_id` (nonzero when the request is inside a transaction).
- **Always reply.** Every request expects exactly one response. If you can't handle an op, reply with a non-OK status rather than dropping it.

## Decoding a request

Inside a handler, decode the payload with the matching `rsi_request_*` parser. For a `LOOKUP`:

```c
void handle_lookup(int fd, const struct rsi_request *req)
{
    struct rsi_lookup q;
    if (rsi_request_lookup(req, &q) != 0) {          /* EBADMSG */
        rsi_respond_status(fd, req, RSI_INVALID);
        return;
    }

    /* q.parent_guid (by value), q.child_name / q.child_name_len (borrowed).
       Resolve the child in your storage across its layers … */

    /* … then reply with the resolved path entries + metadata: */
    rsi_respond_lookup(fd, req, entries, entry_count, metadata, metadata_count);
}
```

And for a `SET_VALUE`, a status-only op:

```c
void handle_set_value(int fd, const struct rsi_request *req)
{
    struct rsi_set_value v;
    if (rsi_request_set_value(req, &v) != 0) {
        rsi_respond_status(fd, req, RSI_INVALID);
        return;
    }

    /* Honour the CAS guard before storing. */
    if (v.expected_sequence != 0 && current_seq(&v) != v.expected_sequence) {
        rsi_respond_status(fd, req, RSI_CAS_FAILED);
        return;
    }
    store_value(&v);                                  /* your storage */
    rsi_respond_status(fd, req, RSI_OK);
}
```

## The borrow rule

Every decoded name and data field — `q.child_name`, `v.value_name`, `v.data`, and the rest — is a `(ptr, len)` pair that **points into `buf`**. Those pointers are valid only until the next `rsi_read_request` reuses the buffer. So:

- **Consume them within the handler** (store the bytes, resolve the name) before the loop comes around again, or
- **Copy out** anything you need to keep. Never stash a raw borrowed pointer across iterations.

GUIDs and scalars in the decoded struct are copied by value, so those are always safe to keep — it's only the borrowed `(ptr, len)` fields that have the lifetime.

## Transactions

When the kernel sends `BEGIN_TRANSACTION`, buffer subsequent writes tagged with that `transaction_id` instead of applying them; `req.txn_id` on each later request tells you which transaction it belongs to (`0` = none). On `COMMIT_TRANSACTION` apply the buffered set atomically; on `ABORT_TRANSACTION` discard it. All three are status-only replies. The kernel coordinates transaction boundaries — your job is to buffer, then commit or discard on command.

## Next

- **[Building responses](/peios/developing-for-peios/registry-sources/building-responses.md)** — choosing and filling the right reply.
- **[`rsi/request.h` reference](/peios/developing-for-peios/sdk-reference/sdk-rsi-request/rsi-request-h-decoding-requests.md)** — every op's decoder and struct.

---

# Building responses

_Peios / Developing for Peios / Registry sources_

> Reply to RSI requests the right way — status-only for most ops and failures, payload-bearing helpers for the five ops that return data, and the validation contract to respect.

Every request gets exactly one response. Choosing the right one is simple once you know the rule; filling it in is a matter of handing librsi flat arrays and letting it encode the frame. The full contract is in [`rsi/response.h`](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md); this guide is the working version.

## The rule

> **On failure, always [`rsi_respond_status`](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md#status-only-responses). On success, `rsi_respond_status` too — unless the op is one of the five that carry a payload.**

Most operations (`SET_VALUE`, `CREATE_KEY`, the transaction ops, `FLUSH`, …) are status-only in both cases. And *any* op reports a non-OK outcome with `rsi_respond_status`, whatever it is:

```c
/* Success for a status-only op: */
rsi_respond_status(fd, req, RSI_OK);

/* Any op reporting a problem: */
rsi_respond_status(fd, req, RSI_NOT_FOUND);   /* e.g. a missing key */
rsi_respond_status(fd, req, RSI_CAS_FAILED);  /* a failed CAS */
```

You never build the frame or echo the request id yourself — the helper reads what it needs from `req` and writes the framed reply to `fd`.

## The five payload-bearing ops

Exactly five ops return data on success, each with its own helper:

| Op | Helper | You supply |
|---|---|---|
| `LOOKUP` | `rsi_respond_lookup` | path entries + the referenced keys' metadata |
| `ENUM_CHILDREN` | `rsi_respond_enum_children` | children (name + path entries) + metadata |
| `READ_KEY` | `rsi_respond_read_key` | one key's non-layered metadata |
| `QUERY_VALUES` | `rsi_respond_query_values` | value entries + blanket tombstones |
| `DELETE_LAYER` | `rsi_respond_delete_layer` | the orphaned keys' GUIDs |

You pass the result as flat arrays; librsi validates and heap-encodes the wire frame. A `QUERY_VALUES` reply, for example:

```c
struct rsi_value_entry values[] = {
    { .value_name = "Timeout", .value_name_len = 7,
      .layer_name = "base",    .layer_name_len = 4,
      .value_type = REG_DWORD, .data = &timeout, .data_len = 4,
      .sequence = 42 },
    /* … one per effective value you hold … */
};

rsi_respond_query_values(fd, req, values, 1, /*blankets=*/NULL, 0);
```

You report *what you store*, per layer; the kernel resolves precedence across the layers you return. (Note the `(NULL, 0)` for the empty blanket array — a NULL pointer is allowed only when its count is zero.)

## The validation contract

The helpers check their inputs before encoding and return `-1`/`EINVAL` if you break the contract — better a caught programming error than a malformed frame on the wire. The rules that apply across all of them:

- **`(ptr, len)` pairs:** a pointer may be `NULL` only when its length/count is zero.
- **Booleans** (`volatile_key`, `symlink`, target types) are strictly `0` or `1`.
- **Hidden path targets** (`RSI_PATH_TARGET_HIDDEN`) carry an **all-zero** `target_guid`.
- **`LOOKUP` / `ENUM_CHILDREN` metadata** must exactly cover the GUID targets in your path entries — every referenced key present, none missing, no duplicates, nothing unreferenced.
- **`DELETE_LAYER` orphan GUIDs** are nonzero and unique.

Beyond `EINVAL`, a helper can also fail with `ENOMEM` or `EOVERFLOW` (building the frame), `EIO` (a short write), or the raw `write(2)` errno — treat those as you would any I/O failure on the source fd.

## Putting it together

A source's handler for a payload-bearing op is: decode the request, gather the result from storage into the flat arrays the helper wants, call the helper. For a status-only op: decode, do the work, `rsi_respond_status`. On any error along the way — a decode failure, a missing key, an I/O problem — `rsi_respond_status` with the appropriate non-OK `RSI_*` code. That uniformity is what keeps a source's dispatch table readable no matter how many ops it supports.

## Next

- **[`rsi/response.h` reference](/peios/developing-for-peios/sdk-reference/sdk-rsi-response/rsi-response-h-building-responses.md)** — every helper, struct, and error in full.
- **[Serving requests](/peios/developing-for-peios/registry-sources/serving-requests.md)** — the loop these replies live in.
