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;
+}
+