From c2e71d537f8784a81176043d7dc28879e62178d8 Mon Sep 17 00:00:00 2001
From: David Disseldorp <ddiss@suse.de>
Date: Fri, 4 Sep 2015 01:23:58 +0200
Subject: [PATCH] target/rbd: implement SCSI2 reservation handling
Patch-mainline: Not yet, SES2 clustered LIO/RBD
References: fate#318836
Piggy-back on existing SCSI3 persistent reservation state to track SCSI2
RESERVE/RELEASE reservations.
Signed-off-by: David Disseldorp <ddiss@suse.de>
---
drivers/target/target_core_rbd.c | 273 +++++++++++++++++++++++++++++++++++++++
1 file changed, 273 insertions(+)
--- a/drivers/target/target_core_rbd.c
+++ b/drivers/target/target_core_rbd.c
@@ -2584,6 +2584,271 @@ tcm_rbd_execute_pr_register_and_move(str
}
static sense_reason_t
+tcm_rbd_execute_pr_scsi2_check_scsi3_conflict(struct tcm_rbd_pr_info *pr_info,
+ char *it_nexus)
+{
+ struct tcm_rbd_pr_rsv *rsv = pr_info->rsv;
+
+ if (rsv) {
+ struct tcm_rbd_pr_reg *reg;
+
+ /*
+ * spc4r17
+ * 5.12.3 Exceptions to SPC-2 RESERVE and RELEASE behavior
+ * A RESERVE(6) command or RESERVE(10) command shall complete
+ * with GOOD status, but no reservation shall be established and
+ * the persistent reservation shall not be changed, if the
+ * command is received from:
+ * a) an I_T nexus that is a persistent reservation holder; or
+ * b) an I_T nexus that is registered if a registrants only or
+ * all registrants type persistent reservation is present.
+ */
+ list_for_each_entry(reg, &pr_info->regs, regs_node) {
+ if (strncmp(reg->it_nexus, it_nexus,
+ ARRAY_SIZE(reg->it_nexus))) {
+ continue;
+ }
+ dout("SCSI2 RESERVE from PR registrant: %s\n",
+ it_nexus);
+ /* ALLREG types checked by tcm_rbd_is_rsv_holder() */
+ if (tcm_rbd_is_rsv_holder(rsv, reg, NULL)
+ || ((rsv->type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY)
+ || (rsv->type == PR_TYPE_EXCLUSIVE_ACCESS_REGONLY))) {
+ return 1;
+ }
+ }
+ }
+
+ if (pr_info->num_regs > 0) {
+ /*
+ * Following spc2r20 5.5.1 Reservations overview:
+ *
+ * If a logical unit has executed a PERSISTENT RESERVE OUT
+ * command with the REGISTER or the REGISTER AND IGNORE
+ * EXISTING KEY service action and is still registered by any
+ * initiator, all RESERVE commands and all RELEASE commands
+ * regardless of initiator shall conflict and shall terminate
+ * with a RESERVATION CONFLICT status.
+ */
+ pr_err("Received legacy SPC-2 RESERVE/RELEASE"
+ " while active SPC-3 registrations exist,"
+ " returning RESERVATION_CONFLICT\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static sense_reason_t
+tcm_rbd_execute_pr_scsi2_reserve(struct se_cmd *cmd)
+{
+ struct se_device *dev = cmd->se_dev;
+ struct tcm_rbd_dev *tcm_rbd_dev = TCM_RBD_DEV(dev);
+ char nexus_buf[TCM_RBD_PR_IT_NEXUS_MAXLEN];
+ struct tcm_rbd_pr_info *pr_info;
+ char *pr_xattr;
+ int pr_xattr_len;
+ int rc;
+ sense_reason_t ret;
+ int retries = 0;
+
+ if (!cmd->se_sess || !cmd->se_lun) {
+ pr_err("SCSI2 RESERVE: se_sess || struct se_lun is NULL!\n");
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ }
+
+ rc = tcm_rbd_gen_it_nexus(cmd->se_sess, nexus_buf,
+ ARRAY_SIZE(nexus_buf));
+ if (rc < 0)
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+
+retry:
+ pr_info = NULL;
+ pr_xattr = NULL;
+ pr_xattr_len = 0;
+ rc = tcm_rbd_pr_info_get(tcm_rbd_dev, &pr_info, &pr_xattr,
+ &pr_xattr_len);
+ if ((rc == -ENODATA) && (retries == 0)) {
+ pr_warn("PR info not present, initializing\n");
+ rc = tcm_rbd_pr_info_init(tcm_rbd_dev, &pr_info, &pr_xattr,
+ &pr_xattr_len);
+ }
+ if (rc < 0) {
+ pr_err("failed to obtain PR info\n");
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ }
+
+ rc = tcm_rbd_execute_pr_scsi2_check_scsi3_conflict(pr_info, nexus_buf);
+ if (rc == -EBUSY) {
+ ret = TCM_RESERVATION_CONFLICT;
+ goto err_info_free;
+ } else if (rc < 0) {
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ goto err_info_free;
+ } else if (rc == 1) {
+ /* return GOOD without processing request */
+ goto done;
+ }
+
+ if (pr_info->scsi2_rsv) {
+ if (strncmp(pr_info->scsi2_rsv->it_nexus,
+ nexus_buf, ARRAY_SIZE(nexus_buf))) {
+ dout("SCSI2 reservation conflict: held by %s\n",
+ pr_info->scsi2_rsv->it_nexus);
+ ret = TCM_RESERVATION_CONFLICT;
+ goto err_info_free;
+ }
+ dout("SCSI2 reservation already held by %s\n",
+ nexus_buf);
+ goto done;
+ }
+
+ dout("new SCSI2 reservation\n");
+ ret = tcm_rbd_pr_info_scsi2_rsv_set(pr_info, nexus_buf);
+ if (ret < 0) {
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ goto err_info_free;
+ }
+
+ rc = tcm_rbd_pr_info_replace(tcm_rbd_dev, pr_xattr, pr_xattr_len,
+ pr_info);
+ if (rc == -ECANCELED) {
+ pr_warn("atomic PR info update failed due to parallel "
+ "change, expected(%d) %s. Retrying...\n",
+ pr_xattr_len, pr_xattr);
+ retries++;
+ if (retries <= TCM_RBD_PR_REG_MAX_RETRIES) {
+ tcm_rbd_pr_info_free(pr_info);
+ kfree(pr_xattr);
+ goto retry;
+ }
+ }
+ if (rc < 0) {
+ pr_err("atomic PR info update failed: %d\n", rc);
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ goto err_info_free;
+ }
+
+done:
+ ret = TCM_NO_SENSE;
+err_info_free:
+ tcm_rbd_pr_info_free(pr_info);
+ kfree(pr_xattr);
+ return ret;
+}
+
+static sense_reason_t
+tcm_rbd_execute_pr_scsi2_release(struct se_cmd *cmd)
+{
+ struct se_device *dev = cmd->se_dev;
+ struct tcm_rbd_dev *tcm_rbd_dev = TCM_RBD_DEV(dev);
+ char nexus_buf[TCM_RBD_PR_IT_NEXUS_MAXLEN];
+ struct tcm_rbd_pr_info *pr_info;
+ char *pr_xattr;
+ int pr_xattr_len;
+ int rc;
+ sense_reason_t ret;
+ int retries = 0;
+
+ if (!cmd->se_sess || !cmd->se_lun) {
+ pr_err("SCSI2 RESERVE: se_sess || struct se_lun is NULL!\n");
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ }
+
+ rc = tcm_rbd_gen_it_nexus(cmd->se_sess, nexus_buf,
+ ARRAY_SIZE(nexus_buf));
+ if (rc < 0)
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+
+retry:
+ pr_info = NULL;
+ pr_xattr = NULL;
+ pr_xattr_len = 0;
+ rc = tcm_rbd_pr_info_get(tcm_rbd_dev, &pr_info, &pr_xattr,
+ &pr_xattr_len);
+ if ((rc == -ENODATA) && (retries == 0)) {
+ dout("PR info not present for SCSI2 release\n");
+ return TCM_NO_SENSE;
+ }
+ if (rc < 0) {
+ pr_err("failed to obtain PR info\n");
+ return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ }
+
+ rc = tcm_rbd_execute_pr_scsi2_check_scsi3_conflict(pr_info, nexus_buf);
+ if (rc == -EBUSY) {
+ ret = TCM_RESERVATION_CONFLICT;
+ goto err_info_free;
+ } else if (rc < 0) {
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ goto err_info_free;
+ } else if (rc == 1) {
+ /* return GOOD without processing request */
+ goto done;
+ }
+
+ if (!pr_info->scsi2_rsv || strncmp(pr_info->scsi2_rsv->it_nexus,
+ nexus_buf, ARRAY_SIZE(nexus_buf))) {
+ dout("SCSI2 release against non-matching reservation\n");
+ goto done;
+ }
+
+ tcm_rbd_pr_info_scsi2_rsv_clear(pr_info);
+
+ rc = tcm_rbd_pr_info_replace(tcm_rbd_dev, pr_xattr, pr_xattr_len,
+ pr_info);
+ if (rc == -ECANCELED) {
+ pr_warn("atomic PR info update failed due to parallel "
+ "change, expected(%d) %s. Retrying...\n",
+ pr_xattr_len, pr_xattr);
+ retries++;
+ if (retries <= TCM_RBD_PR_REG_MAX_RETRIES) {
+ tcm_rbd_pr_info_free(pr_info);
+ kfree(pr_xattr);
+ goto retry;
+ }
+ }
+ if (rc < 0) {
+ pr_err("atomic PR info update failed: %d\n", rc);
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
+ goto err_info_free;
+ }
+
+done:
+ ret = TCM_NO_SENSE;
+err_info_free:
+ tcm_rbd_pr_info_free(pr_info);
+ kfree(pr_xattr);
+ return ret;
+}
+
+static sense_reason_t
+tcm_rbd_execute_pr_check_scsi2_conflict(struct tcm_rbd_pr_info *pr_info,
+ char *it_nexus,
+ enum target_pr_check_type type)
+{
+ if (!pr_info->scsi2_rsv) {
+ dout("no SCSI2 reservation\n");
+ return TCM_NO_SENSE;
+ }
+
+ if (type == TARGET_PR_CHECK_SCSI2_ANY) {
+ dout("SCSI2 reservation conflict: %s with ANY\n",
+ it_nexus);
+ return TCM_RESERVATION_CONFLICT;
+ }
+
+ if (strncmp(pr_info->scsi2_rsv->it_nexus, it_nexus,
+ ARRAY_SIZE(pr_info->scsi2_rsv->it_nexus))) {
+ dout("SCSI2 reservation conflict: %s with %s holder\n",
+ it_nexus, pr_info->scsi2_rsv->it_nexus);
+ return TCM_RESERVATION_CONFLICT;
+ }
+
+ return TCM_NO_SENSE;
+}
+
+static sense_reason_t
tcm_rbd_execute_pr_check_scsi3_conflict(struct se_cmd *cmd,
struct tcm_rbd_pr_info *pr_info,
char *it_nexus)
@@ -2667,6 +2932,12 @@ tcm_rbd_execute_pr_check_conflict(struct
goto out_info_free;
}
+ ret = tcm_rbd_execute_pr_check_scsi2_conflict(pr_info, nexus_buf, type);
+ if (ret || (type == TARGET_PR_CHECK_SCSI2_ANY)) {
+ /* SCSI2 conflict/failure, or caller only interested in SCSI2 */
+ goto out_info_free;
+ }
+
ret = tcm_rbd_execute_pr_check_scsi3_conflict(cmd, pr_info, nexus_buf);
if (ret)
goto out_info_free;
@@ -2679,6 +2950,8 @@ out_info_free:
static struct target_pr_ops tcm_rbd_pr_ops = {
.check_conflict = tcm_rbd_execute_pr_check_conflict,
+ .scsi2_reserve = tcm_rbd_execute_pr_scsi2_reserve,
+ .scsi2_release = tcm_rbd_execute_pr_scsi2_release,
.pr_read_keys = tcm_rbd_execute_pr_read_keys,
.pr_read_reservation = tcm_rbd_execute_pr_read_reservation,