[mlir-c] Fix testDialectMaterializeConstant review comments
Correct the misleading comment about the func dialect (it does have a
constant materializer; it just declines an i32 IntegerAttr), and assert
the documented insertion-point contract: the op lands in the current
insertion block and the rewriter's insertion point is preserved.
[mlir-c] Fix testDialectMaterializeConstant review comments
Correct the misleading comment about the func dialect (it does have a
constant materializer; it just declines an i32 IntegerAttr), and assert
the documented insertion-point contract: the op lands in the current
insertion block and the rewriter's insertion point is preserved.
Register the iSCSI extent delegate and split iscsi.extent.update_internal
## Problem
LUN size and readonly resyncs after a zvol property change ran only from `pool.dataset.update`, so a `zfs.resource.set` on a zvol backing an extent left the target stale. On top of that, the readonly resync went through the public `iscsi.extent.update`, which writes `readonly` back to the zvol, so a readonly change on a shared zvol produced a second CHANGED event.
## Solution
Add an `iscsi.extent` delegate for volumes that rejects hiding snapshot devices an extent is using, resyncs the LUN size when `volsize` changes and resyncs the extent's `ro` flag whenever `readonly` is set or inherited. It is registered with `zfs.resource.set` from the iSCSI plugin setup.
The body of `iscsi.extent.update` moves to a private `update_internal` that only writes `readonly` to the zvol when asked to. The public update still does, while the ZFS-originated resync calls it without the write-back. As a side effect that resync no longer writes an "Update iSCSI extent" audit row, since it's an internal sync rather than a user action.
Skip the refreservation headroom leg when a refquota clamps available
## Problem
`zfs.resource.set`'s reservation headroom rule takes `available − usedbyrefreservation` as the space the kernel will check a refreservation against. That identity holds only when the dataset has no refquota of its own; a filesystem's `available` is clamped to `refquota − referenced`, so on a refquota'd filesystem the rule rejected any reservation above 80% of the refquota remainder, reported a second error next to the refquota check, and refused a refquota raise and refreservation grow sent in one request.
## Solution
Check the refreservation against the effective refquota (the kernel compares after the same-nvlist refquota is applied) and run the headroom leg only when the on-disk refquota is zero, which is exactly when the visible base equals the kernel's directory-level figure; the shared headroom helper loses its refquota leg. The dedup-inheritance integration test is restructured so its entitlement mocks are sequential rather than nested.
Point pool.dataset.update at zfs.resource.set
## Problem
`pool.dataset.update` carried its own copy of every ZFS policy check (ACL coupling, headroom, blocksize alignment, recordsize choices, dedup licensing, SMB/snapdev guards, LUN resyncs) and wrote through the raw `update_impl`, so the two write paths could drift and the pool path skipped whatever `zfs.resource.set` and its delegates enforce.
## Solution
Translate the pool payload into a `zfs.resource.set` request (lowercasing the index values the pool Literals uppercase, passing sizes and `copies` as ints, `quota: null` as 0, `INHERIT` and removed user properties as `inherit`) and let `zfs.resource.set` and its delegates validate and write. The update validation keeps only the field-shape rejections and the `user_properties_update` handling; everything else, the resyncs and the `ZFSException` mapping go.
When the shim itself already found errors, the request is run through `zfs.resource.set` as a `dry_run` so the caller still gets shim-side and zfs-side errors in one `ValidationErrors`. The request model is built inside the same `try`, so a value the zfs models reject comes back as a validation error rather than a raw pydantic one. Errors are re-keyed onto `pool_dataset_update.<field>`: the property's own field when the caller sent it, `acltype` for the derived `aclmode`/`aclinherit`, `volsize` for the re-reservation a volsize grow triggers, `user_properties_update`/`user_properties` for the caller's own user properties, and bare `pool_dataset_update` for anything structural.
Behaviour changes worth knowing: `copies` outside 1..3 and a negative `reservation` are now rejected; the recordsize and shrink messages come from the zfs rules; the ACL combination error lands on `acltype` rather than `aclmode`; a ZFS failure is keyed on `pool_dataset_update` instead of `pool.dataset.update`; the dRAID volblocksize check on update is gone, and `force_size` no longer bypasses the headroom check (its description says so). An empty payload still skips the write and emits CHANGED.
Add a delegate registry to zfs.resource.set
## Problem
Other plugins' policy on a property change (share ACL constraints, LUN resyncs, snapshot-device visibility) lived inline in `pool.dataset.update`, so `zfs.resource.set` could not enforce it and every new consumer of the raw namespace would have had to re-implement it.
## Solution
Add a delegate registry to `zfs.resource`: a delegate declares the resource types and property names it cares about, validates into the same `ValidationErrors` as the intrinsic rules before anything is written, and runs its side effects after a successful write, where a failure is logged and never breaks the write's return. Both hooks are coroutines, bridged once per phase from the thread `set` runs on and skipped when no delegate participates. A volume's present snapshot devices are handed to delegates when the request touches `snapdev`. The mechanism is pinned with fake delegates; the real registrants follow separately.
Guard rename, promote and snapshot rename against protected paths and mount clones
## Problem
`zfs.resource.rename` guarded neither name against protected paths and carried a ceremonial `force` flag, `promote` had no guard at all, and snapshot rename only guarded the source. A failed destroy surfaced as a validation error even though the input was fine, and a filesystem created by `zfs.resource.snapshot.clone` was never mounted.
## Solution
- **Protected paths**: rename rejects a protected source or destination, promote rejects a protected path and snapshot rename also checks the destination. Each model gets a `Private` `bypass` for middleware callers, and `force` is gone from the rename model.
- **Destroy failures**: an operational destroy failure is raised as a `CallError` carrying the errno, and the clones error is attributed to the method rather than `defer`. The snapshot destroy docstring now documents `EBUSY`, which is what is actually raised.
- **Clone mounts**: `clone_impl` reports whether it created a filesystem, and the public `clone` mounts only those unless the new `no_mount` option is set. A mount failure is logged as a warning and the clone is kept, matching zfs(8).
Validate type validity, headroom, blocksize alignment and ACL coupling in zfs.resource.set
## Problem
With the effective-state machinery in place, `zfs.resource.set` still accepted properties invalid for the target type, inherits of values that replication would immediately bring back, a reservation grow with no headroom, a volsize that is not a multiple of the volblocksize, dedup on a dataset whose descendants are tiered, an out-of-range recordsize and an out-of-range `special_small_blocks`. The ACL companions of `acltype` were also filled in before the resource was read, so an `acltype` on a volume fanned out into three errors, and a thick zvol grow left its reservation behind.
## Solution
Add the remaining intrinsic rules to the registry, couple the ACL companions after the read on filesystems only, and follow a thick zvol's reservation on grow with libzfs's own `auto` so its rollback stays live and an over-reservation is never lowered. Names middleware adds on the caller's behalf are recorded as derived, and errors about them are attributed to the field the caller actually sent.
Return the written state from the same handle in zfs.resource.set_impl
## Problem
`set` re-read the dataset through a fresh `list` call after writing and indexed `[0]`, so what it returned was a second query rather than the state the write produced. libzfs failures from all three write phases were collapsed into one attribute with no hint of which phase failed or what had already been applied, `EZFS_BADVERSION` and `EZFS_NOTSUP` fell through to a generic error, and nothing pinned that `volsize` and `refreservation` travel in one nvlist, which is what keeps libzfs's volsize rollback alive.
## Solution
`set_impl` normalises its optional arguments once, writes, and returns the entry projected from the handle that did the write (touched natives only, `properties` null when none were touched). Each phase maps its own failure: invalid input becomes a `ValidationError` on the property, `user_properties` or `inherit.<name>` attribute, a vanished resource and operational failures become `CallError`s, and every message except the vanished case ends with the values now on disk, read from the same handle after refreshing it since libzfs only refreshes on success. `EZFS_BADVERSION`/`EZFS_NOTSUP` join the invalid-input table, the set model's `path` is typed as a dataset name, and the set model's `volsize`, `refreservation`, `user_properties` and `inherit` descriptions state the set-time contract. Unit tests pin the single nvlist, the same-handle projection and every row of the error mapping.
Register the share, namespace and VM device delegates
## Problem
The SMB acltype guard, the NVMe-oF namespace resync and the VM device snapshot-device guard still lived only in `pool.dataset.update`, so `zfs.resource.set` could change the ACL type under enabled SMB shares, leave a namespace at its old size after a volsize grow, or hide a zvol snapshot device a VM disk depends on.
## Solution
Register `smb.share`, `nvmet.namespace` and `vm.device` delegates with `zfs.resource.set` from each plugin's setup. The SMB delegate goes through `pool.dataset.attachments`, which resolves the mountpoint itself, and lists every share name joined with commas rather than printing the list repr. The namespace and VM legs of the snapdev guard decode their stored device paths before comparing them against the volume's snapshot devices.
A unit test runs `validate_delegate` over the four concrete delegates so a malformed one fails CI instead of boot, and new integration tests cover the delegates end to end and the `zfs.resource.list` event stream, including the kernel-sourced destroy events and the replication receive filter.
Emit zfs.resource.list events
## Problem
`zfs.resource` mutations were invisible to subscribers: no ADDED/CHANGED event was sent from set, create, rename or promote, the kernel history hook fed only `pool.dataset.query` REMOVED, and no per-type event models existed to document the payload.
## Solution
Register `zfs.resource.list` with per-type event models and emit from the service `*_impl` methods once the write has returned: CHANGED from `set_impl` carrying only what was touched under `fields` (so tier, pool import and HA writes emit too, while a dry run emits nothing), ADDED from `create_impl`, REMOVED then ADDED from `rename_impl` and an `origin`-only CHANGED from `promote_impl`. Destroy is left to the kernel history hook, which now also sends `zfs.resource.list` REMOVED for destroyed datasets, skipping snapshots and hidden `%` clones such as the ones replication receives through. An import test catches event model naming mistakes in CI and unit tests pin every emission.
The set model's `user_properties` description also stops promising an 8192-byte value bound; the API caps strings at 1024 characters.
Run zfs.resource.processes, recommended_zvol_blocksize and tier job queries off the event loop
## Problem
`zfs.resource.processes` and `recommended_zvol_blocksize` were `async` shells over synchronous work, and `zfs.tier.rewrite_job_query`/`rewrite_job_status` were declared `async` with no awaits while calling blocking zfstierd client functions, so all of them ran on the event loop. The blocksize helper also read `pool.query`'s renamed, uppercased vdev types, which don't match the raw pylibzfs topology.
## Solution
Make the four methods plain `def` so they dispatch to the io thread pool. The blocksize wrapper reads the raw topology from `zpool.query_impl` and the helper compares lowercase `vdev_type` values; a missing pool raises an ENOENT validation error. `zpool.query_impl`'s return annotation is corrected to the dicts it actually returns, and the blocksize branches are pinned with fixtures whose answer differs from the fallthrough. The integration test that only compared `zfs.resource` against its `pool.dataset` shim is replaced by a raidz2 pool check and a nonexistent-pool check.
Fix integration test assertions for promote, destroy and blocksize errors
## Problem
Three groups of `zfs.resource` integration tests asserted the wrong exception shape: promote's empty-path test still expected the old non-empty-string message after `path` became a dataset name, the destroy failure tests caught `ClientException` although the integration client re-raises server `CallError`s as `CallError`, and the nonexistent-pool blocksize test expected `ValidationErrors` where the method raises a single `ValidationError`.
## Solution
Point each assertion at the exception class and message the server actually raises, keeping every attribute, errno and message expectation.
Encode zvol names when matching snapshot devices to iSCSI extents
## Problem
The iSCSI extent delegate looked up extents on the snapshot devices about to be hidden by `zvol/<dataset>@<snap>` built from the raw dataset name, but an extent stores its path in the encoded form where a space becomes `+`. A zvol or snapshot with a space in its name therefore never matched, and `snapdev=hidden` removed the device node from under a live extent, which the attachment scan it replaced caught correctly.
## Solution
Build the lookup paths with `zvol_name_to_path`, the same encoder the extent disk choices come from, and pin it with unit tests over names containing spaces, including the exact path list passed to `iscsi.extent.query`.
Resolve snapshot names to their dataset in the locked-path check
## Problem
`pool.dataset.path_in_locked_datasets` opened the resource named by the path and read its `crypto()`; a snapshot-backed iSCSI extent, NVMe-oF namespace or VM disk (`zvol/<dataset>@<snap>`) therefore reached it with an `@` name, pylibzfs returned a snapshot object with no `crypto`, and the AttributeError escaped, so `iscsi.extent.create` on such an extent inserted its row and then crashed in `get_instance`, and every later extent query failed on the orphan.
## Solution
A snapshot holds no keys, so its lock state is its dataset's: strip the snapshot suffix before the parent walk, which also lets the ancestor-prefix check see the dataset. Pinned with a unit test over the three accepted path forms.
Aggregate create-side validation and share the headroom rule
## Problem
`zfs.resource.create`'s rules raised on the first violation, so a caller had to fix a request one error at a time. The create-side headroom check measured a new volume's reservation against the ancestor's `available`, which counts the unused part of the ancestor's own refreservation that a new child can never use. And the post-write re-read indexed `[0]` on a list whose order nothing guaranteed.
## Solution
The shared `reject_*` helpers move into `rules_common.py`, where each one appends to a `ValidationErrors` under a caller-supplied attribute instead of raising. Every create rule now appends too, and `create_impl` checks the collection at two barriers: once after the name, entitlement and parent-type checks (the rules below read filesystem-only properties from the parent), and once right before the ioctl. `check_volume_capacity` goes through the new shared `reject_insufficient_headroom` rule with `available - usedbyrefreservation` of the nearest ancestor as its base and reports on `properties.refreservation`. User property names follow ZFS's own grammar and values are length-checked and single-line. The tier config is read only when a tier-managed property can be involved, and the read-back finds the new resource by name. The set rules call the shared helpers through a local collector and raise the first violation, so the set path's behaviour is unchanged.
Point pool.snapshot and pool.dataset at public zfs.resource methods
## Problem
`pool.snapshot.rename` has been broken on master since the typesafe conversion of `zfs.resource.rename`: it called the dataset method, which rejects every `@` path with "Use `zfs.resource.snapshot.rename` to rename snapshots", so every call failed. `pool.snapshot.create`, `clone`, `hold` and `release` called the raw `*_impl`s, which translate nothing, so a nonexistent path or an existing destination surfaced as a raw traceback with EINVAL instead of a validation error, and the pool layer duplicated the translation it did get right (`rollback`).
## Solution
Call the public `zfs.resource.snapshot.*` and `zfs.resource.*` wrappers, which carry the ENOENT/EEXIST/not-a-snapshot translation and the `@` placement checks, and re-key their `ValidationError` attributes onto the `pool.snapshot.*` names so the pool error contract is preserved. `pool.dataset.rename`, `promote`, `delete` and `snapshot_count` go through the public methods too; `delete` keeps its own protected-path and children checks so a doomed delete fails before attachments are stopped.
`pool.snapshot.create` is the one seam whose callee returns a model to internal callers, so it dumps it before the dict-reading transform. A unit test drives the real method through the stub middleware so a dropped dump or a wrong re-key fails in CI rather than taking the VM suite down.
Audit zfs.resource mutating methods
## Problem
None of the `zfs.resource` or `zfs.resource.snapshot` mutating methods wrote an audit entry, so a destroy, rename, promote, property change or snapshot operation issued through the public API left no trace in the audit log.
## Solution
Add `audit=` and `audit_extended` to every mutating method, describing the target path and the flags that change the blast radius (recursive, defer, all_snapshots and the rollback options). The descriptions are built from the raw request dict with `.get` so a malformed request can never break the audit hook, and `zfs.resource.set` lists every property it changes, user property it sets and property it inherits.
Assert the raw copies value in the pool.dataset.update shim test
## Problem
The test compared `copies` from `zfs.resource.list` against the integer 2, but `copies` is an index property in ZFS, so its parsed value comes back as the string label "2" and the assertion failed on every run.
## Solution
Assert the raw value "2", matching how the neighbouring dedup and compression tests check what ZFS stored.
Resolve effective state before writing in zfs.resource.set
## Problem
`zfs.resource.set` validated each rule against the target's current value, so an inherited property was judged by the dataset's own value instead of its parent's (`inherit=["dedup"]` could not be gated at all). Rules raised one at a time, a bug inside a rule surfaced as a 500 instead of a validation failure, the dataset was read with a property set that varied per request, and the tier config was read on every call.
## Solution
- **One fixed read**: the target, plus its parent only when a native is inherited on a non-root dataset, is read once with a fixed property set. A missing target is a `ValidationError` on `path` (ENOENT); the tier config and the DEDUP entitlement are read only when the request touches the properties they govern.
- **Effective state**: a frozen `SetContext` resolves every property through `effective()` (requested value, else the parent's value or the registered ZFS default on a pool root, else the current value). Values live in a `PropertyView` that raises `CallError` for a name that was not read.
- **Aggregated rules**: the request rules run before any I/O and report together. The volsize, ACL, tiering and dedup rules are listed once in `SET_RULES` and report into one `ValidationErrors`; `validate_set` never raises, and a rule that breaks is logged and makes the request fail closed with a `CallError` only when nothing else was reported.
- **Dry run**: a middleware-only `dry_run` flag validates the request and returns the current values of the touched properties without writing.
Split pool dataset validation by mode
## Problem
`pool.dataset`'s `__common_validation` served both create and update through a `mode` argument plus a handful of branches that were mode-dependent by content rather than by an explicit gate, which made it hard to see which checks actually apply to an update and impossible to re-point the update path at `zfs.resource.set` without dragging create-only logic along.
## Solution
Split it into `__create_validation` and `__update_validation`. Every `mode` gate is inlined into the side it belongs to, the `create_ancestors` / missing-parent handling and the readonly-parent check stay create-only, the SMB acltype guard and the `user_properties_update` normalisation stay update-only, and the parent lookup, dRAID checks and field-shape rejections are kept on both sides. No behaviour change on either path.
Rename zfs.resource.update to set
## Problem
The raw ZFS namespace models `zfs(8)`, whose property write verb is `set`, and the `update` name collides with the CRUD-style `pool.dataset.update` it is meant to replace. `zfs.resource.update` has never shipped on master, so there is nothing to alias.
## Solution
Rename the method, its impl, its API models, its module and rule module, and the tests to `set`, and regenerate the entitlement gate inventory. `.set` and `.destroy` are also added to the read-role resource blacklist so a read role can never be granted either write verb.
Type ZFS size properties as byte counts
## Problem
Size properties on `zfs.resource.create` and `zfs.resource.set` were typed `str | int`, so a suffixed string like "512M" slipped past the volsize shrink guard (`int(str(volsize))` raised and the rule returned) and libzfs happily shrank the zvol. The create-side headroom check had the same `except ValueError` escape, so `refreservation="1G"` skipped the 80% check entirely.
## Solution
Sizes are parsed to int bytes at the model boundary with new `ZFSSize` / `ZFSSpaceLimit` types (binary K/M/G/T/P/E suffixes with optional B or iB, a fractional value only when it is a whole number of bytes, and "none" as 0 for the space limits). `copies` is a strict 1-3 int and `refreservation` on set also takes "auto". `create_rules.size_bytes` is gone and the rules compare ints directly.
Since a cleared limit is now 0, `set_impl` translates a zero quota/refquota to the literal "none" right before libzfs, which refuses 0 for exactly those two properties. Create drops a zero quota/refquota instead, as an unset one already means no limit.
Round out zfs.resource with list, update and the dataset parity methods
## Problem
`zfs.resource` is replacing `pool.dataset`, but it had no way to change a property, its listing method was called `query` while taking a single object rather than the `(filters, options)` that name implies everywhere else, and the choices methods, the zvol blocksize recommendation and the processes family still lived only under `pool.dataset`, so retiring that namespace would have taken those capabilities with it.
## Solution
**`query` becomes `list`.** The behaviour is unchanged - a single object describing what to walk, a flat result unless `nest_results` is set - but the name no longer promises a filterable query it never was. Its primitive is `list_impl`, so the private entry point reads as the method it serves. This is a hard rename with no compatibility shim, so 25.10.x and 26.0 clients lose `zfs.resource.query`; that needs a release note.
**`update` is new.** It takes `{path, properties, user_properties, inherit}` and returns the updated entry, rather than splitting into the `set` and `inherit` that `zfs(8)` uses. A dataset edit is one save, the underlying handle already performs the property set, the user-property set and the per-property inherit in a single pass, and splitting the API would only export sequencing and partial rollback to every caller. Its property vocabulary subclasses the create model so the public/private split is inherited rather than restated, minus what ZFS cannot change after creation. It is the one public method whose role the decorator does not force, so an integration test holds `ZFS_RESOURCE_WRITE` to granting it and `ZFS_RESOURCE_READ` to being refused.
**The write primitive lives on `zfs.resource`.** `update_impl` takes untyped keyword arguments, since internal callers legitimately write property names outside the public vocabulary, and like `create_impl` and `destroy_impl` it refuses snapshot paths and, unless the caller passes `bypass`, protected paths - a private method is still reachable over the wire, and every system dataset, apps and pool-import writer now funnels through this one call. `pool.dataset.update_impl` is a shim that unpacks its existing argument dict onto the primitive with `bypass` set, since those writers reach it that way; the dict stays intact as the HA wire format it is and its callers are untouched. `zfs.tier` calls the primitive directly through a typed call, also with `bypass`, because it is the only way to re-tier an internal dataset once tiering owns `special_small_blocks`, and moves onto the `ZFS_RESOURCE_*` roles that match its namespace - a custom privilege built on `DATASET_*` alone therefore loses `zfs.tier`, which also needs a release note. `pool.dataset.update` keeps its own policy layer and is not shimmed onto the new method; dedup licensing, the acltype-versus-SMB-share block and the LUN resync side effects belong above the raw ZFS layer, not on it.
**`rename` and `promote` become public**, typed and single-object, held by `ZFS_RESOURCE_WRITE`, with the private entry points renamed `rename_impl` and `promote_impl`. `rename` requires `force` as the same acknowledgement `pool.dataset.rename` requires and drops `recursive`, which a dataset rename can never be; the message for that moved to the `pool.dataset.rename` shim so the caller-visible error is unchanged. `pool.snapshot.rename` is re-pointed at the snapshot implementation, which is where snapshot renames actually live - it has been dead since the rename guard landed.
**The read-parity methods move** to `zfs.resource` behind one-line `pool.dataset` shims that keep their existing models and roles, with the pure parts split into modules that need no service or filesystem access.
**Fixes carried along.** Normalising sources no longer dereferences a null `properties`; the overlapping-path check now runs whenever the walk descends rather than only when `get_children` is set, and names the option that actually triggered it; `max_depth` is bounded below at zero; and nesting no longer raises when a parent's `children` is absent because the walk never descended into it.