nvmet: fix Reservation Register Replace for unregistered host with IEKEY

When a host sends a Reservation Register command with RREGA=Replace
and IEKEY=1 without being previously registered, nvmet returns
Reservation Conflict.

The NVMe specification states:

  "A host may replace its reservation key without regard to its
   registration status or current reservation key value by setting
   the Ignore Existing Key (IEKEY) bit to '1' in the Reservation
   Register command."

Fix nvmet_pr_replace() to add a new registrant when the host is not
found in the registrant list and IEKEY is set with a non-zero NRKEY.
If IEKEY is set but NRKEY is zero, return Invalid Field since there
is no valid reservation key to register.

Tested with nvme-cli against nvmet-tcp:

  # no prior registration
  nvme resv-register /dev/nvmeXn1 -n 1 --rrega=2 --iekey --nrkey=0x9999

  Before: RESERVATION_CONFLICT (0x4083)
  After:  success, registrant created with rkey 0x9999

Fixes: 5a47c2080a ("nvmet: support reservation feature")
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Guixin Liu <kanie@linux.alibaba.com>
Signed-off-by: Zhengrong Li <zhengrong_li@linux.alibaba.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>
This commit is contained in:
Zhengrong Li 2026-07-28 16:26:01 +08:00 committed by Keith Busch
parent ba98d6796d
commit 0a96b9e440

View File

@ -355,9 +355,15 @@ static u16 nvmet_pr_replace(struct nvmet_req *req,
u16 status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR;
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmet_pr *pr = &req->ns->pr;
struct nvmet_pr_registrant *reg;
struct nvmet_pr_registrant *reg, *new = NULL;
u64 nrkey = le64_to_cpu(d->nrkey);
if (ignore_key && nrkey) {
new = kzalloc_obj(*new);
if (!new)
return NVME_SC_INTERNAL;
}
down(&pr->pr_sem);
list_for_each_entry_rcu(reg, &pr->registrant_list, entry) {
if (uuid_equal(&reg->hostid, &ctrl->hostid)) {
@ -365,9 +371,26 @@ static u16 nvmet_pr_replace(struct nvmet_req *req,
status = nvmet_pr_update_reg_attr(pr, reg,
nvmet_pr_update_reg_rkey,
&nrkey);
break;
goto free_data;
}
}
if (ignore_key) {
if (!nrkey) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto free_data;
}
INIT_LIST_HEAD(&new->entry);
new->rkey = nrkey;
uuid_copy(&new->hostid, &ctrl->hostid);
list_add_tail_rcu(&new->entry, &pr->registrant_list);
status = NVME_SC_SUCCESS;
goto out;
}
free_data:
kfree(new);
out:
up(&pr->pr_sem);
return status;
}