blob: c7952c3e95c32dc3b44567323ea877b1640a88b6 [file] [log] [blame]
/*
* device-inq.c: inquire SCSI device information.
*
* Copyright (c) 2010 EMC Corporation, Haiying Tang <Tang_Haiying@emc.com>
* All rights reserved.
*
* This program refers to "SCSI Primary Commands - 3 (SPC-3)
* at http://www.t10.org and sg_inq.c in sg3_utils-1.26 for
* Linux OS SCSI subsystem, by D. Gilbert.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/select.h>
#include <scsi/scsi.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/sg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>
#include <libgen.h>
#include <errno.h>
#include "device-discovery.h"
#define DEF_ALLOC_LEN 255
#define MX_ALLOC_LEN (0xc000 + 0x80)
static struct bl_serial *bl_create_scsi_string(int len, const char *bytes)
{
struct bl_serial *s;
s = malloc(sizeof(*s) + len);
if (s) {
s->data = (char *)&s[1];
s->len = len;
memcpy(s->data, bytes, len);
}
return s;
}
static void bl_free_scsi_string(struct bl_serial *str)
{
if (str)
free(str);
}
#define sg_io_ok(io_hdr) \
((((io_hdr).status & 0x7e) == 0) && \
((io_hdr).host_status == 0) && \
(((io_hdr).driver_status & 0x0f) == 0))
static int sg_timeout = 1 * 1000;
static int bldev_inquire_page(int fd, int page, char *buffer, int len)
{
unsigned char cmd[] = { INQUIRY, 0, 0, 0, 0, 0 };
unsigned char sense_b[28];
struct sg_io_hdr io_hdr;
if (page >= 0) {
cmd[1] = 1;
cmd[2] = page;
}
cmd[3] = (unsigned char)((len >> 8) & 0xff);
cmd[4] = (unsigned char)(len & 0xff);
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = sizeof(cmd);
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = len;
io_hdr.dxferp = buffer;
io_hdr.cmdp = cmd;
io_hdr.sbp = sense_b;
io_hdr.timeout = sg_timeout;
if (ioctl(fd, SG_IO, &io_hdr) < 0)
return -1;
if (sg_io_ok(io_hdr))
return 0;
return -1;
}
static int bldev_inquire_pages(int fd, int page, char **buffer)
{
int status = 0;
char *tmp;
int len;
*buffer = calloc(DEF_ALLOC_LEN, sizeof(char));
if (!*buffer) {
BL_LOG_ERR("%s: Out of memory!\n", __func__);
return -ENOMEM;
}
status = bldev_inquire_page(fd, page, *buffer, DEF_ALLOC_LEN);
if (status)
goto out;
status = -1;
if ((*(*buffer + 1) & 0xff) != page)
goto out;
len = (*(*buffer + 2) << 8) + *(*buffer + 3) + 4;
if (len > MX_ALLOC_LEN) {
BL_LOG_ERR("SCSI response length too long: %d\n", len);
goto out;
}
if (len > DEF_ALLOC_LEN) {
tmp = realloc(*buffer, len);
if (!tmp) {
BL_LOG_ERR("%s: Out of memory!\n", __func__);
status = -ENOMEM;
goto out;
}
*buffer = tmp;
status = bldev_inquire_page(fd, page, *buffer, len);
if (status)
goto out;
}
status = 0;
out:
return status;
}
/* For EMC multipath devices, use VPD page (0xc0) to get status.
* For other devices, return ACTIVE for now
*/
extern enum bl_path_state_e bldev_read_ap_state(int fd)
{
int status = 0;
char *buffer = NULL;
enum bl_path_state_e ap_state = BL_PATH_STATE_ACTIVE;
status = bldev_inquire_pages(fd, 0xc0, &buffer);
if (status)
goto out;
if (buffer[4] < 0x02)
ap_state = BL_PATH_STATE_PASSIVE;
out:
if (buffer)
free(buffer);
return ap_state;
}
struct bl_serial *bldev_read_serial(int fd, const char *filename)
{
struct bl_serial *serial_out = NULL;
int status = 0;
char *buffer;
struct bl_dev_id *dev_root, *dev_id;
unsigned int pos, len, current_id = 0;
size_t devid_len = sizeof(struct bl_dev_id) - sizeof(unsigned char);
status = bldev_inquire_pages(fd, 0x83, &buffer);
if (status)
goto out;
dev_root = (struct bl_dev_id *)buffer;
pos = 0;
current_id = 0;
len = dev_root->len;
if (len < devid_len)
goto out;
while (pos < (len - devid_len)) {
dev_id = (struct bl_dev_id *)&(dev_root->data[pos]);
pos += (dev_id->len + devid_len);
/* Some targets export zero length EVPD pages,
* skip them to not confuse the device id
* cache.
*/
if (!dev_id->len)
continue;
if ((dev_id->ids & 0xf) < current_id)
continue;
switch (dev_id->ids & 0xf) {
/* We process SCSI ID with four ID cases: 0, 1, 2 and 3.
* When more than one ID is available, priority is
* 3>2>1>0.
*/
case 2: /* EUI-64 based */
if ((dev_id->len != 8) && (dev_id->len != 12) &&
(dev_id->len != 16))
break;
/* FALLTHRU */
case 3: /* NAA */
/* TODO: NAA validity judgement too complicated,
* so just ingore it here.
*/
if ((dev_id->type & 0xf) != 1) {
BL_LOG_ERR("Binary code_set expected\n");
break;
}
/* FALLTHRU */
case 0: /* vendor specific */
case 1: /* T10 vendor identification */
current_id = dev_id->ids & 0xf;
if (serial_out)
bl_free_scsi_string(serial_out);
serial_out = bl_create_scsi_string(dev_id->len,
dev_id->data);
break;
}
if (current_id == 3)
break;
}
out:
if (!serial_out)
serial_out = bl_create_scsi_string(strlen(filename), filename);
if (buffer)
free(buffer);
return serial_out;
}