RTPG support

Implementing support of the Report Target Port Groups command.

Tested on Ubuntu against Pure Storage Flash Array
using designated unit tests and new iscsi-rtpg utility

./iscsi-rtpg  -i iqn.2005-03.org.open-iscsi:6feb2db21ea iscsi://192.168.1.12/iqn.2010-06.com.purestorage:flasharray.4e8d52d82e4b2c0f/1
RTPG retrieved 2 groups
Group 0x0000: preferred 0, format 0x00, ALUA state ACTIVE-OPTIMIZED,flags 0x8f, status code 0x02, port count 65
Ports: [ 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c
0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67
0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70 0x71]
Group 0x0001: preferred 0, format 0x00, ALUA state ACTIVE-OPTIMIZED,flags 0x8f, status code 0x02, port count 65
Ports: [ 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a
0x7b 0x7c 0x7d 0x7e 0x7f 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 0x90 0x91 0x92 0x93 0x94 0x95
0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 0xa0 0xa1 0xa2]
This commit is contained in:
Anatoliy Glagolev
2025-07-01 15:34:37 -06:00
parent e7c44b802f
commit 19d05ab7a7
12 changed files with 664 additions and 1 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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 \

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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 },

View File

@@ -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);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <CUnit/CUnit.h>
#include "iscsi.h"
#include "scsi-lowlevel.h"
#include "iscsi-support.h"
#include "iscsi-test-cu.h"
#include <stdio.h>
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;
};

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <CUnit/CUnit.h>
#include "iscsi.h"
#include "scsi-lowlevel.h"
#include "iscsi-support.h"
#include "iscsi-test-cu.h"
#include <stdio.h>
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;
};

View File

@@ -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

222
utils/iscsi-rtpg.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <inttypes.h>
#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", full_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] <iscsi-url>\n");
}
void print_help(void)
{
fprintf(stderr, "Usage: iscsi-rtpg [OPTION...] <iscsi-url>\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, "<host> 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;
}