/* * device-inq.c: inquire SCSI device information. * * Copyright (c) 2010 EMC Corporation, Haiying Tang * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; 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; while (pos < (len - sizeof(struct bl_dev_id) + sizeof(unsigned char))) { dev_id = (struct bl_dev_id *)&(dev_root->data[pos]); 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; 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; } 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; pos += (dev_id->len + sizeof(struct bl_dev_id) - sizeof(unsigned char)); } out: if (!serial_out) serial_out = bl_create_scsi_string(strlen(filename), filename); if (buffer) free(buffer); return serial_out; }