When we reissue an inquiry due to the firast attempt not containing all of the data, make sure to ask for the same page once we know the proper size and ask again. Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
370 lines
8.9 KiB
C
370 lines
8.9 KiB
C
/*
|
|
iscsi test-tool multipath support
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#define _GNU_SOURCE
|
|
#include <assert.h>
|
|
#include <sys/syscall.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <poll.h>
|
|
#include <fnmatch.h>
|
|
|
|
#ifdef HAVE_SG_IO
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <scsi/sg.h>
|
|
#endif
|
|
|
|
#include "slist.h"
|
|
#include "iscsi.h"
|
|
#include "scsi-lowlevel.h"
|
|
#include "iscsi-private.h"
|
|
#include "iscsi-support.h"
|
|
#include "iscsi-multipath.h"
|
|
|
|
int mp_num_sds = 0;
|
|
struct scsi_device *mp_sds[MPATH_MAX_DEVS];
|
|
|
|
static void
|
|
mpath_des_free(struct scsi_inquiry_device_designator *des)
|
|
{
|
|
if (!des) {
|
|
return;
|
|
}
|
|
|
|
free(des->designator);
|
|
free(des);
|
|
}
|
|
|
|
static int
|
|
mpath_des_copy(struct scsi_inquiry_device_designator *des,
|
|
struct scsi_inquiry_device_designator **_des_cp)
|
|
{
|
|
struct scsi_inquiry_device_designator *des_cp;
|
|
|
|
if (!_des_cp) {
|
|
return -1;
|
|
}
|
|
|
|
des_cp = malloc(sizeof(*des_cp));
|
|
if (des_cp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
des_cp->protocol_identifier = des->protocol_identifier;
|
|
des_cp->code_set = des->code_set;
|
|
des_cp->piv = des->piv;
|
|
des_cp->association = des->association;
|
|
des_cp->designator_type = des->designator_type;
|
|
des_cp->designator_length = des->designator_length;
|
|
des_cp->designator = malloc(des->designator_length);
|
|
if (des_cp->designator == NULL) {
|
|
free(des_cp);
|
|
return -1;
|
|
}
|
|
memcpy(des_cp->designator, des->designator, des->designator_length);
|
|
*_des_cp = des;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mpath_des_cmp(struct scsi_inquiry_device_designator *des1,
|
|
struct scsi_inquiry_device_designator *des2)
|
|
{
|
|
if (des1->protocol_identifier != des2->protocol_identifier) {
|
|
return -1;
|
|
}
|
|
|
|
if (des1->code_set != des2->code_set) {
|
|
return -1;
|
|
}
|
|
|
|
if (des1->piv != des2->piv) {
|
|
return -1;
|
|
}
|
|
|
|
if (des1->association != des2->association) {
|
|
return -1;
|
|
}
|
|
|
|
if (des1->designator_type != des2->designator_type) {
|
|
return -1;
|
|
}
|
|
|
|
if (des1->designator_length != des2->designator_length) {
|
|
return -1;
|
|
}
|
|
|
|
return memcmp(des1->designator, des2->designator,
|
|
des1->designator_length);
|
|
}
|
|
|
|
static int
|
|
mpath_check_matching_ids_devid_vpd(int num_sds,
|
|
struct scsi_device **sds)
|
|
{
|
|
int i;
|
|
int num_sds_with_valid_id = 0;
|
|
struct scsi_task *inq_task = NULL;
|
|
struct scsi_inquiry_device_designator *des_saved = NULL;
|
|
|
|
for (i = 0; i < num_sds; i++) {
|
|
int ret;
|
|
int full_size;
|
|
struct scsi_inquiry_device_identification *inq_id_data;
|
|
struct scsi_inquiry_device_designator *des;
|
|
|
|
/*
|
|
* dev ID inquiry to confirm that all multipath devices carry
|
|
* an identical logical unit identifier.
|
|
*/
|
|
inquiry(sds[i], &inq_task, 1,
|
|
SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
|
|
64,
|
|
EXPECT_STATUS_GOOD);
|
|
if (inq_task && inq_task->status != SCSI_STATUS_GOOD) {
|
|
printf("Inquiry command failed : %s\n",
|
|
sds[i]->error_str);
|
|
goto err_cleanup;
|
|
}
|
|
full_size = scsi_datain_getfullsize(inq_task);
|
|
if (full_size > inq_task->datain.size) {
|
|
/* we need more data */
|
|
scsi_free_scsi_task(inq_task);
|
|
inq_task = NULL;
|
|
inquiry(sds[i], &inq_task, 1,
|
|
SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
|
|
full_size,
|
|
EXPECT_STATUS_GOOD);
|
|
if (inq_task == NULL) {
|
|
printf("Inquiry command failed : %s\n",
|
|
sds[i]->error_str);
|
|
goto err_cleanup;
|
|
}
|
|
}
|
|
|
|
inq_id_data = scsi_datain_unmarshall(inq_task);
|
|
if (inq_id_data == NULL) {
|
|
printf("failed to unmarshall inquiry ID datain blob\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (inq_id_data->qualifier
|
|
!= SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
|
|
printf("error: multipath device not connected\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (inq_id_data->device_type
|
|
!= SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
|
|
printf("error: multipath devices must be SBC\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
/* walk the list of IDs, and find a suitable LU candidate */
|
|
for (des = inq_id_data->designators;
|
|
des != NULL;
|
|
des = des->next) {
|
|
if (des->association != SCSI_ASSOCIATION_LOGICAL_UNIT) {
|
|
printf("skipping non-LU designator: %d\n",
|
|
des->association);
|
|
continue;
|
|
}
|
|
|
|
if ((des->designator_type != SCSI_DESIGNATOR_TYPE_EUI_64)
|
|
&& (des->designator_type != SCSI_DESIGNATOR_TYPE_NAA)
|
|
&& (des->designator_type != SCSI_DESIGNATOR_TYPE_MD5_LOGICAL_UNIT_IDENTIFIER)
|
|
&& (des->designator_type != SCSI_DESIGNATOR_TYPE_SCSI_NAME_STRING)) {
|
|
printf("skipping unsupported des type: %d\n",
|
|
des->designator_type);
|
|
continue;
|
|
}
|
|
|
|
if (des->designator_length <= 0) {
|
|
printf("skipping designator with bad len: %d\n",
|
|
des->designator_length);
|
|
continue;
|
|
}
|
|
|
|
if (des_saved == NULL) {
|
|
ret = mpath_des_copy(des, &des_saved);
|
|
if (ret < 0) {
|
|
goto err_cleanup;
|
|
}
|
|
/*
|
|
* we now have a reference to look for in all
|
|
* subsequent paths.
|
|
*/
|
|
num_sds_with_valid_id++;
|
|
break;
|
|
} else if (mpath_des_cmp(des, des_saved) == 0) {
|
|
/* found match for previous path designator */
|
|
num_sds_with_valid_id++;
|
|
break;
|
|
}
|
|
/* no match yet, keep checking other designators */
|
|
}
|
|
|
|
scsi_free_scsi_task(inq_task);
|
|
inq_task = NULL;
|
|
}
|
|
mpath_des_free(des_saved);
|
|
|
|
if (num_sds_with_valid_id != num_sds) {
|
|
printf("failed to find matching LU device ID for all paths\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("found matching LU device identifier for all (%d) paths\n",
|
|
num_sds);
|
|
return 0;
|
|
|
|
err_cleanup:
|
|
mpath_des_free(des_saved);
|
|
scsi_free_scsi_task(inq_task);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
mpath_check_matching_ids_serial_vpd(int num_sds,
|
|
struct scsi_device **sds)
|
|
{
|
|
int i;
|
|
int num_sds_with_valid_id = 0;
|
|
struct scsi_task *inq_task = NULL;
|
|
char *usn_saved = NULL;
|
|
|
|
for (i = 0; i < num_sds; i++) {
|
|
int full_size;
|
|
struct scsi_inquiry_unit_serial_number *inq_serial;
|
|
|
|
/*
|
|
* inquiry to confirm that all multipath devices carry an
|
|
* identical unit serial number.
|
|
*/
|
|
inq_task = NULL;
|
|
inquiry(sds[i], &inq_task, 1,
|
|
SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER, 64,
|
|
EXPECT_STATUS_GOOD);
|
|
if (inq_task && inq_task->status != SCSI_STATUS_GOOD) {
|
|
printf("Inquiry command failed : %s\n",
|
|
sds[i]->error_str);
|
|
goto err_cleanup;
|
|
}
|
|
full_size = scsi_datain_getfullsize(inq_task);
|
|
if (full_size > inq_task->datain.size) {
|
|
scsi_free_scsi_task(inq_task);
|
|
|
|
/* we need more data */
|
|
inq_task = NULL;
|
|
inquiry(sds[i], &inq_task, 0, 0, full_size,
|
|
EXPECT_STATUS_GOOD);
|
|
if (inq_task == NULL) {
|
|
printf("Inquiry command failed : %s\n",
|
|
sds[i]->error_str);
|
|
goto err_cleanup;
|
|
}
|
|
}
|
|
|
|
inq_serial = scsi_datain_unmarshall(inq_task);
|
|
if (inq_serial == NULL) {
|
|
printf("failed to unmarshall inquiry datain blob\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (inq_serial->qualifier
|
|
!= SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
|
|
printf("error: multipath device not connected\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (inq_serial->device_type
|
|
!= SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
|
|
printf("error: multipath devices must be SBC\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (inq_serial->usn == NULL) {
|
|
printf("error: empty usn for multipath device\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
if (usn_saved == NULL) {
|
|
usn_saved = strdup(inq_serial->usn);
|
|
if (usn_saved == NULL) {
|
|
goto err_cleanup;
|
|
}
|
|
num_sds_with_valid_id++;
|
|
} else if (strcmp(usn_saved, inq_serial->usn) == 0) {
|
|
num_sds_with_valid_id++;
|
|
} else {
|
|
printf("multipath unit serial mismatch: %s != %s\n",
|
|
usn_saved, inq_serial->usn);
|
|
}
|
|
|
|
scsi_free_scsi_task(inq_task);
|
|
inq_task = NULL;
|
|
}
|
|
|
|
if (num_sds_with_valid_id != num_sds) {
|
|
printf("failed to find matching serial number for all paths\n");
|
|
goto err_cleanup;
|
|
}
|
|
|
|
printf("found matching serial number for all (%d) paths: %s\n",
|
|
num_sds, usn_saved);
|
|
free(usn_saved);
|
|
|
|
return 0;
|
|
|
|
err_cleanup:
|
|
free(usn_saved);
|
|
scsi_free_scsi_task(inq_task);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
mpath_check_matching_ids(int num_sds,
|
|
struct scsi_device **sds)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* first check all devices for a matching LU identifier in the device
|
|
* identification INQUIRY VPD page.
|
|
*/
|
|
ret = mpath_check_matching_ids_devid_vpd(num_sds, sds);
|
|
if (ret == 0) {
|
|
return 0; /* found matching */
|
|
}
|
|
|
|
/* fall back to a unit serial number check */
|
|
ret = mpath_check_matching_ids_serial_vpd(num_sds, sds);
|
|
return ret;
|
|
}
|