]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/blkmapd/device-inq.c
blkmapd: Add complex block layout discovery and mapping daemon
[nfs-utils.git] / utils / blkmapd / device-inq.c
diff --git a/utils/blkmapd/device-inq.c b/utils/blkmapd/device-inq.c
new file mode 100644 (file)
index 0000000..eabc70c
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * 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;
+
+       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;
+}