diff --git a/include/scsi-lowlevel.h b/include/scsi-lowlevel.h index fe65daf..be1348a 100644 --- a/include/scsi-lowlevel.h +++ b/include/scsi-lowlevel.h @@ -130,9 +130,22 @@ struct scsi_persistent_reserve_out_basic { }; enum scsi_maintenance_in { + SCSI_REPORT_TARGET_PORT_GROUPS = 0x0a, SCSI_REPORT_SUPPORTED_OP_CODES = 0x0c }; +enum scsi_alue_state { + SCSI_ALUA_ACTIVE_OPTIMIZED = 0x0, + SCSI_ALUA_ACTIVE_NONOPTIMIZED = 0x1, + SCSI_ALUA_STANDBY = 0x2, + SCSI_ALUA_UNAVAILABLE = 0x3, + SCSI_ALUA_LOGICAL_BLOCK_DEPENDENT = 0x4, + SCSI_ALUA_OFFLINE = 0xe, + SCSI_ALUA_TRANSITIONING = 0xf +}; + +const char *scsi_alua_state_to_str(uint8_t state); + enum scsi_op_code_reporting_options { SCSI_REPORT_SUPPORTING_OPS_ALL = 0x00, SCSI_REPORT_SUPPORTING_OPCODE = 0x01, @@ -969,6 +982,32 @@ struct scsi_report_supported_op_codes_one_command { struct scsi_op_timeout_descriptor to; }; +struct scsi_target_port_group { + union { + struct { + uint8_t pref:1; + uint8_t rtpg_fmt:3; + uint8_t alua_state:4; + }; + uint8_t byte0; + }; + uint8_t flags; + uint16_t port_group; + uint8_t status_code; + uint8_t vendor_specific; + uint8_t port_count; + /* retrieved_port_count may be less than port_count when RTPG output + * was trimmed due to the buffer size */ + uint8_t retrieved_port_count; + /* points to 'retrieved_port_count' relative port ids */ + uint16_t *ports; +}; + +struct scsi_report_target_port_groups { + int num_groups; + struct scsi_target_port_group groups[0]; +}; + struct scsi_persistent_reserve_in_read_keys { uint32_t prgeneration; uint32_t additional_length; @@ -1162,6 +1201,7 @@ EXTERN struct scsi_task *scsi_cdb_read16(uint64_t lba, uint32_t xferlen, int blo EXTERN struct scsi_task *scsi_cdb_readcapacity16(void); EXTERN struct scsi_task *scsi_cdb_readdefectdata10(int req_plist, int req_glist, int defect_list_format, uint16_t alloc_len); EXTERN struct scsi_task *scsi_cdb_readdefectdata12(int req_plist, int req_glist, int defect_list_format, uint32_t address_descriptor_index, uint32_t alloc_len); +EXTERN struct scsi_task *scsi_cdb_report_target_port_groups(uint32_t alloc_len); EXTERN struct scsi_task *scsi_cdb_report_supported_opcodes(int rctd, int options, enum scsi_opcode opcode, int sa, uint32_t alloc_len); EXTERN struct scsi_task *scsi_cdb_serviceactionin16(enum scsi_service_action_in sa, uint32_t xferlen); EXTERN struct scsi_task *scsi_cdb_startstopunit(int immed, int pcm, int pc, int no_flush, int loej, int start); diff --git a/lib/libiscsi.syms.in b/lib/libiscsi.syms.in index d100968..8d8dfa6 100644 --- a/lib/libiscsi.syms.in +++ b/lib/libiscsi.syms.in @@ -210,6 +210,7 @@ iscsi_writeverify16_iov_sync iscsi_writeverify16_iov_task iscsi_writeverify16_sync iscsi_writeverify16_task +scsi_alua_state_to_str scsi_association_to_str scsi_cdb_compareandwrite scsi_cdb_extended_copy @@ -237,6 +238,7 @@ scsi_cdb_readtoc scsi_cdb_receive_copy_results scsi_cdb_release6 scsi_cdb_report_supported_opcodes +scsi_cdb_report_target_port_groups scsi_cdb_reserve6 scsi_cdb_sanitize scsi_cdb_serviceactionin16 diff --git a/lib/scsi-lowlevel.c b/lib/scsi-lowlevel.c index e23ec0d..4ffecac 100644 --- a/lib/scsi-lowlevel.c +++ b/lib/scsi-lowlevel.c @@ -1172,11 +1172,87 @@ scsi_maintenancein_datain_getfullsize(struct scsi_task *task) task_get_uint16(task, 2); } return -1; + case SCSI_REPORT_TARGET_PORT_GROUPS: + return task_get_uint32(task, 0) + 4; default: return -1; } } +static struct scsi_report_target_port_groups * +scsi_report_target_port_groups_unmarshal(struct scsi_task *task) +{ + int const group_descriptor_size = 8; + int const port_descriptor_size = 4; + + struct scsi_report_target_port_groups *rtpg = NULL; + uint16_t *port = NULL; + int group_count, port_count, i, j, k; + + if (task->datain.size < 4) { + return NULL; + } + + port_count= 0; + group_count = 0; + for (j = 0; j < 2; ++j) { + /* 1st pass counts groups and ports, then allocates data structs to fit those; + * 2nd pass populates the allocated data structs.*/ + for (i = 4; i< task->datain.size; ) { + uint8_t current_port_count; + + if (task->datain.size - i < group_descriptor_size) { + break; + } + current_port_count = task_get_uint8(task, i + 7); + + if (j == 1) { + rtpg->groups[group_count].port_count = current_port_count; + rtpg->groups[group_count].byte0 = task_get_uint8(task, i); + rtpg->groups[group_count].flags = task_get_uint8(task, i + 1); + rtpg->groups[group_count].port_group = task_get_uint16(task, i + 2); + rtpg->groups[group_count].status_code = task_get_uint8(task, i + 5); + rtpg->groups[group_count].ports = port; + } + + i += group_descriptor_size; + for (k = 0; k < current_port_count && + i + (k + 1) * port_descriptor_size <= task->datain.size; ++k) { + if (j == 1) { + rtpg->groups[group_count].ports[k] = + task_get_uint16(task, i + k * port_descriptor_size + 2); + } + } + if (j == 1) { + rtpg->groups[group_count].retrieved_port_count = k; + port += k; + } + ++group_count; + port_count += k; + i += k * port_descriptor_size; + } + + if (j == 0) { + rtpg = scsi_malloc( + task, + sizeof(struct scsi_report_target_port_groups) + + sizeof(struct scsi_target_port_group) * group_count + + sizeof(uint16_t) * port_count); + if (rtpg == NULL) { + return NULL; + } + port = (uint16_t *)((uint8_t *)rtpg + + sizeof(struct scsi_report_target_port_groups) + + sizeof(struct scsi_target_port_group) * group_count); + rtpg->num_groups = group_count; + group_count = 0; + port_count = 0; + } + } + + return rtpg; +} + /* * maintenance_in unmarshall */ @@ -1283,11 +1359,43 @@ scsi_maintenancein_datain_unmarshall(struct scsi_task *task) } return rsoc_one; } + case SCSI_REPORT_TARGET_PORT_GROUPS: + return scsi_report_target_port_groups_unmarshal(task); }; return NULL; } +/* + * MAINTENANCE In / Report Target Port Groups + */ +struct scsi_task * +scsi_cdb_report_target_port_groups(uint32_t alloc_len) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + memset(task, 0, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_MAINTENANCE_IN; + task->cdb[1] = SCSI_REPORT_TARGET_PORT_GROUPS; + + scsi_set_uint32(&task->cdb[6], alloc_len); + + task->cdb_size = 12; + if (alloc_len != 0) { + task->xfer_dir = SCSI_XFER_READ; + } else { + task->xfer_dir = SCSI_XFER_NONE; + } + task->expxferlen = alloc_len; + + return task; +} + /* * MAINTENANCE In / Read Supported Op Codes */ @@ -4287,6 +4395,29 @@ scsi_designator_type_to_str(int type) return "unknown"; } +const char * +scsi_alua_state_to_str(uint8_t state) +{ + switch (state) { + case SCSI_ALUA_ACTIVE_OPTIMIZED: + return "ACTIVE-OPTIMIZED"; + case SCSI_ALUA_ACTIVE_NONOPTIMIZED: + return "ACTIVE-NONOPTIMIZED"; + case SCSI_ALUA_STANDBY: + return "STANDBY"; + case SCSI_ALUA_UNAVAILABLE: + return "UNAVAILABLE"; + case SCSI_ALUA_LOGICAL_BLOCK_DEPENDENT: + return "BLOCK-DEPENDENT"; + case SCSI_ALUA_OFFLINE: + return "OFFLINE"; + case SCSI_ALUA_TRANSITIONING: + return "TRANSITIONING"; + } + + return "unknown"; +} + void scsi_set_task_private_ptr(struct scsi_task *task, void *ptr) { diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index a39bf3e..ad3f261 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -125,6 +125,8 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_reserve6_target_warm_reset.c \ test_reserve6_target_cold_reset.c \ test_reserve6_lun_reset.c \ + test_rtpg_alloc_length.c \ + test_rtpg_simple.c \ test_sanitize_block_erase.c \ test_sanitize_block_erase_reserved.c \ test_sanitize_crypto_erase.c \ diff --git a/test-tool/iscsi-support.c b/test-tool/iscsi-support.c index 117017e..54bc427 100644 --- a/test-tool/iscsi-support.c +++ b/test-tool/iscsi-support.c @@ -2711,6 +2711,29 @@ inquiry(struct scsi_device *sdev, struct scsi_task **out_task, int evpd, int pag return ret; } +int rtpg(struct scsi_device *sdev, struct scsi_task **out_task, int maxsize, int status, + enum scsi_sense_key key, int *ascq, int num_ascq) +{ + struct scsi_task *task; + int ret; + + logging(LOG_VERBOSE, "Send RTPG (expecting %s) alloc_len %d", + scsi_status_str(status), maxsize); + + task = scsi_cdb_report_target_port_groups(maxsize); + assert (task != NULL); + + task = send_scsi_command(sdev, task, NULL); + + ret = check_result("RTPG", sdev, task, status, key, ascq, num_ascq); + if (out_task) { + *out_task = task; + } else if (task) { + scsi_free_scsi_task(task); + } + return ret; +} + struct scsi_command_descriptor * get_command_descriptor(int opcode, int sa) { diff --git a/test-tool/iscsi-support.h b/test-tool/iscsi-support.h index 4c78a8a..c040dae 100644 --- a/test-tool/iscsi-support.h +++ b/test-tool/iscsi-support.h @@ -890,6 +890,7 @@ int report_supported_opcodes(struct scsi_device *sdev, struct scsi_task **save_t int release6(struct scsi_device *sdev); int reserve6(struct scsi_device *sdev); int reserve6_conflict(struct scsi_device *sdev); +int rtpg(struct scsi_device *sdev, struct scsi_task **out_task, int maxsize, int status, enum scsi_sense_key key, int *ascq, int num_ascq); int sanitize(struct scsi_device *sdev, int immed, int ause, int sa, int param_len, struct iscsi_data *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq); int startstopunit(struct scsi_device *sdev, int immed, int pcm, int pc, int no_flush, int loej, int start, int status, enum scsi_sense_key key, int *ascq, int num_ascq); int synchronizecache10(struct scsi_device *sdev, uint32_t lba, int num_blocks, int sync_nv, int immed, int status, enum scsi_sense_key key, int *ascq, int num_ascq); diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index e8b5aa0..cc23782 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -93,6 +93,12 @@ static CU_TestInfo tests_inquiry[] = { CU_TEST_INFO_NULL }; +static CU_TestInfo test_rtpg[] = { + { "Simple", test_rtpg_simple }, + { "AllocLength", test_rtpg_alloc_length }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_mandatory[] = { { "MandatorySBC", test_mandatory_sbc }, CU_TEST_INFO_NULL @@ -507,6 +513,7 @@ static libiscsi_suite_info scsi_suites[] = { { "ExtendedCopy", NON_PGR_FUNCS, tests_extended_copy }, { "GetLBAStatus", NON_PGR_FUNCS, tests_get_lba_status }, { "Inquiry", NON_PGR_FUNCS, tests_inquiry }, + { "ReportTargetPortGroups", NON_PGR_FUNCS, test_rtpg }, { "Mandatory", NON_PGR_FUNCS, tests_mandatory }, { "ModeSense6", NON_PGR_FUNCS, tests_modesense6 }, { "NoMedia", NON_PGR_FUNCS, tests_nomedia }, @@ -629,6 +636,7 @@ static libiscsi_suite_info all_suites[] = { { "ExtendedCopy", NON_PGR_FUNCS, tests_extended_copy }, { "GetLBAStatus", NON_PGR_FUNCS, tests_get_lba_status }, { "Inquiry", NON_PGR_FUNCS, tests_inquiry }, + { "ReportTargetPortGroups", NON_PGR_FUNCS, test_rtpg }, { "Mandatory", NON_PGR_FUNCS, tests_mandatory }, { "ModeSense6", NON_PGR_FUNCS, tests_modesense6 }, { "NoMedia", NON_PGR_FUNCS, tests_nomedia }, diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index fd230cb..5ed7eb7 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -204,6 +204,8 @@ void test_reserve6_itnexus_loss(void); void test_reserve6_target_cold_reset(void); void test_reserve6_target_warm_reset(void); void test_reserve6_lun_reset(void); +void test_rtpg_alloc_length(void); +void test_rtpg_simple(void); void test_sanitize_block_erase(void); void test_sanitize_block_erase_reserved(void); diff --git a/test-tool/test_rtpg_alloc_length.c b/test-tool/test_rtpg_alloc_length.c new file mode 100644 index 0000000..fa3558c --- /dev/null +++ b/test-tool/test_rtpg_alloc_length.c @@ -0,0 +1,145 @@ +/* + 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 "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" + +#include + +void +test_rtpg_alloc_length(void) +{ + int ret, full_size, size, group; + struct scsi_inquiry_standard *std_inq; + struct scsi_report_target_port_groups *report; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test of the RTPG command with insufficient buffers"); + + logging(LOG_VERBOSE, "Checking if the target supports RTPG"); + ret = inquiry(sd, &task, 0, 0, 260, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + std_inq = scsi_datain_unmarshall(task); + CU_ASSERT_NOT_EQUAL(std_inq, NULL); + if (std_inq->tpgs == 0) { + logging(LOG_VERBOSE, "The target does not support RTPG. Skipping RTPG tests."); + scsi_free_scsi_task(task); + task = NULL; + return; + } + scsi_free_scsi_task(task); + task = NULL; + + logging(LOG_VERBOSE, "Retrieving 4 bytes of RTPG data"); + ret = rtpg(sd, &task, 4, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + full_size = scsi_datain_getfullsize(task); + scsi_free_scsi_task(task); + task = NULL; + logging(LOG_VERBOSE, "Retrieving all RTPG data (%d bytes)", full_size); + ret = rtpg(sd, &task, full_size, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + report = scsi_datain_unmarshall(task); + CU_ASSERT_NOT_EQUAL(report, NULL); + /* data size stays the same */ + CU_ASSERT_EQUAL(full_size, scsi_datain_getfullsize(task)); + + /* The test assumes that groups are reported in the same order for any buffer size. */ + size = 4; /* offset of the 1st target port group descriptor */ + for (group = 0; group < report->num_groups; ++group) { + const int group_descriptor_size = 8; + const int port_descriptor_size = 4; + struct scsi_report_target_port_groups *report_partial = NULL; + struct scsi_task *task_partial = NULL; + int i; + + logging(LOG_VERBOSE, "Buffer boundary cuts descriptor of group %d in half", group); + size += group_descriptor_size / 2; + ret = rtpg(sd, &task_partial, size, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + report_partial = scsi_datain_unmarshall(task_partial); + CU_ASSERT_NOT_EQUAL(report_partial, NULL); + /* cut group not unmarshalled */ + CU_ASSERT_EQUAL(group, report_partial->num_groups); + /* previous groups unmarshalled along with their ports */ + for (i = 0; i < group; ++i) { + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report_partial->groups[i].port_count); + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report->groups[i].retrieved_port_count); + } + scsi_free_scsi_task(task_partial); + task_partial = NULL; + + logging(LOG_VERBOSE, "Buffer boundary at the end of descriptor of group %d", group); + size += group_descriptor_size / 2; + ret = rtpg(sd, &task_partial, size, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + report_partial = scsi_datain_unmarshall(task_partial); + CU_ASSERT_NOT_EQUAL(report_partial, NULL); + /* group unmarshalled */ + CU_ASSERT_EQUAL(group + 1, report_partial->num_groups); + /* previous groups unmarshalled along with their ports */ + for (i = 0; i < group; ++i) { + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report_partial->groups[i].port_count); + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report->groups[i].retrieved_port_count); + } + /* no retrieved ports for the current group */ + CU_ASSERT_EQUAL(report_partial->groups[group].retrieved_port_count, 0); + CU_ASSERT_EQUAL(report_partial->groups[group].port_count, + report->groups[group].port_count); + scsi_free_scsi_task(task_partial); + task_partial = NULL; + + if (report->groups[group].port_count == 0) { + continue; + } + size += port_descriptor_size; + if (report->groups[group].port_count > 1) { + logging(LOG_VERBOSE, "Just one port of group %d fits the buffer", group); + ret = rtpg(sd, &task_partial, size, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + report_partial = scsi_datain_unmarshall(task_partial); + CU_ASSERT_NOT_EQUAL(report_partial, NULL); + /* group unmarshalled */ + CU_ASSERT_EQUAL(group + 1, report_partial->num_groups); + /* previous groups unmarshalled along with their ports */ + for (i = 0; i < group; ++i) { + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report_partial->groups[i].port_count); + CU_ASSERT_EQUAL(report_partial->groups[i].retrieved_port_count, + report->groups[i].retrieved_port_count); + } + /* 1 retrieved port for the current group */ + CU_ASSERT_EQUAL(report_partial->groups[group].retrieved_port_count, 1); + CU_ASSERT_EQUAL(report_partial->groups[group].port_count, + report->groups[group].port_count); + scsi_free_scsi_task(task_partial); + task_partial = NULL; + size += port_descriptor_size * (report->groups[group].port_count - 1); + } + } + + scsi_free_scsi_task(task); + task = NULL; +}; diff --git a/test-tool/test_rtpg_simple.c b/test-tool/test_rtpg_simple.c new file mode 100644 index 0000000..e5957be --- /dev/null +++ b/test-tool/test_rtpg_simple.c @@ -0,0 +1,87 @@ +/* + 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 "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" + +#include + +void +test_rtpg_simple(void) +{ + int ret, full_size, i, io_ready_groups; + struct scsi_inquiry_standard *std_inq; + struct scsi_report_target_port_groups *report; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test of the RTPG command"); + + logging(LOG_VERBOSE, "Checking if the target supports RTPG"); + ret = inquiry(sd, &task, 0, 0, 260, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + std_inq = scsi_datain_unmarshall(task); + CU_ASSERT_NOT_EQUAL(std_inq, NULL); + if (std_inq->tpgs == 0) { + logging(LOG_VERBOSE, "The target does not support RTPG. Skipping RTPG tests."); + scsi_free_scsi_task(task); + task = NULL; + return; + } + scsi_free_scsi_task(task); + task = NULL; + + logging(LOG_VERBOSE, "Retrieving 4 bytes of RTPG data"); + ret = rtpg(sd, &task, 4, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + full_size = scsi_datain_getfullsize(task); + scsi_free_scsi_task(task); + task = NULL; + logging(LOG_VERBOSE, "Retrieving all RTPG data (%d bytes)", full_size); + ret = rtpg(sd, &task, full_size, EXPECT_STATUS_GOOD); + CU_ASSERT_EQUAL(ret, 0); + report = scsi_datain_unmarshall(task); + CU_ASSERT_NOT_EQUAL(report, NULL); + /* data size stays the same */ + CU_ASSERT_EQUAL(full_size, scsi_datain_getfullsize(task)); + + logging(LOG_VERBOSE, "Validating %d target port groups", report->num_groups); + io_ready_groups = 0; + for (i = 0; i < report->num_groups; ++i) { + /* Valid ALUA state */ + CU_ASSERT(report->groups[i].alua_state == SCSI_ALUA_ACTIVE_OPTIMIZED || + report->groups[i].alua_state == SCSI_ALUA_ACTIVE_NONOPTIMIZED || + report->groups[i].alua_state == SCSI_ALUA_STANDBY || + report->groups[i].alua_state == SCSI_ALUA_UNAVAILABLE || + report->groups[i].alua_state == SCSI_ALUA_LOGICAL_BLOCK_DEPENDENT || + report->groups[i].alua_state == SCSI_ALUA_OFFLINE || + report->groups[i].alua_state == SCSI_ALUA_TRANSITIONING); + if (report->groups[i].alua_state == SCSI_ALUA_ACTIVE_OPTIMIZED || + report->groups[i].alua_state == SCSI_ALUA_ACTIVE_NONOPTIMIZED) { + ++io_ready_groups; + } + /* Since we retrieved full size, we get all port ids */ + CU_ASSERT_EQUAL(report->groups[i].port_count, report->groups[i].retrieved_port_count); + } + CU_ASSERT(io_ready_groups > 0); + + scsi_free_scsi_task(task); + task = NULL; +}; diff --git a/utils/Makefile.am b/utils/Makefile.am index b03f4d0..49f3ae2 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -3,7 +3,7 @@ AM_CFLAGS = $(WARN_CFLAGS) AM_LDFLAGS = -no-undefined LIBS = ../lib/libiscsi.la -bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr iscsi-discard iscsi-md5sum +bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr iscsi-discard iscsi-md5sum iscsi-rtpg if !TARGET_OS_IS_WIN32 bin_PROGRAMS += iscsi-perf iscsi-readcapacity16 endif diff --git a/utils/iscsi-rtpg.c b/utils/iscsi-rtpg.c new file mode 100644 index 0000000..0914800 --- /dev/null +++ b/utils/iscsi-rtpg.c @@ -0,0 +1,222 @@ +/* + 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 . +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_POLL_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "scsi-lowlevel.h" + +void report_tpg(const struct scsi_report_target_port_groups *rtpg) +{ + int group, port; + + printf("RTPG retrieved %d groups\n", rtpg->num_groups); + for(group = 0; group < rtpg->num_groups; ++group) { + printf("Group 0x%04x: preferred %d, format 0x%02x, ALUA state %s," + "flags 0x%02x, status code 0x%02x, port count %d\n", + rtpg->groups[group].port_group, + rtpg->groups[group].pref, + rtpg->groups[group].rtpg_fmt, + scsi_alua_state_to_str(rtpg->groups[group].alua_state), + rtpg->groups[group].flags, + rtpg->groups[group].status_code, + rtpg->groups[group].port_count); + printf("Ports: ["); + for (port = 0; port < rtpg->groups[group].retrieved_port_count; ++port) { + printf(" 0x%x", rtpg->groups[group].ports[port]); + } + printf("]\n"); + } +} + +void do_rtpg(struct iscsi_context *iscsi, int lun) +{ + struct scsi_task *task; + int alloc_size = 512, retries; + struct scsi_report_target_port_groups *rtpg; + + for (retries = 0; retries < 2; ++retries) { + int full_size; + + task = scsi_cdb_report_target_port_groups(alloc_size); + if (task == NULL) { + fprintf(stderr, "Failed to allocate CBD for RTPG (size %d)\n", alloc_size); + exit(10); + } + + task = iscsi_scsi_command_sync(iscsi, lun, task, NULL); + if (task == NULL) { + fprintf(stderr, "RTPG command failed\n"); + exit(10); + } + + full_size = scsi_datain_getfullsize(task); + if (full_size > alloc_size) { + alloc_size = full_size; + scsi_free_scsi_task(task); + continue; + } + + rtpg = scsi_datain_unmarshall(task); + if (rtpg == NULL) { + fprintf(stderr, "Failed to unmarshal RTPG data blob\n"); + exit(10); + } + + report_tpg(rtpg); + return; + } + + fprintf(stderr, + "Gave up after 2 RTPG attempts: the report did not fit in %d bytes\n", + alloc_size); + exit(10); +} + +void print_usage(void) +{ + fprintf(stderr, + "Usage: iscsi-rtpg [-?|--help] [--usage] " + "[-i|--initiator-name=iqn-name] \n"); +} + +void print_help(void) +{ + fprintf(stderr, "Usage: iscsi-rtpg [OPTION...] \n"); + fprintf(stderr, " -i, --initiator-name=iqn-name Initiatorname to use\n"); + fprintf(stderr, " -d, --debug=integer debug level (0=disabled)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Help options:\n"); + fprintf(stderr, " -?, --help Show this help message\n"); + fprintf(stderr, " --usage Display brief usage message\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "iSCSI URL format : %s\n", ISCSI_URL_SYNTAX); + fprintf(stderr, "\n"); + fprintf(stderr, " is either of:\n"); + fprintf(stderr, " \"hostname\" iscsi.example\n"); + fprintf(stderr, " \"ipv4-address\" 10.1.1.27\n"); + fprintf(stderr, " \"ipv6-address\" [fce0::1]\n"); +} + +int main(int argc, char *argv[]) +{ + struct iscsi_context *iscsi; + char *url = NULL; + struct iscsi_url *iscsi_url = NULL; + const char *initiator = "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-inq"; + int show_help = 0, show_usage = 0, debug = 0; + int c; + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"usage", no_argument, NULL, 'u'}, + {"debug", required_argument, NULL, 'd'}, + {"initiator-name", required_argument, NULL, 'i'}, + {0, 0, 0, 0} + }; + int option_index; + + while ((c = getopt_long(argc, argv, "h?ud:i:", long_options, + &option_index)) != -1) { + switch (c) { + case 'h': + case '?': + show_help = 1; + break; + case 'u': + show_usage = 1; + break; + case 'd': + debug = strtol(optarg, NULL, 0); + break; + case 'i': + initiator = optarg; + break; + default: + fprintf(stderr, "Unrecognized option '%c'\n\n", c); + print_help(); + exit(0); + } + } + + if (show_help != 0) { + print_help(); + exit(0); + } + + if (show_usage != 0) { + print_usage(); + exit(0); + } + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + fprintf(stderr, "Failed to create context\n"); + exit(10); + } + + if (debug > 0) { + iscsi_set_log_level(iscsi, debug); + iscsi_set_log_fn(iscsi, iscsi_log_to_stderr); + } + + if (argv[optind] != NULL) { + url = strdup(argv[optind]); + } + if (url == NULL) { + fprintf(stderr, "You must specify the URL\n"); + print_usage(); + exit(10); + } + iscsi_url = iscsi_parse_full_url(iscsi, url); + + free(url); + + if (iscsi_url == NULL) { + fprintf(stderr, "Failed to parse URL: %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + + if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) { + fprintf(stderr, "Login Failed. %s\n", iscsi_get_error(iscsi)); + iscsi_destroy_url(iscsi_url); + iscsi_destroy_context(iscsi); + exit(10); + } + + do_rtpg(iscsi, iscsi_url->lun); + iscsi_destroy_url(iscsi_url); + + iscsi_logout_sync(iscsi); + iscsi_destroy_context(iscsi); + return 0; +} +