diff --git a/Makefile.am b/Makefile.am index 9ee2a38..806262b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -194,6 +194,7 @@ bin_iscsi_test_cu_LDFLAGS = -ldl -lcunit bin_iscsi_test_cu_SOURCES = test-tool/iscsi-test-cu.c \ test-tool/iscsi-support.c \ test-tool/test_compareandwrite_simple.c \ + test-tool/test_compareandwrite_miscompare.c \ test-tool/test_get_lba_status_simple.c \ test-tool/test_get_lba_status_beyond_eol.c \ test-tool/test_inquiry_alloc_length.c \ diff --git a/include/scsi-lowlevel.h b/include/scsi-lowlevel.h index b9a0deb..44bf80a 100644 --- a/include/scsi-lowlevel.h +++ b/include/scsi-lowlevel.h @@ -152,6 +152,7 @@ EXTERN const char *scsi_sense_key_str(int key); #define SCSI_SENSE_ASCQ_WRITE_AFTER_SANITIZE_REQUIRED 0x1115 #define SCSI_SENSE_ASCQ_PARAMETER_LIST_LENGTH_ERROR 0x1a00 #define SCSI_SENSE_ASCQ_MISCOMPARE_DURING_VERIFY 0x1d00 +#define SCSI_SENSE_ASCQ_MISCOMPARE_VERIFY_OF_UNMAPPED_LBA 0x1d01 #define SCSI_SENSE_ASCQ_INVALID_OPERATION_CODE 0x2000 #define SCSI_SENSE_ASCQ_LBA_OUT_OF_RANGE 0x2100 #define SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB 0x2400 diff --git a/lib/scsi-lowlevel.c b/lib/scsi-lowlevel.c index fd96157..7257d9d 100644 --- a/lib/scsi-lowlevel.c +++ b/lib/scsi-lowlevel.c @@ -215,6 +215,8 @@ scsi_sense_ascq_str(int ascq) "INTERNAL_TARGET_FAILURE"}, {SCSI_SENSE_ASCQ_MISCOMPARE_DURING_VERIFY, "MISCOMPARE_DURING_VERIFY"}, + {SCSI_SENSE_ASCQ_MISCOMPARE_VERIFY_OF_UNMAPPED_LBA, + "MISCOMPARE_VERIFY_OF_UNMAPPED_LBA"}, { SCSI_SENSE_ASCQ_MEDIUM_LOAD_OR_EJECT_FAILED, "MEDIUM_LOAD_OR_EJECT_FAILED" }, {SCSI_SENSE_ASCQ_MEDIUM_REMOVAL_PREVENTED, diff --git a/test-tool/iscsi-support.c b/test-tool/iscsi-support.c index bcb1a9e..3cccbe8 100644 --- a/test-tool/iscsi-support.c +++ b/test-tool/iscsi-support.c @@ -1757,6 +1757,58 @@ int compareandwrite(struct iscsi_context *iscsi, int lun, uint64_t lba, return 0; } +int compareandwrite_miscompare(struct iscsi_context *iscsi, int lun, + uint64_t lba, unsigned char *data, + uint32_t len, int blocksize, + int wrprotect, int dpo, + int fua, int group_number) +{ + struct scsi_task *task; + + logging(LOG_VERBOSE, "Send COMPARE_AND_WRITE LBA:%" PRIu64 + " LEN:%d WRPROTECT:%d (expecting MISCOMPARE)", + lba, len, wrprotect); + + task = iscsi_compareandwrite_sync(iscsi, lun, lba, + data, len, blocksize, + wrprotect, dpo, fua, 0, group_number); + if (task == NULL) { + logging(LOG_NORMAL, "[FAILED] Failed to send COMPARE_AND_WRITE " + "command: %s", + iscsi_get_error(iscsi)); + return -1; + } + if (task->status == SCSI_STATUS_CHECK_CONDITION + && task->sense.key == SCSI_SENSE_ILLEGAL_REQUEST + && task->sense.ascq == SCSI_SENSE_ASCQ_INVALID_OPERATION_CODE) { + logging(LOG_NORMAL, "[SKIPPED] COMPARE_AND_WRITE is not " + "implemented on target"); + scsi_free_scsi_task(task); + return -2; + } + if (task->status == SCSI_STATUS_GOOD) { + logging(LOG_NORMAL, "[FAILED] COMPARE_AND_WRITE successful " + "but should have failed with MISCOMPARE."); + scsi_free_scsi_task(task); + return -1; + } + + if (task->status != SCSI_STATUS_CHECK_CONDITION + || task->sense.key != SCSI_SENSE_MISCOMPARE + || task->sense.ascq != SCSI_SENSE_ASCQ_MISCOMPARE_DURING_VERIFY) { + logging(LOG_NORMAL, "[FAILED] COMPARE_AND_WRITE failed with " + "the wrong sense code. Should have failed with " + "MISCOMPARE/MISCOMPARE_DURING_VERIFY but failed with " + "sense:%s", iscsi_get_error(iscsi)); + scsi_free_scsi_task(task); + return -1; + } + + scsi_free_scsi_task(task); + logging(LOG_VERBOSE, "[OK] COMPARE_AND_WRITE returned MISCOMPARE."); + return 0; +} + struct scsi_task *get_lba_status_task(struct iscsi_context *iscsi, int lun, uint64_t lba, uint32_t len) { struct scsi_task *task; diff --git a/test-tool/iscsi-support.h b/test-tool/iscsi-support.h index f64f89b..ec88220 100644 --- a/test-tool/iscsi-support.h +++ b/test-tool/iscsi-support.h @@ -227,6 +227,7 @@ int inquiry(struct iscsi_context *iscsi, int lun, int evpd, int page_code, int m int inquiry_invalidfieldincdb(struct iscsi_context *iscsi, int lun, int evpd, int page_code, int maxsize); struct scsi_task *get_lba_status_task(struct iscsi_context *iscsi, int lun, uint64_t lba, uint32_t len); int compareandwrite(struct iscsi_context *iscsi, int lun, uint64_t lba, unsigned char *data, uint32_t len, int blocksize, int wrprotect, int dpo, int fua, int group_number); +int compareandwrite_miscompare(struct iscsi_context *iscsi, int lun, uint64_t lba, unsigned char *data, uint32_t len, int blocksize, int wrprotect, int dpo, int fua, int group_number); int get_lba_status(struct iscsi_context *iscsi, int lun, uint64_t lba, uint32_t len); int get_lba_status_lbaoutofrange(struct iscsi_context *iscsi, int lun, uint64_t lba, uint32_t len); int get_lba_status_nomedium(struct iscsi_context *iscsi, int lun, uint64_t lba, uint32_t len); diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index c94d7b5..84298ad 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -62,6 +62,7 @@ int (*real_iscsi_queue_pdu)(struct iscsi_context *iscsi, struct iscsi_pdu *pdu); *****************************************************************/ static CU_TestInfo tests_compareandwrite[] = { { (char *)"Simple", test_compareandwrite_simple }, + { (char *)"Miscompare", test_compareandwrite_miscompare }, CU_TEST_INFO_NULL }; diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index 1745b9b..8d93c76 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -41,6 +41,7 @@ int test_setup_pgr(void); int test_teardown_pgr(void); void test_compareandwrite_simple(void); +void test_compareandwrite_miscompare(void); void test_get_lba_status_simple(void); void test_get_lba_status_beyond_eol(void); diff --git a/test-tool/test_compareandwrite_miscompare.c b/test-tool/test_compareandwrite_miscompare.c new file mode 100644 index 0000000..f73ee56 --- /dev/null +++ b/test-tool/test_compareandwrite_miscompare.c @@ -0,0 +1,136 @@ +/* + Copyright (C) 2013 Ronnie Sahlberg + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include +#include +#include + +#include + +#include "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" + + +void +test_compareandwrite_miscompare(void) +{ + int i, ret; + unsigned j; + unsigned char *buf = alloca(2 * 256 * block_size); + + CHECK_FOR_DATALOSS; + CHECK_FOR_SBC; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test COMPARE_AND_WRITE of 1-256 blocks at the " + "start of the LUN. One Byte miscompare in the final block."); + for (i = 1; i < 256; i++) { + logging(LOG_VERBOSE, "Write %d blocks of 'A' at LBA:0", i); + memset(buf, 'A', 2 * i * block_size); + if (maximum_transfer_length && maximum_transfer_length < i) { + break; + } + ret = write16(iscsic, tgt_lun, 0, i * block_size, + block_size, 0, 0, 0, 0, 0, buf); + if (ret == -2) { + logging(LOG_NORMAL, "[SKIPPED] WRITE16 is not implemented."); + CU_PASS("WRITE16 is not implemented."); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + + logging(LOG_VERBOSE, "Change byte 27 from the end to 'C' so that it does not match."); + buf[i * block_size - 27] = 'C'; + + + memset(buf + i * block_size, 'B', i * block_size); + + logging(LOG_VERBOSE, "Overwrite %d blocks with 'B' " + "at LBA:0 (if they all contain 'A')", i); + ret = compareandwrite_miscompare(iscsic, tgt_lun, 0, + buf, 2 * i * block_size, block_size, 0, 0, 0, 0); + if (ret == -2) { + CU_PASS("[SKIPPED] Target does not support " + "COMPARE_AND_WRITE. Skipping test"); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, "Read %d blocks at LBA:0 and verify " + "they are still unchanged as 'A'", i); + ret = read16(iscsic, tgt_lun, 0, i * block_size, + block_size, 0, 0, 0, 0, 0, buf); + CU_ASSERT_EQUAL(ret, 0); + + for (j = 0; j < i * block_size; j++) { + if (buf[j] != 'A') { + logging(LOG_VERBOSE, "[FAILED] Data changed " + "eventhough there was a miscompare"); + CU_FAIL("Block was written to"); + return; + } + } + } + + + logging(LOG_VERBOSE, "Test COMPARE_AND_WRITE of 1-256 blocks at the " + "end of the LUN"); + for (i = 1; i < 256; i++) { + logging(LOG_VERBOSE, "Write %d blocks of 'A' at LBA:%" PRIu64, + i, num_blocks - i); + memset(buf, 'A', 2 * i * block_size); + if (maximum_transfer_length && maximum_transfer_length < i) { + break; + } + ret = write16(iscsic, tgt_lun, num_blocks - i, i * block_size, + block_size, 0, 0, 0, 0, 0, buf); + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, "Change byte 27 from the end to 'C' so that it does not match."); + buf[i * block_size - 27] = 'C'; + + + memset(buf + i * block_size, 'B', i * block_size); + + logging(LOG_VERBOSE, "Overwrite %d blocks with 'B' " + "at LBA:%" PRIu64 " (if they all contain 'A')", + i, num_blocks - i); + ret = compareandwrite_miscompare(iscsic, tgt_lun, + num_blocks - i, + buf, 2 * i * block_size, block_size, 0, 0, 0, 0); + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, "Read %d blocks at LBA:%" PRIu64 + "they are still unchanged as 'A'", + i, num_blocks - i); + ret = read16(iscsic, tgt_lun, num_blocks - i, i * block_size, + block_size, 0, 0, 0, 0, 0, buf); + CU_ASSERT_EQUAL(ret, 0); + + for (j = 0; j < i * block_size; j++) { + if (buf[j] != 'A') { + logging(LOG_VERBOSE, "[FAILED] Data changed " + "eventhough there was a miscompare"); + CU_FAIL("Block was written to"); + return; + } + } + } +}