diff --git a/Makefile.am b/Makefile.am index 296d546..9ee2a38 100644 --- a/Makefile.am +++ b/Makefile.am @@ -193,6 +193,7 @@ bin_iscsi_test_cu_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/test-tool 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_get_lba_status_simple.c \ test-tool/test_get_lba_status_beyond_eol.c \ test-tool/test_inquiry_alloc_length.c \ diff --git a/lib/scsi-lowlevel.c b/lib/scsi-lowlevel.c index da31b73..be160d7 100644 --- a/lib/scsi-lowlevel.c +++ b/lib/scsi-lowlevel.c @@ -1714,7 +1714,7 @@ scsi_cdb_write16(uint64_t lba, uint32_t xferlen, int blocksize, int wrprotect, i scsi_set_uint32(&task->cdb[2], lba >> 32); scsi_set_uint32(&task->cdb[6], lba & 0xffffffff); - scsi_set_uint32(&task->cdb[10], xferlen/blocksize); + scsi_set_uint32(&task->cdb[10], xferlen / blocksize); task->cdb[14] |= (group_number & 0x1f); @@ -1802,7 +1802,7 @@ scsi_cdb_compareandwrite(uint64_t lba, uint32_t xferlen, int blocksize, int wrpr scsi_set_uint32(&task->cdb[2], lba >> 32); scsi_set_uint32(&task->cdb[6], lba & 0xffffffff); - task->cdb[13] = xferlen/blocksize/2; + task->cdb[13] = xferlen / blocksize / 2; task->cdb[14] |= (group_number & 0x1f); task->cdb_size = 16; diff --git a/test-tool/iscsi-support.c b/test-tool/iscsi-support.c index 078e6d2..bcb1a9e 100644 --- a/test-tool/iscsi-support.c +++ b/test-tool/iscsi-support.c @@ -1717,6 +1717,46 @@ testunitready_conflict(struct iscsi_context *iscsi, int lun) return 0; } +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) +{ + struct scsi_task *task; + + logging(LOG_VERBOSE, "Send COMPARE_AND_WRITE LBA:%" PRIu64 + " LEN:%d WRPROTECT:%d", + 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 command: " + "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 SUCCESS."); + 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 b50ea22..f64f89b 100644 --- a/test-tool/iscsi-support.h +++ b/test-tool/iscsi-support.h @@ -226,6 +226,7 @@ int verify_write_fails(struct iscsi_context *iscsi, int lun, unsigned char *buf) int inquiry(struct iscsi_context *iscsi, int lun, int evpd, int page_code, int maxsize, struct scsi_task **save_task); 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 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 a70f014..c94d7b5 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -60,6 +60,11 @@ int (*real_iscsi_queue_pdu)(struct iscsi_context *iscsi, struct iscsi_pdu *pdu); * list of tests and test suites * *****************************************************************/ +static CU_TestInfo tests_compareandwrite[] = { + { (char *)"Simple", test_compareandwrite_simple }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_get_lba_status[] = { { (char *)"Simple", test_get_lba_status_simple }, { (char *)"BeyondEol", test_get_lba_status_beyond_eol }, @@ -393,6 +398,8 @@ static CU_TestInfo tests_writeverify16[] = { /* SCSI protocol tests */ static CU_SuiteInfo scsi_suites[] = { + { (char *)"CompareAndWrite", test_setup, test_teardown, + tests_compareandwrite }, { (char *)"GetLBAStatus", test_setup, test_teardown, tests_get_lba_status }, { (char *)"Inquiry", test_setup, test_teardown, @@ -501,6 +508,8 @@ static CU_SuiteInfo iscsi_suites[] = { /* All tests */ static CU_SuiteInfo all_suites[] = { + { (char *)"CompareAndWrite", test_setup, test_teardown, + tests_compareandwrite }, { (char *)"GetLBAStatus", test_setup, test_teardown, tests_get_lba_status }, { (char *)"Inquiry", test_setup, test_teardown, @@ -583,6 +592,8 @@ static CU_SuiteInfo all_suites[] = { }; static CU_SuiteInfo scsi_usb_sbc_suites[] = { + { (char *)"CompareAndWrite", test_setup, test_teardown, + tests_compareandwrite }, { (char *)"GetLBAStatus", test_setup, test_teardown, tests_get_lba_status }, { (char *)"Inquiry", test_setup, test_teardown, diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index 034e09f..1745b9b 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -40,6 +40,8 @@ int test_teardown(void); int test_setup_pgr(void); int test_teardown_pgr(void); +void test_compareandwrite_simple(void); + void test_get_lba_status_simple(void); void test_get_lba_status_beyond_eol(void); diff --git a/test-tool/test_compareandwrite_simple.c b/test-tool/test_compareandwrite_simple.c new file mode 100644 index 0000000..dd155f3 --- /dev/null +++ b/test-tool/test_compareandwrite_simple.c @@ -0,0 +1,126 @@ +/* + 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_simple(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"); + 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); + + 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(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 all 'B'", 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] != 'B') { + logging(LOG_VERBOSE, "[FAILED] Data did not " + "read back as 'B'"); + CU_FAIL("Block was not written correctly"); + 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); + + 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(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 + " and verify they are all 'B'", + 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] != 'B') { + logging(LOG_VERBOSE, "[FAILED] Data did not " + "read back as 'B'"); + CU_FAIL("Block was not written correctly"); + return; + } + } + } +}