5.5.3 Key Ioctls

Sixteen ioctls act on a key fd. Each checks the fd's granted mask first and returns EACCES without contacting the source if the required right is absent (§5.4.2). Numbers, directions and argument layouts are in §5.A.

Every mutating ioctl accepts an optional transaction fd. So do the four read ioctls — a read inside a bound transaction sees the transaction's own uncommitted writes. A transaction bound to a different hive is EXDEV; a committed, aborted or closed one is EINVAL; a timed-out one is ETIMEDOUT; one whose source went Down is EIO. An unbound transaction fd does not bind on a read, which is simply performed non-transactionally.

Every mutating ioctl that names a layer performs layer write authorization first (§5.3.4).

5.5.3.1 Common errors #

ErrnoCondition
EACCESThe granted mask lacks the required right, or layer write authorization failed.
EFAULTAn input pointer is invalid, or a non-zero-length output buffer pointer is null or unwritable.
EIOThe source is unavailable, failed, or the transaction's source went Down.
ETIMEDOUTThe source did not answer within RequestTimeoutMs, or the transaction had timed out.
ENOMEMKernel allocation failure.
EXDEVThe transaction is bound to a different hive.
ENOENTThe named layer is not in the layer table.
EINVALA non-zero reserved or padding field.
ENOTTYAn ioctl number this fd type does not implement.

5.5.3.2 Reading #

REG_IOC_QUERY_VALUE returns the effective value at a name: its type, data, the sequence number of the winning entry, and the canonical name of the winning layer — which is the layer table's spelling, not whatever string the source stored. A base-layer entry therefore reports base. A winning tombstone or blanket, or no entries at all, is ENOENT.

REG_IOC_QUERY_VALUES_BATCH returns every effective value on the key in one call: name, type and data for each, with tombstoned and blanket-masked values omitted. This is the call to use when you want them all (§5.3.6).

REG_IOC_ENUM_VALUES and REG_IOC_ENUM_SUBKEYS return the entry at an index, and ENOENT past the end. Both re-resolve the full set on every call, and enumeration order is undefined — see §5.3.6 for why the batch call usually wins.

ENUM_SUBKEYS performs no per-child access check. It returns every visible child with its last write time, subkey count and value count. The caller learns names and must open each child separately to read anything (§5.4.2).

REG_IOC_QUERY_KEY_INFO returns the key's name, last write time, subkey and value counts, the maximum subkey name length, maximum value name length and maximum value data size, the descriptor size, the volatile and symlink flags, and the hive generation number. It requires READ_CONTROL.

It is declared _IOWR, which is what it does: it reads the caller's output-buffer fields out of the argument structure before writing it back. It was declared _IOR, and because the direction bits are part of the encoded ioctl number and the kernel dispatches on the whole encoded value, correcting that was an ABI break rather than a relabelling. A binary built against the old constant gets ENOTTY from a kernel carrying the new one.

5.5.3.2.1 The hive generation number #

A monotonic per-hive change epoch owned by the kernel. It is not a sequence number and must not be read as one: sources neither report it nor persist it (§5.3.7).

It is incremented once per committed mutation, or once per committed transaction per affected hive, however many operations that transaction contained. Its baseline is initialised from the source's reported maximum sequence at registration so that observed values are monotonic relative to persisted entries; saturating at U64_MAX is EOVERFLOW.

It is exposed on every key because it is cheap and because a watcher that receives OVERFLOW can compare the generation it last saw with the current one and skip the recovery re-read entirely if nothing has committed (§5.6.4).

Layer operations produce a single generation increment covering the metadata key deletion, RSI_DELETE_LAYER, the recomputation and the resulting watch effects. There is no generation at which the metadata key is gone but the layer's entries are still resolving. An operation affecting several hives increments each independently.

5.5.3.3 Writing #

REG_IOC_SET_VALUE writes a value entry in a layer. LCS allocates a sequence number and tells the source to store (key GUID, value name, layer) → (type, data, sequence), then updates the key's last write time.

Writing type REG_TOMBSTONE is the explicit tombstone operation and requires zero-length data (§5.2.6).

A non-zero expected_sequence makes the write conditional. It is passed to the source, which atomically verifies that the layer's own current entry carries that sequence number before writing, and answers RSI_CAS_FAILED if not — which LCS returns as EAGAIN. There is no kernel-side query-then-write: the check is the source's, and it is atomic there or nowhere.

The condition is evaluated against the layer's own entry, not against the effective value. A higher-precedence layer overriding a value is not a lost update; it is the layer system working.

Additional errors: EINVAL for an unknown type or a tombstone with data; EAGAIN for a failed conditional write; ENOSPC for the layer cap or oversized data; ENAMETOOLONG for the value name; EPERM for a Precedence above 0 without SeTcbPrivilege (§5.3.4).

REG_IOC_DELETE_VALUE removes one layer's entry at a value name — whether that entry was a value or a tombstone. Removing a layer's opinion lets lower-precedence layers surface.

The operation is meant to be idempotent, and it is idempotent as far as a caller sees, provided the source answers RSI_OK for an entry that was not there. LCS does not mask a source's RSI_NOT_FOUND; that propagates as ENOENT. Idempotency is a source obligation, not a kernel behaviour.

REG_IOC_BLANKET_TOMBSTONE sets or removes a blanket tombstone for a layer on this key (§5.2.7). A new sequence number is assigned for dispatch ordering, and watch events are generated for every value whose effective state changed.

REG_IOC_DELETE_KEY removes this key's path entry from a layer. The parent GUID comes from the fd's ancestor chain and the child name from the last component of its resolved path. ENOTEMPTY if the key has visible children; EINVAL on a hive root (§5.2.9).

REG_IOC_HIDE_KEY creates a HIDDEN path entry at the same place instead, masking lower-precedence entries. The caller must hold an open fd to the key, which is how it proved access to it. EINVAL on a hive root.

5.5.3.4 Security #

REG_IOC_GET_SECURITY and REG_IOC_SET_SECURITY read and merge Security Descriptor components, selected by a security_info bitmask. §5.4.3 covers the rights, the merge, and the validation.

5.5.3.5 Watches, durability, bulk #

REG_IOC_NOTIFY arms, re-arms or disarms a watch (§5.6.1).

REG_IOC_FLUSH tells the source to persist pending writes for this key's hive, and returns when persistence is confirmed. The hive name comes from the first component of the fd's resolved path. It requires KEY_SET_VALUE (§5.4.2).

REG_IOC_BACKUP and REG_IOC_RESTORE export and replace a subtree. They are privilege-gated with no per-key access check, and they are covered in §5.9.

Edit this page