7.5 Subkeys, metadata, and watches
7.5.0.1 Enumerating subkeys #
;
int ;
peios_reg_enum_subkey reads the child key at index, dense over visible children — walk from 0 until ENOENT. There is no per-child access check during enumeration (you see the names and counts; opening a child still checks its SD).
7.5.0.2 Key metadata #
;
int ;
peios_reg_query_key_info reads the key's leaf name and its metadata (needs READ_CONTROL). Note the ordering wrinkle: the kernel reports the metadata only once the name fits, so a too-small (or zero-capacity) name buffer returns ERANGE with the required name_len and no metadata — size the name buffer from that, then call again to get everything. The max_* fields are sizing hints for enumerations; hive_generation is a per-hive change epoch you can watch to detect that anything under the hive changed.
7.5.0.3 Deleting and hiding keys #
int ;
int ;
Both need DELETE access, take a layer (NULL/0 = base) and an optional txn_fd, and cannot target a hive root (EINVAL).
peios_reg_delete_keyremoves this key's path entry in a layer; lower-layer entries re-emerge. It fails withENOTEMPTYif the key has visible children.peios_reg_hide_keycreates aHIDDENpath entry that masks the key in a layer; removing that layer makes the key reappear. This is the key-level analogue of a tombstone — hide rather than destroy.
7.5.0.4 Watching for changes #
int ;
int ;
peios_reg_notifyarms change watches onkey_fd(needsKEY_NOTIFY).filteris a mask ofREG_NOTIFY_VALUE/REG_NOTIFY_SUBKEY/REG_NOTIFY_SD(orREG_NOTIFY_ALL);subtree(0/1) extends the watch to descendants.filter == 0disarms. Once armed, the key fd itself becomes pollable —EPOLLINsignals pending events, andread()on the fd returns the change records. So a watch integrates directly into anepollloop with no side channel. Errors:ENOENT(orphaned key),EINVAL,EACCES.peios_reg_flushforces the source to persist this key's hive's pending writes (needsKEY_SET_VALUE) and returns once persistence is confirmed — the durability barrier.
The change records. A read() on an armed key fd returns as many complete records as fit in your buffer — records are never split across reads. If the buffer is too small for even the next record the read fails EINVAL (so size it generously — a few KiB), and a non-blocking fd with nothing pending fails EAGAIN. Each record is a little-endian, possibly unaligned byte stream (Peios Kernel TRM §5.6, Watches, with the header offsets in §5.A):
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 4 | total_len | Record size in bytes — advance by this to the next record (future versions may append fields). |
| 4 | 2 | event_type | REG_WATCH_VALUE_SET / _VALUE_DELETED / _SUBKEY_CREATED / _SUBKEY_DELETED / _SD_CHANGED / _KEY_DELETED / _OVERFLOW. |
| 6 | 2 | name_len | Byte length of name; 0 for the no-name events (SD_CHANGED, KEY_DELETED, OVERFLOW). |
| 8 | name_len | name | The changed value or subkey name (UTF-8, not NUL-terminated). |
A subtree watch appends two further fields after name: path_depth (u16) and that many length-prefixed path components (u16 length + UTF-8 bytes), locating the changed key relative to the watched key — depth 0 means the watched key itself.
Delivery is best-effort with an overflow fallback: if records accumulate faster than you read them, the oldest are dropped and a REG_WATCH_OVERFLOW record is queued — on seeing one, re-read the watched key (and subtree) to recover current state rather than trusting the stream. Records describe effective (layer-resolved) changes, and uncommitted transactions produce none — events fire at commit.