diff --git a/include/scsi-lowlevel.h b/include/scsi-lowlevel.h index a7144f1..adb1be1 100644 --- a/include/scsi-lowlevel.h +++ b/include/scsi-lowlevel.h @@ -945,6 +945,22 @@ struct scsi_persistent_reserve_in_read_reservation { unsigned char pr_type; }; +enum scsi_persistent_reservation_type_mask { + SCSI_PR_TYPE_MASK_EX_AC_AR = (1 << 0), + SCSI_PR_TYPE_MASK_WR_EX = (1 << 9), + SCSI_PR_TYPE_MASK_EX_AC = (1 << 11), + SCSI_PR_TYPE_MASK_WR_EX_RO = (1 << 13), + SCSI_PR_TYPE_MASK_EX_AC_RO = (1 << 14), + SCSI_PR_TYPE_MASK_WR_EX_AR = (1 << 15), + + SCSI_PR_TYPE_MASK_ALL = (SCSI_PR_TYPE_MASK_EX_AC_AR + | SCSI_PR_TYPE_MASK_WR_EX + | SCSI_PR_TYPE_MASK_EX_AC + | SCSI_PR_TYPE_MASK_WR_EX_RO + | SCSI_PR_TYPE_MASK_EX_AC_RO + | SCSI_PR_TYPE_MASK_WR_EX_AR) +}; + struct scsi_persistent_reserve_in_report_capabilities { uint16_t length; uint8_t crh; diff --git a/lib/scsi-lowlevel.c b/lib/scsi-lowlevel.c index f39b0fb..42d2106 100644 --- a/lib/scsi-lowlevel.c +++ b/lib/scsi-lowlevel.c @@ -947,7 +947,7 @@ scsi_persistentreservein_datain_unmarshall(struct scsi_task *task) rc->atp_c = !!(task_get_uint8(task, 2) & 0x04); rc->ptpl_c = !!(task_get_uint8(task, 2) & 0x01); rc->tmv = !!(task_get_uint8(task, 3) & 0x80); - rc->allow_commands = task_get_uint8(task, 3) >> 4; + rc->allow_commands = (task_get_uint8(task, 3) & 0x70) >> 4; rc->persistent_reservation_type_mask = task_get_uint16(task, 4); return rc; @@ -3396,7 +3396,7 @@ scsi_datain_getfullsize(struct scsi_task *task) case SCSI_OPCODE_INQUIRY: return scsi_inquiry_datain_getfullsize(task); case SCSI_OPCODE_MODESENSE6: - return scsi_modesense_datain_getfullsize(task, 1); + return scsi_modesense_datain_getfullsize(task, 1); case SCSI_OPCODE_READCAPACITY10: return scsi_readcapacity10_datain_getfullsize(task); case SCSI_OPCODE_SYNCHRONIZECACHE10: @@ -3425,7 +3425,7 @@ scsi_datain_unmarshall(struct scsi_task *task) case SCSI_OPCODE_MODESENSE6: return scsi_modesense_datain_unmarshall(task, 1); case SCSI_OPCODE_MODESENSE10: - return scsi_modesense_datain_unmarshall(task, 0); + return scsi_modesense_datain_unmarshall(task, 0); case SCSI_OPCODE_READCAPACITY10: return scsi_readcapacity10_datain_unmarshall(task); case SCSI_OPCODE_READTOC: diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index 8b2e2e0..f026a60 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -69,11 +69,13 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_preventallow_2_itnexuses.c \ test_prin_read_keys_simple.c \ test_prin_serviceaction_range.c \ + test_prin_report_caps.c \ test_prout_register_simple.c \ test_prout_reserve_simple.c \ test_prout_reserve_access.c \ test_prout_reserve_ownership.c \ test_prout_clear_simple.c \ + test_prout_preempt.c \ test_read6_simple.c \ test_read6_beyond_eol.c \ test_read10_simple.c \ diff --git a/test-tool/iscsi-support.c b/test-tool/iscsi-support.c index ce11612..97fa594 100644 --- a/test-tool/iscsi-support.c +++ b/test-tool/iscsi-support.c @@ -33,6 +33,7 @@ #include #include #include +#include #ifdef HAVE_SG_IO #include @@ -1022,6 +1023,58 @@ prout_clear(struct scsi_device *sdev, unsigned long long key) return ret; } +int +prout_preempt(struct scsi_device *sdev, + unsigned long long sark, unsigned long long rk, + enum scsi_persistent_out_type pr_type) +{ + struct scsi_persistent_reserve_out_basic poc; + struct scsi_task *task; + int ret = 0; + + /* reserve the target using specified reservation type */ + logging(LOG_VERBOSE, + "Send PROUT/PREEMPT to preempt reservation and/or " + "registration"); + + if (!data_loss) { + printf("--dataloss flag is not set in. Skipping PROUT\n"); + return -1; + } + + memset(&poc, 0, sizeof (poc)); + poc.reservation_key = rk; + poc.service_action_reservation_key = sark; + task = scsi_cdb_persistent_reserve_out( + SCSI_PERSISTENT_RESERVE_PREEMPT, + SCSI_PERSISTENT_RESERVE_SCOPE_LU, + pr_type, &poc); + assert(task != NULL); + + task = send_scsi_command(sdev, task, NULL); + if (task == NULL) { + logging(LOG_NORMAL, + "[FAILED] Failed to send PROUT command: %s", + iscsi_get_error(sdev->iscsi_ctx)); + return -1; + } + if (status_is_invalid_opcode(task)) { + scsi_free_scsi_task(task); + logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented."); + return -2; + } + + if (task->status != SCSI_STATUS_GOOD) { + logging(LOG_NORMAL, + "[FAILED] PROUT command: failed with sense. %s", + iscsi_get_error(sdev->iscsi_ctx)); + ret = -1; + } + + scsi_free_scsi_task(task); + return ret; +} + int prin_verify_reserved_as(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type) @@ -1155,6 +1208,52 @@ prin_verify_not_reserved(struct scsi_device *sdev) return ret; } +int +prin_report_caps(struct scsi_device *sdev, struct scsi_task **tp, + struct scsi_persistent_reserve_in_report_capabilities **_rcaps) +{ + const int buf_sz = 16384; + struct scsi_persistent_reserve_in_report_capabilities *rcaps = NULL; + + logging(LOG_VERBOSE, "Send PRIN/REPORT_CAPABILITIES"); + + *tp = scsi_cdb_persistent_reserve_in( + SCSI_PERSISTENT_RESERVE_REPORT_CAPABILITIES, + buf_sz); + assert(*tp != NULL); + + *tp = send_scsi_command(sdev, *tp, NULL); + if (*tp == NULL) { + logging(LOG_NORMAL, + "[FAILED] Failed to send PRIN command: %s", + iscsi_get_error(sdev->iscsi_ctx)); + return -1; + } + if (status_is_invalid_opcode(*tp)) { + logging(LOG_NORMAL, + "[SKIPPED] PERSISTENT RESERVE IN is not implemented."); + return -2; + } + if ((*tp)->status != SCSI_STATUS_GOOD) { + logging(LOG_NORMAL, + "[FAILED] PRIN command: failed with sense. %s", + iscsi_get_error(sdev->iscsi_ctx)); + return -1; + } + + rcaps = scsi_datain_unmarshall(*tp); + if (rcaps == NULL) { + logging(LOG_NORMAL, + "[FAIL] failed to unmarshall PRIN/REPORT_CAPABILITIES " + "data. %s", iscsi_get_error(sdev->iscsi_ctx)); + return -1; + } + if (_rcaps != NULL) + *_rcaps = rcaps; + + return 0; +} + int verify_read_works(struct scsi_device *sdev, unsigned char *buf) { @@ -2845,3 +2944,37 @@ int receive_copy_results(struct scsi_device *sdev, enum scsi_copy_results_sa sa, return ret; } + +#define TEST_ISCSI_TUR_MAX_RETRIES 5 + +int +test_iscsi_tur_until_good(struct scsi_device *iscsi_sd, int *num_uas) +{ + int num_turs; + + if (iscsi_sd->iscsi_ctx == NULL) { + logging(LOG_NORMAL, "invalid sd for tur_until_good"); + return -EINVAL; + } + + *num_uas = 0; + for (num_turs = 0; num_turs < TEST_ISCSI_TUR_MAX_RETRIES; num_turs++) { + struct scsi_task *tsk; + tsk = iscsi_testunitready_sync(iscsi_sd->iscsi_ctx, + iscsi_sd->iscsi_lun); + if (tsk->status == SCSI_STATUS_GOOD) { + logging(LOG_VERBOSE, "TUR good after %d retries", + num_turs); + return 0; + } else if ((tsk->status == SCSI_STATUS_CHECK_CONDITION) + && (tsk->sense.key == SCSI_SENSE_UNIT_ATTENTION)) { + logging(LOG_VERBOSE, "Got UA for TUR"); + (*num_uas)++; + } else { + logging(LOG_NORMAL, "unexpected non-UA failure: %d,%d", + tsk->status, tsk->sense.key); + } + } + + return -ETIMEDOUT; +} diff --git a/test-tool/iscsi-support.h b/test-tool/iscsi-support.h index 4c43b14..df7fb11 100644 --- a/test-tool/iscsi-support.h +++ b/test-tool/iscsi-support.h @@ -274,9 +274,14 @@ int prout_reserve(struct scsi_device *sdev, int prout_release(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type); int prout_clear(struct scsi_device *sdev, unsigned long long key); +int prout_preempt(struct scsi_device *sdev, + unsigned long long sark, unsigned long long rk, + enum scsi_persistent_out_type pr_type); int prin_verify_not_reserved(struct scsi_device *sdev); int prin_verify_reserved_as(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type); +int prin_report_caps(struct scsi_device *sdev, struct scsi_task **tp, + struct scsi_persistent_reserve_in_report_capabilities **_rcaps); int verify_read_works(struct scsi_device *sdev, unsigned char *buf); int verify_write_works(struct scsi_device *sdev, unsigned char *buf); int verify_read_fails(struct scsi_device *sdev, unsigned char *buf); @@ -331,4 +336,5 @@ int populate_seg_desc_hdr(unsigned char *hdr, enum ec_descr_type_code desc_type, int populate_seg_desc_b2b(unsigned char *desc, int dc, int cat, int src_index, int dst_index, int num_blks, uint64_t src_lba, uint64_t dst_lba); void populate_param_header(unsigned char *buf, int list_id, int str, int list_id_usage, int prio, int tgt_desc_len, int seg_desc_len, int inline_data_len); int receive_copy_results(struct scsi_device *sdev, enum scsi_copy_results_sa sa, int list_id, void **datap, int status, enum scsi_sense_key key, int *ascq, int num_ascq); +int test_iscsi_tur_until_good(struct scsi_device *iscsi_sd, int *num_uas); #endif /* _ISCSI_SUPPORT_H_ */ diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index 5646b74..cb34f78 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -157,6 +157,11 @@ static CU_TestInfo tests_prin_read_keys[] = { CU_TEST_INFO_NULL }; +static CU_TestInfo tests_prin_report_caps[] = { + { (char *)"Simple", test_prin_report_caps_simple }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_prout_register[] = { { (char *)"Simple", test_prout_register_simple }, CU_TEST_INFO_NULL @@ -198,6 +203,12 @@ static CU_TestInfo tests_prout_clear[] = { CU_TEST_INFO_NULL }; +static CU_TestInfo tests_prout_preempt[] = { + { (char *)"RemoveRegistration", + test_prout_preempt_rm_reg }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_prin_serviceaction_range[] = { { (char *)"Range", test_prin_serviceaction_range }, CU_TEST_INFO_NULL @@ -482,9 +493,11 @@ static libiscsi_suite_info scsi_suites[] = { { "PreventAllow", NON_PGR_FUNCS, tests_preventallow }, { "PrinReadKeys", NON_PGR_FUNCS, tests_prin_read_keys }, { "PrinServiceactionRange", NON_PGR_FUNCS, tests_prin_serviceaction_range }, + { "PrinReportCapabilities", NON_PGR_FUNCS, tests_prin_report_caps }, { "ProutRegister", NON_PGR_FUNCS, tests_prout_register }, { "ProutReserve", NON_PGR_FUNCS, tests_prout_reserve }, { "ProutClear", NON_PGR_FUNCS, tests_prout_clear }, + { "ProutPreempt", NON_PGR_FUNCS, tests_prout_preempt }, { "Read6", NON_PGR_FUNCS, tests_read6 }, { "Read10", NON_PGR_FUNCS, tests_read10 }, { "Read12", NON_PGR_FUNCS, tests_read12 }, @@ -568,9 +581,11 @@ static libiscsi_suite_info all_suites[] = { { "PrinReadKeys", NON_PGR_FUNCS, tests_prin_read_keys }, { "PrinServiceactionRange", NON_PGR_FUNCS, tests_prin_serviceaction_range }, + { "PrinReportCapabilities", NON_PGR_FUNCS, tests_prin_report_caps }, { "ProutRegister", NON_PGR_FUNCS, tests_prout_register }, { "ProutReserve", NON_PGR_FUNCS, tests_prout_reserve }, { "ProutClear", NON_PGR_FUNCS, tests_prout_clear }, + { "ProutPreempt", NON_PGR_FUNCS, tests_prout_preempt }, { "Read6", NON_PGR_FUNCS, tests_read6 }, { "Read10", NON_PGR_FUNCS, tests_read10 }, { "Read12", NON_PGR_FUNCS, tests_read12 }, diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index fa9a0c0..dc75c6e 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -114,6 +114,7 @@ void test_preventallow_2_itnexuses(void); void test_prin_read_keys_simple(void); void test_prin_serviceaction_range(void); +void test_prin_report_caps_simple(void); void test_prout_register_simple(void); void test_prout_reserve_simple(void); @@ -130,6 +131,7 @@ void test_prout_reserve_ownership_wero(void); void test_prout_reserve_ownership_eaar(void); void test_prout_reserve_ownership_wear(void); void test_prout_clear_simple(void); +void test_prout_preempt_rm_reg(void); void test_read6_simple(void); void test_read6_beyond_eol(void); diff --git a/test-tool/test_multipathio_reset.c b/test-tool/test_multipathio_reset.c index e7b78ad..f2c8da3 100644 --- a/test-tool/test_multipathio_reset.c +++ b/test-tool/test_multipathio_reset.c @@ -27,36 +27,6 @@ #include "iscsi-test-cu.h" #include "iscsi-multipath.h" -#define MPATH_MAX_TUR_RETRIES 5 - -static int -test_iscsi_tur_until_good(struct scsi_device *iscsi_sd, - int *num_uas) -{ - int num_turs; - - *num_uas = 0; - for (num_turs = 0; num_turs < MPATH_MAX_TUR_RETRIES; num_turs++) { - struct scsi_task *tsk; - tsk = iscsi_testunitready_sync(iscsi_sd->iscsi_ctx, - iscsi_sd->iscsi_lun); - if (tsk->status == SCSI_STATUS_GOOD) { - logging(LOG_VERBOSE, "TUR good after %d retries", - num_turs); - return 0; - } else if ((tsk->status == SCSI_STATUS_CHECK_CONDITION) - && (tsk->sense.key == SCSI_SENSE_UNIT_ATTENTION)) { - logging(LOG_VERBOSE, "Got UA for TUR"); - (*num_uas)++; - } else { - logging(LOG_NORMAL, "unexpected non-UA failure: %d,%d", - tsk->status, tsk->sense.key); - } - } - - return -ETIMEDOUT; -} - void test_multipathio_reset(void) { diff --git a/test-tool/test_prin_report_caps.c b/test-tool/test_prin_report_caps.c new file mode 100644 index 0000000..688c3b2 --- /dev/null +++ b/test-tool/test_prin_report_caps.c @@ -0,0 +1,114 @@ +/* + Copyright (C) 2015 David Disseldorp + + 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 "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" + +static struct test_prin_report_caps_types { + enum scsi_persistent_reservation_type_mask mask; + enum scsi_persistent_out_type op; +} report_caps_types_array[] = { + { SCSI_PR_TYPE_MASK_WR_EX_AR, + SCSI_PERSISTENT_RESERVE_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS }, + { SCSI_PR_TYPE_MASK_EX_AC_RO, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY }, + { SCSI_PR_TYPE_MASK_WR_EX_RO, + SCSI_PERSISTENT_RESERVE_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY }, + { SCSI_PR_TYPE_MASK_EX_AC, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS }, + { SCSI_PR_TYPE_MASK_WR_EX, + SCSI_PERSISTENT_RESERVE_TYPE_WRITE_EXCLUSIVE }, + { SCSI_PR_TYPE_MASK_EX_AC_AR, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS }, + { 0, 0 } +}; + +void +test_prin_report_caps_simple(void) +{ + int ret = 0; + const unsigned long long key = rand_key(); + struct scsi_task *tsk; + struct scsi_persistent_reserve_in_report_capabilities *rcaps; + struct test_prin_report_caps_types *type; + + CHECK_FOR_DATALOSS; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, + "Test Persistent Reserve In REPORT CAPABILITIES works."); + + /* register our reservation key with the target */ + ret = prout_register_and_ignore(sd, key); + if (ret == -2) { + logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented."); + CU_PASS("PERSISTENT RESERVE OUT is not implemented."); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + ret = prin_report_caps(sd, &tsk, &rcaps); + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, + "Checking PERSISTENT RESERVE IN REPORT CAPABILITIES fields."); + CU_ASSERT_EQUAL(rcaps->length, 8); + CU_ASSERT_TRUE(rcaps->allow_commands <= 5); + CU_ASSERT_EQUAL(rcaps->persistent_reservation_type_mask + & ~SCSI_PR_TYPE_MASK_ALL, 0); + + for (type = &report_caps_types_array[0]; type->mask != 0; type++) { + if (!(rcaps->persistent_reservation_type_mask & type->mask)) { + logging(LOG_NORMAL, + "PERSISTENT RESERVE op 0x%x not supported", + type->op); + continue; + } + + logging(LOG_VERBOSE, + "PERSISTENT RESERVE OUT op 0x%x supported, testing", + type->op); + + /* reserve the target */ + ret = prout_reserve(sd, key, type->op); + CU_ASSERT_EQUAL(ret, 0); + + /* verify target reservation */ + ret = prin_verify_reserved_as(sd, + pr_type_is_all_registrants(type->op) ? 0 : key, + type->op); + CU_ASSERT_EQUAL(0, ret); + + /* release the target */ + ret = prout_release(sd, key, type->op); + CU_ASSERT_EQUAL(ret, 0); + } + + scsi_free_scsi_task(tsk); + rcaps = NULL; /* freed with tsk */ + + /* drop registration */ + ret = prout_register_key(sd, 0, key); + CU_ASSERT_EQUAL(ret, 0); +} diff --git a/test-tool/test_prout_preempt.c b/test-tool/test_prout_preempt.c new file mode 100644 index 0000000..47953bb --- /dev/null +++ b/test-tool/test_prout_preempt.c @@ -0,0 +1,121 @@ +/* + Copyright (C) 2015 David Disseldorp + + 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 "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" +#include "iscsi-multipath.h" + +void +test_prout_preempt_rm_reg(void) +{ + int ret = 0; + const unsigned long long k1 = rand_key(); + const unsigned long long k2 = rand_key(); + struct scsi_device *sd2; + struct scsi_task *tsk; + uint32_t old_gen; + int num_uas; + struct scsi_persistent_reserve_in_read_keys *rk; + + CHECK_FOR_DATALOSS; + + if (sd->iscsi_ctx == NULL) { + const char *err = "[SKIPPED] This PERSISTENT RESERVE test is " + "only supported for iSCSI backends"; + logging(LOG_NORMAL, "%s", err); + CU_PASS(err); + return; + } + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test Persistent Reserve IN PREEMPT works."); + + ret = prout_register_and_ignore(sd, k1); + if (ret == -2) { + logging(LOG_NORMAL, "[SKIPPED] PERSISTEN RESERVE OUT is not implemented."); + CU_PASS("PERSISTENT RESERVE OUT is not implemented."); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + /* clear all PR state */ + ret = prout_clear(sd, k1); + CU_ASSERT_EQUAL(ret, 0); + + /* need to reregister cleared key */ + ret = prout_register_and_ignore(sd, k1); + CU_ASSERT_EQUAL(ret, 0); + + ret = mpath_sd2_get_or_clone(sd, &sd2); + CU_ASSERT_EQUAL(ret, 0); + + /* register secondary key */ + ret = prout_register_and_ignore(sd2, k2); + CU_ASSERT_EQUAL(ret, 0); + + /* confirm that k1 and k2 are registered */ + ret = prin_read_keys(sd, &tsk, &rk); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + CU_ASSERT_EQUAL(rk->num_keys, 2); + /* retain PR generation number to check for increments */ + old_gen = rk->prgeneration; + + scsi_free_scsi_task(tsk); + rk = NULL; /* freed with tsk */ + + /* use second connection to clear k1 registration */ + ret = prout_preempt(sd2, k1, k2, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS); + CU_ASSERT_EQUAL(ret, 0); + + /* clear any UAs generated by preempt */ + ret = test_iscsi_tur_until_good(sd, &num_uas); + CU_ASSERT_EQUAL(ret, 0); + ret = test_iscsi_tur_until_good(sd2, &num_uas); + CU_ASSERT_EQUAL(ret, 0); + + ret = prin_read_keys(sd, &tsk, &rk); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + CU_ASSERT_EQUAL(rk->num_keys, 1); + /* ensure preempt bumped generation number */ + CU_ASSERT_EQUAL(rk->prgeneration, old_gen + 1); + /* ensure k2 is retained */ + CU_ASSERT_EQUAL(rk->keys[0], k2); + + /* unregister k2 */ + ret = prout_register_key(sd2, 0, k2); + CU_ASSERT_EQUAL(ret, 0); + + CU_ASSERT_EQUAL(rk->num_keys, 1); + /* ensure preempt bumped generation number */ + CU_ASSERT_EQUAL(rk->prgeneration, old_gen + 1); + /* ensure k2 is retained */ + CU_ASSERT_EQUAL(rk->keys[0], k2); + + /* unregister k2 */ + ret = prout_register_key(sd2, 0, k2); + CU_ASSERT_EQUAL(ret, 0); +}