summaryrefslogtreecommitdiff
path: root/extras/multipath-tools/multipath
diff options
context:
space:
mode:
Diffstat (limited to 'extras/multipath-tools/multipath')
-rw-r--r--extras/multipath-tools/multipath/Makefile60
-rw-r--r--extras/multipath-tools/multipath/devinfo.c240
-rw-r--r--extras/multipath-tools/multipath/devinfo.h21
-rw-r--r--extras/multipath-tools/multipath/main.c895
-rw-r--r--extras/multipath-tools/multipath/main.h131
-rw-r--r--extras/multipath-tools/multipath/multipath.866
-rw-r--r--extras/multipath-tools/multipath/multipath.hotplug7
-rw-r--r--extras/multipath-tools/multipath/sg_err.h162
-rw-r--r--extras/multipath-tools/multipath/sg_include.h43
-rw-r--r--extras/multipath-tools/multipath/unused.c95
10 files changed, 1720 insertions, 0 deletions
diff --git a/extras/multipath-tools/multipath/Makefile b/extras/multipath-tools/multipath/Makefile
new file mode 100644
index 0000000000..4121f4fc3b
--- /dev/null
+++ b/extras/multipath-tools/multipath/Makefile
@@ -0,0 +1,60 @@
+# Makefile
+#
+# Copyright (C) 2003 Christophe Varoqui, <christophe.varoqui@free.fr>
+
+EXEC = multipath
+
+prefix =
+exec_prefix = ${prefix}
+bindir = ${exec_prefix}/sbin
+udevdir = ../../..
+klibcdir = $(udevdir)/klibc
+sysfsdir = $(udevdir)/libsysfs
+mandir = /usr/share/man/man8
+libdmdir = ../libdevmapper
+arch = i386
+klibcarch = $(klibcdir)/klibc/arch/$(arch)/include
+
+CC = gcc
+GZIP = /bin/gzip -9 -c
+
+GCCINCDIR := ${shell $(CC) -print-search-dirs | sed -ne "s/install: \(.*\)/\1include/gp"}
+KERNEL_DIR = /lib/modules/${shell uname -r}/build
+CFLAGS = -pipe -g -O2 -Wall -Wunused -Wstrict-prototypes -nostdinc \
+ -I$(klibcdir)/klibc/include -I$(klibcdir)/klibc/include/bits32 \
+ -I$(GCCINCDIR) -I$(KERNEL_DIR)/include -I$(sysfsdir) -I. -I$(klibcarch)
+
+OBJS = devinfo.o main.o
+CRT0 = $(klibcdir)/klibc/crt0.o
+LIB = $(klibcdir)/klibc/libc.a
+LIBGCC := $(shell $(CC) -print-libgcc-file-name )
+
+DMOBJS = $(libdmdir)/libdm-common.o $(libdmdir)/ioctl/libdevmapper.o
+SYSFSOBJS = $(sysfsdir)/dlist.o $(sysfsdir)/sysfs_bus.o \
+ $(sysfsdir)/sysfs_class.o $(sysfsdir)/sysfs_device.o \
+ $(sysfsdir)/sysfs_dir.o $(sysfsdir)/sysfs_driver.o \
+ $(sysfsdir)/sysfs_utils.o
+
+$(EXEC): $(OBJS)
+ $(LD) -o $(EXEC) $(CRT0) $(OBJS) $(SYSFSOBJS) $(DMOBJS) $(LIB) $(LIBGCC)
+ strip $(EXEC)
+ $(GZIP) $(EXEC).8 > $(EXEC).8.gz
+
+clean:
+ rm -f core *.o $(EXEC) *.gz
+
+install:
+ install -d $(DESTDIR)$(bindir)
+ install -m 755 $(EXEC) $(DESTDIR)$(bindir)/
+ install -d $(DESTDIR)/etc/hotplug.d/scsi/
+ install -m 755 multipath.hotplug $(DESTDIR)/etc/hotplug.d/scsi/
+ install -d $(DESTDIR)$(mandir)
+ install -m 644 multipath.8.gz $(DESTDIR)$(mandir)
+
+uninstall:
+ rm $(DESTDIR)/etc/hotplug.d/scsi/multipath.hotplug
+ rm $(DESTDIR)$(bindir)/$(EXEC)
+ rm $(DESTDIR)$(mandir)/multipath.8.gz
+
+# Code dependencies
+main.o: main.c main.h sg_include.h devinfo.h
diff --git a/extras/multipath-tools/multipath/devinfo.c b/extras/multipath-tools/multipath/devinfo.c
new file mode 100644
index 0000000000..21890621e0
--- /dev/null
+++ b/extras/multipath-tools/multipath/devinfo.c
@@ -0,0 +1,240 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sysfs/libsysfs.h>
+#include "devinfo.h"
+#include "sg_include.h"
+
+#define FILE_NAME_SIZE 255
+
+void
+basename(char * str1, char * str2)
+{
+ char *p = str1 + (strlen(str1) - 1);
+
+ while (*--p != '/')
+ continue;
+ strcpy(str2, ++p);
+}
+
+static int
+do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op,
+ void *resp, int mx_resp_len, int noisy)
+{
+ unsigned char inqCmdBlk[INQUIRY_CMDLEN] =
+ { INQUIRY_CMD, 0, 0, 0, 0, 0 };
+ unsigned char sense_b[SENSE_BUFF_LEN];
+ struct sg_io_hdr io_hdr;
+
+ if (cmddt)
+ inqCmdBlk[1] |= 2;
+ if (evpd)
+ inqCmdBlk[1] |= 1;
+ inqCmdBlk[2] = (unsigned char) pg_op;
+ inqCmdBlk[4] = (unsigned char) mx_resp_len;
+ memset(&io_hdr, 0, sizeof (struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof (inqCmdBlk);
+ io_hdr.mx_sb_len = sizeof (sense_b);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = mx_resp_len;
+ io_hdr.dxferp = resp;
+ io_hdr.cmdp = inqCmdBlk;
+ io_hdr.sbp = sense_b;
+ io_hdr.timeout = DEF_TIMEOUT;
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("SG_IO (inquiry) error");
+ return -1;
+ }
+
+ /* treat SG_ERR here to get rid of sg_err.[ch] */
+ io_hdr.status &= 0x7e;
+ if ((0 == io_hdr.status) && (0 == io_hdr.host_status) &&
+ (0 == io_hdr.driver_status))
+ return 0;
+ if ((SCSI_CHECK_CONDITION == io_hdr.status) ||
+ (SCSI_COMMAND_TERMINATED == io_hdr.status) ||
+ (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) {
+ if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) {
+ int sense_key;
+ unsigned char * sense_buffer = io_hdr.sbp;
+ if (sense_buffer[0] & 0x2)
+ sense_key = sense_buffer[1] & 0xf;
+ else
+ sense_key = sense_buffer[2] & 0xf;
+ if(RECOVERED_ERROR == sense_key)
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int
+get_serial (char * str, char * devname)
+{
+ int fd;
+ int len;
+ char buff[MX_ALLOC_LEN + 1];
+
+ if ((fd = open(devname, O_RDONLY)) < 0)
+ return 0;
+
+ if (0 == do_inq(fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, 0)) {
+ len = buff[3];
+ if (len > 0) {
+ memcpy(str, buff + 4, len);
+ buff[len] = '\0';
+ }
+ close(fd);
+ return 1;
+ }
+ close(fd);
+ return 0;
+}
+
+int
+get_lun_strings(char * vendor_id, char * product_id, char * rev, char * devname)
+{
+ int fd;
+ char buff[36];
+ char attr_path[FILE_NAME_SIZE];
+ char sysfs_path[FILE_NAME_SIZE];
+ char basedev[FILE_NAME_SIZE];
+
+ if (0 == sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) {
+ /* sysfs style */
+ basename(devname, basedev);
+
+ sprintf(attr_path, "%s/block/%s/device/vendor",
+ sysfs_path, basedev);
+ if (0 > sysfs_read_attribute_value(attr_path,
+ vendor_id, 8)) return 0;
+
+ sprintf(attr_path, "%s/block/%s/device/model",
+ sysfs_path, basedev);
+ if (0 > sysfs_read_attribute_value(attr_path,
+ product_id, 16)) return 0;
+
+ sprintf(attr_path, "%s/block/%s/device/rev",
+ sysfs_path, basedev);
+ if (0 > sysfs_read_attribute_value(attr_path,
+ rev, 4)) return 0;
+ } else {
+ /* ioctl style */
+ if ((fd = open(devname, O_RDONLY)) < 0)
+ return 0;
+ if (0 != do_inq(fd, 0, 0, 0, buff, 36, 1))
+ return 0;
+ memcpy(vendor_id, &buff[8], 8);
+ memcpy(product_id, &buff[16], 16);
+ memcpy(rev, &buff[32], 4);
+ close(fd);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+sprint_wwid(char * buff, const char * str)
+{
+ int i;
+ const char *p;
+ char *cursor;
+ unsigned char c;
+
+ p = str;
+ cursor = buff;
+ for (i = 0; i <= WWID_SIZE / 2 - 1; i++) {
+ c = *p++;
+ sprintf(cursor, "%.2x", (int) (unsigned char) c);
+ cursor += 2;
+ }
+ buff[WWID_SIZE - 1] = '\0';
+}
+
+/* get EVPD page 0x83 off 8 */
+/* tested ok with StorageWorks */
+int
+get_evpd_wwid(char * devname, char * wwid)
+{
+ int fd;
+ char buff[MX_ALLOC_LEN + 1];
+
+ if ((fd = open(devname, O_RDONLY)) < 0)
+ return 0;
+
+ if (0 == do_inq(fd, 0, 1, 0x83, buff, MX_ALLOC_LEN, 1)) {
+ sprint_wwid(wwid, &buff[8]);
+ close(fd);
+ return 1; /* success */
+ }
+
+ close(fd);
+ return 0; /* not good */
+}
+
+long
+get_disk_size (char * devname) {
+ long size;
+ int fd;
+ char attr_path[FILE_NAME_SIZE];
+ char sysfs_path[FILE_NAME_SIZE];
+ char buff[FILE_NAME_SIZE];
+ char basedev[FILE_NAME_SIZE];
+
+ if (0 == sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) {
+ basename(devname, basedev);
+ sprintf(attr_path, "%s/block/%s/size",
+ sysfs_path, basedev);
+ if (0 > sysfs_read_attribute_value(attr_path, buff,
+ FILE_NAME_SIZE * sizeof(char)))
+ return -1;
+ size = atoi(buff);
+ return size;
+ } else {
+ if ((fd = open(devname, O_RDONLY)) < 0)
+ return -1;
+ if(!ioctl(fd, BLKGETSIZE, &size))
+ return size;
+ }
+ return -1;
+}
+
+int
+do_tur(char *dev)
+{
+ unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 };
+ struct sg_io_hdr io_hdr;
+ unsigned char sense_buffer[32];
+ int fd;
+
+ fd = open(dev, O_RDONLY);
+
+ if (fd < 0)
+ return 0;
+
+ memset(&io_hdr, 0, sizeof (struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof (turCmdBlk);
+ io_hdr.mx_sb_len = sizeof (sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = turCmdBlk;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000;
+ io_hdr.pack_id = 0;
+
+ if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ if (io_hdr.info & SG_INFO_OK_MASK)
+ return 0;
+
+ return 1;
+}
diff --git a/extras/multipath-tools/multipath/devinfo.h b/extras/multipath-tools/multipath/devinfo.h
new file mode 100644
index 0000000000..ee17aba923
--- /dev/null
+++ b/extras/multipath-tools/multipath/devinfo.h
@@ -0,0 +1,21 @@
+#define INQUIRY_CMDLEN 6
+#define INQUIRY_CMD 0x12
+#define SENSE_BUFF_LEN 32
+#define DEF_TIMEOUT 60000
+#define RECOVERED_ERROR 0x01
+#define MX_ALLOC_LEN 255
+#define WWID_SIZE 33
+#define BLKGETSIZE _IO(0x12,96)
+#define TUR_CMD_LEN 6
+
+/* exerpt from "sg_err.h" */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SG_ERR_DRIVER_SENSE 0x08
+
+void basename (char *, char *);
+int get_serial (char *, char *);
+int get_lun_strings (char *, char *, char *, char *);
+int get_evpd_wwid(char *, char *);
+long get_disk_size (char *);
+int do_tur (char *);
diff --git a/extras/multipath-tools/multipath/main.c b/extras/multipath-tools/multipath/main.c
new file mode 100644
index 0000000000..8f06a8f94c
--- /dev/null
+++ b/extras/multipath-tools/multipath/main.c
@@ -0,0 +1,895 @@
+/*
+ * Soft: multipath device mapper target autoconfig
+ *
+ * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $
+ *
+ * Author: Copyright (C) 2003 Christophe Varoqui
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/kdev_t.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <dlist.h>
+#include <sysfs/libsysfs.h>
+#include "../libdevmapper/libdevmapper.h"
+#include "main.h"
+#include "devinfo.h"
+
+/* White list switch */
+static int
+get_unique_id(struct path * mypath)
+{
+ int i;
+ static struct {
+ char * vendor;
+ char * product;
+ int iopolicy;
+ int (*getuid) (char *, char *);
+ } wlist[] = {
+ {"COMPAQ ", "HSV110 (C)COMPAQ", GROUP_BY_TUR, &get_evpd_wwid},
+ {"COMPAQ ", "MSA1000 ", GROUP_BY_TUR, &get_evpd_wwid},
+ {"COMPAQ ", "MSA1000 VOLUME ", GROUP_BY_TUR, &get_evpd_wwid},
+ {"DEC ", "HSG80 ", GROUP_BY_TUR, &get_evpd_wwid},
+ {"HP ", "HSV100 ", GROUP_BY_TUR, &get_evpd_wwid},
+ {"HP ", "A6189A ", MULTIBUS, &get_evpd_wwid},
+ {"HP ", "OPEN- ", MULTIBUS, &get_evpd_wwid},
+ {"DDN ", "SAN DataDirector", MULTIBUS, &get_evpd_wwid},
+ {"FSC ", "CentricStor ", MULTIBUS, &get_evpd_wwid},
+ {"HITACHI ", "DF400 ", MULTIBUS, &get_evpd_wwid},
+ {"HITACHI ", "DF500 ", MULTIBUS, &get_evpd_wwid},
+ {"HITACHI ", "DF600 ", MULTIBUS, &get_evpd_wwid},
+ {"IBM ", "ProFibre 4000R ", MULTIBUS, &get_evpd_wwid},
+ {"SGI ", "TP9100 ", MULTIBUS, &get_evpd_wwid},
+ {"SGI ", "TP9300 ", MULTIBUS, &get_evpd_wwid},
+ {"SGI ", "TP9400 ", MULTIBUS, &get_evpd_wwid},
+ {"SGI ", "TP9500 ", MULTIBUS, &get_evpd_wwid},
+ {NULL, NULL, 0, NULL},
+ };
+
+ for (i = 0; wlist[i].vendor; i++) {
+ if (strncmp(mypath->vendor_id, wlist[i].vendor, 8) == 0 &&
+ strncmp(mypath->product_id, wlist[i].product, 16) == 0) {
+ mypath->iopolicy = wlist[i].iopolicy;
+ if (!wlist[i].getuid(mypath->sg_dev, mypath->wwid))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+sysfsdevice2devname (char *devname, char *device)
+{
+ char sysfs_path[FILE_NAME_SIZE];
+ char block_path[FILE_NAME_SIZE];
+ char link_path[FILE_NAME_SIZE];
+ int r;
+
+ if (sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) {
+ fprintf(stderr, "[device] feature available with sysfs only\n");
+ exit (1);
+ }
+
+ sprintf(link_path, "%s%s/block", sysfs_path, device);
+
+ r = sysfs_get_link(link_path, block_path, FILE_NAME_SIZE);
+
+ if (r != 0)
+ return 1;
+
+ sysfs_get_name_from_path(block_path, devname, FILE_NAME_SIZE);
+
+ return 0;
+}
+
+
+static int
+devt2devname (char *devname, int major, int minor)
+{
+ struct sysfs_directory * sdir;
+ struct sysfs_directory * devp;
+ char sysfs_path[FILE_NAME_SIZE];
+ char block_path[FILE_NAME_SIZE];
+ char attr_path[FILE_NAME_SIZE];
+ char attr_value[16];
+ char attr_ref_value[16];
+
+ if (sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) {
+ fprintf(stderr, "-D feature available with sysfs only\n");
+ exit (1);
+ }
+
+ sprintf(attr_ref_value, "%i:%i\n", major, minor);
+ sprintf(block_path, "%s/block", sysfs_path);
+ sdir = sysfs_open_directory(block_path);
+ sysfs_read_directory(sdir);
+
+ dlist_for_each_data(sdir->subdirs, devp, struct sysfs_directory) {
+ sprintf(attr_path, "%s/%s/dev", block_path, devp->name);
+ sysfs_read_attribute_value(attr_path, attr_value, 16);
+
+ if (!strcmp(attr_value, attr_ref_value)) {
+ sprintf(attr_path, "%s/%s", block_path, devp->name);
+ sysfs_get_name_from_path(attr_path, devname, FILE_NAME_SIZE);
+ break;
+ }
+ }
+
+ sysfs_close_directory(sdir);
+
+ return 0;
+}
+
+static int
+blacklist (char * dev) {
+ int i;
+ static struct {
+ char * headstr;
+ int lengh;
+ } blist[] = {
+ {"cciss", 5},
+ {"hd", 2},
+ {"md", 2},
+ {"dm", 2},
+ {"sr", 2},
+ {"scd", 3},
+ {"ram", 3},
+ {"raw", 3},
+ {NULL, 0},
+ };
+
+ for (i = 0; blist[i].lengh; i++) {
+ if (strncmp(dev, blist[i].headstr, blist[i].lengh) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+devinfo (struct path *curpath)
+{
+ get_lun_strings(curpath->vendor_id,
+ curpath->product_id,
+ curpath->rev,
+ curpath->sg_dev);
+ get_serial(curpath->serial, curpath->sg_dev);
+ curpath->tur = do_tur(curpath->sg_dev);
+ if (!get_unique_id(curpath))
+ return 1;
+
+ return 0;
+}
+
+
+static int
+get_all_paths_sysfs(struct env * conf, struct path * all_paths)
+{
+ int k=0;
+ struct sysfs_directory * sdir;
+ struct sysfs_directory * devp;
+ struct sysfs_link * linkp;
+ char refwwid[WWID_SIZE];
+ char empty_buff[WWID_SIZE];
+ char buff[FILE_NAME_SIZE];
+ char path[FILE_NAME_SIZE];
+ struct path curpath;
+
+ memset(empty_buff, 0, WWID_SIZE);
+ memset(refwwid, 0, WWID_SIZE);
+
+ /* if called from hotplug, only consider the paths that relate
+ to the device pointed by conf.hotplugdev */
+
+ if (strncmp("/devices", conf->hotplugdev, 8) == 0) {
+ if (sysfsdevice2devname (buff, conf->hotplugdev))
+ return 0;
+
+ sprintf(curpath.sg_dev, "/dev/%s", buff);
+
+ if (devinfo(&curpath))
+ return 0;
+
+ strcpy(refwwid, curpath.wwid);
+ memset(&curpath, 0, sizeof(path));
+ }
+
+ /* if major/minor specified on the cmd line,
+ only consider affiliated paths */
+
+ if (conf->major >= 0 && conf->minor >= 0) {
+ if (devt2devname(buff, conf->major, conf->minor))
+ return 0;
+
+ sprintf(curpath.sg_dev, "/dev/%s", buff);
+
+ if (devinfo(&curpath))
+ return 0;
+
+ strcpy(refwwid, curpath.wwid);
+ memset(&curpath, 0, sizeof(path));
+ }
+
+
+ sprintf(path, "%s/block", conf->sysfs_path);
+ sdir = sysfs_open_directory(path);
+ sysfs_read_directory(sdir);
+
+ dlist_for_each_data(sdir->subdirs, devp, struct sysfs_directory) {
+ if (blacklist(devp->name))
+ continue;
+
+ sysfs_read_directory(devp);
+
+ if(devp->links == NULL)
+ continue;
+
+ dlist_for_each_data(devp->links, linkp, struct sysfs_link) {
+ if (!strncmp(linkp->name, "device", 6))
+ break;
+ }
+
+ if (linkp == NULL) {
+ continue;
+ }
+
+ basename(devp->path, buff);
+ sprintf(curpath.sg_dev, "/dev/%s", buff);
+
+ if(devinfo(&curpath)) {
+ memset(&curpath, 0, sizeof(path));
+ continue;
+ }
+
+ if (memcmp(empty_buff, refwwid, WWID_SIZE) != 0 &&
+ strncmp(curpath.wwid, refwwid, WWID_SIZE) != 0) {
+ memset(&curpath, 0, sizeof(path));
+ continue;
+ }
+
+ strcpy(all_paths[k].sg_dev, curpath.sg_dev);
+ strcpy(all_paths[k].dev, curpath.sg_dev);
+ strcpy(all_paths[k].wwid, curpath.wwid);
+ strcpy(all_paths[k].vendor_id, curpath.vendor_id);
+ strcpy(all_paths[k].product_id, curpath.product_id);
+ all_paths[k].iopolicy = curpath.iopolicy;
+ all_paths[k].tur = curpath.tur;
+
+ /* done with curpath, zero for reuse */
+ memset(&curpath, 0, sizeof(path));
+
+ basename(linkp->target, buff);
+ sscanf(buff, "%i:%i:%i:%i",
+ &all_paths[k].sg_id.host_no,
+ &all_paths[k].sg_id.channel,
+ &all_paths[k].sg_id.scsi_id,
+ &all_paths[k].sg_id.lun);
+ k++;
+ }
+ sysfs_close_directory(sdir);
+ return 0;
+}
+
+static int
+get_all_paths_nosysfs(struct env * conf, struct path * all_paths,
+ struct scsi_dev * all_scsi_ids)
+{
+ int k, i, fd;
+ char buff[FILE_NAME_SIZE];
+ char file_name[FILE_NAME_SIZE];
+
+ for (k = 0; k < conf->max_devs; k++) {
+ strcpy(file_name, "/dev/sg");
+ sprintf(buff, "%d", k);
+ strncat(file_name, buff, FILE_NAME_SIZE);
+ strcpy(all_paths[k].sg_dev, file_name);
+
+ get_lun_strings(all_paths[k].vendor_id,
+ all_paths[k].product_id,
+ all_paths[k].rev,
+ all_paths[k].sg_dev);
+ get_serial(all_paths[k].serial, all_paths[k].sg_dev);
+ if (!get_unique_id(&all_paths[k]))
+ continue;
+
+ if ((fd = open(all_paths[k].sg_dev, O_RDONLY)) < 0)
+ return 0;
+
+ if (0 > ioctl(fd, SG_GET_SCSI_ID, &(all_paths[k].sg_id)))
+ printf("device %s failed on sg ioctl, skip\n",
+ file_name);
+
+ close(fd);
+
+ for (i = 0; i < conf->max_devs; i++) {
+ if ((all_paths[k].sg_id.host_no ==
+ all_scsi_ids[i].host_no)
+ && (all_paths[k].sg_id.scsi_id ==
+ (all_scsi_ids[i].scsi_id.dev_id & 0xff))
+ && (all_paths[k].sg_id.lun ==
+ ((all_scsi_ids[i].scsi_id.dev_id >> 8) & 0xff))
+ && (all_paths[k].sg_id.channel ==
+ ((all_scsi_ids[i].scsi_id.
+ dev_id >> 16) & 0xff))) {
+ strcpy(all_paths[k].dev, all_scsi_ids[i].dev);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+get_all_scsi_ids(struct env * conf, struct scsi_dev * all_scsi_ids)
+{
+ int k, big, little, res, host_no, fd;
+ char buff[64];
+ char fname[FILE_NAME_SIZE];
+ struct scsi_idlun my_scsi_id;
+
+ for (k = 0; k < conf->max_devs; k++) {
+ strcpy(fname, "/dev/sd");
+ if (k < 26) {
+ buff[0] = 'a' + (char) k;
+ buff[1] = '\0';
+ strcat(fname, buff);
+ } else if (k <= 255) {
+ /* assumes sequence goes x,y,z,aa,ab,ac etc */
+ big = k / 26;
+ little = k - (26 * big);
+ big = big - 1;
+
+ buff[0] = 'a' + (char) big;
+ buff[1] = 'a' + (char) little;
+ buff[2] = '\0';
+ strcat(fname, buff);
+ } else
+ strcat(fname, "xxxx");
+
+ if ((fd = open(fname, O_RDONLY)) < 0) {
+ if (conf->verbose)
+ fprintf(stderr, "can't open %s. mknod ?",
+ fname);
+ continue;
+ }
+
+ res = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &my_scsi_id);
+ if (res < 0) {
+ close(fd);
+ printf("Could not get scsi idlun\n");
+ continue;
+ }
+
+ res = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no);
+ if (res < 0) {
+ close(fd);
+ printf("Could not get host_no\n");
+ continue;
+ }
+
+ close(fd);
+
+ strcpy(all_scsi_ids[k].dev, fname);
+ all_scsi_ids[k].scsi_id = my_scsi_id;
+ all_scsi_ids[k].host_no = host_no;
+ }
+ return 0;
+}
+
+/* print_path style */
+#define ALL 0
+#define NOWWID 1
+
+static void
+print_path(struct path * all_paths, int k, int style)
+{
+ if (style != NOWWID)
+ printf("%s ", all_paths[k].wwid);
+ else
+ printf(" \\_");
+ printf("(%i %i %i %i) ",
+ all_paths[k].sg_id.host_no,
+ all_paths[k].sg_id.channel,
+ all_paths[k].sg_id.scsi_id, all_paths[k].sg_id.lun);
+ if(0 != strcmp(all_paths[k].sg_dev, all_paths[k].dev))
+ printf("%s ", all_paths[k].sg_dev);
+ printf("%s ", all_paths[k].dev);
+ printf("[%.16s]\n", all_paths[k].product_id);
+}
+
+static void
+print_all_path(struct env * conf, struct path * all_paths)
+{
+ int k;
+ char empty_buff[WWID_SIZE];
+
+ memset(empty_buff, 0, WWID_SIZE);
+ for (k = 0; k < conf->max_devs; k++) {
+ if (memcmp(empty_buff, all_paths[k].wwid, WWID_SIZE) == 0)
+ continue;
+ print_path(all_paths, k, ALL);
+ }
+}
+
+static void
+print_all_mp(struct path * all_paths, struct multipath * mp, int nmp)
+{
+ int k, i;
+
+ for (k = 0; k <= nmp; k++) {
+ printf("%s\n", mp[k].wwid);
+ for (i = 0; i <= mp[k].npaths; i++)
+ print_path(all_paths, PINDEX(k,i), NOWWID);
+ }
+}
+
+static int
+coalesce_paths(struct env * conf, struct multipath * mp,
+ struct path * all_paths)
+{
+ int k, i, nmp, np, already_done;
+ char empty_buff[WWID_SIZE];
+
+ nmp = -1;
+ already_done = 0;
+ memset(empty_buff, 0, WWID_SIZE);
+
+ for (k = 0; k < conf->max_devs - 1; k++) {
+ /* skip this path for some reason */
+
+ /* 1. if path has no unique id */
+ if (memcmp(empty_buff, all_paths[k].wwid, WWID_SIZE) == 0)
+ continue;
+
+ /* 2. if mp with this uid already instanciated */
+ for (i = 0; i <= nmp; i++) {
+ if (0 == strcmp(mp[i].wwid, all_paths[k].wwid))
+ already_done = 1;
+ }
+ if (already_done) {
+ already_done = 0;
+ continue;
+ }
+
+ /* at this point, we know we really got a new mp */
+ np = 0;
+ nmp++;
+ strcpy(mp[nmp].wwid, all_paths[k].wwid);
+ PINDEX(nmp,np) = k;
+
+ if (mp[nmp].size == 0)
+ mp[nmp].size = get_disk_size(all_paths[k].dev);
+
+ for (i = k + 1; i < conf->max_devs; i++) {
+ if (0 == strcmp(all_paths[k].wwid, all_paths[i].wwid)) {
+ np++;
+ PINDEX(nmp,np) = i;
+ mp[nmp].npaths = np;
+ }
+ }
+ }
+ return nmp;
+}
+
+static void
+group_by_tur(struct multipath * mp, struct path * all_paths, char * str) {
+ int left_path_count = 0;
+ int right_path_count = 0;
+ int i;
+ char left_path_buff[FILE_NAME_SIZE], right_path_buff[FILE_NAME_SIZE];
+ char * left_path_buff_p = &left_path_buff[0];
+ char * right_path_buff_p = &right_path_buff[0];
+
+ for (i = 0; i <= mp->npaths; i++) {
+ if (all_paths[mp->pindex[i]].tur) {
+ left_path_buff_p += sprintf(left_path_buff_p, " %s", all_paths[mp->pindex[i]].dev);
+ left_path_count++;
+ } else {
+ right_path_buff_p += sprintf(right_path_buff_p, " %s", all_paths[mp->pindex[i]].dev);
+ right_path_count++;
+ }
+ }
+ if (!left_path_count)
+ sprintf(str, " 1 round-robin %i 0 %s", right_path_count, right_path_buff);
+ else if (!right_path_count)
+ sprintf(str, " 1 round-robin %i 0 %s", left_path_count, left_path_buff);
+ else
+ sprintf(str, " 2 round-robin %i 0 %s round-robin %i 0 %s",
+ left_path_count, left_path_buff,
+ right_path_count, right_path_buff);
+}
+
+static void
+group_by_serial(struct multipath * mp, struct path * all_paths, char * str) {
+ int path_count, pg_count = 0;
+ int i, k;
+ int * bitmap;
+ char path_buff[FILE_NAME_SIZE];
+ char pg_buff[FILE_NAME_SIZE];
+ char * path_buff_p = &path_buff[0];
+ char * pg_buff_p = &pg_buff[0];
+
+ /* init the bitmap */
+ bitmap = malloc((mp->npaths + 1) * sizeof(int));
+ memset(bitmap, 0, (mp->npaths + 1) * sizeof(int));
+
+ for (i = 0; i <= mp->npaths; i++) {
+ if (bitmap[i])
+ continue;
+
+ /* here, we really got a new pg */
+ pg_count++;
+ path_count = 1;
+ memset(&path_buff, 0, FILE_NAME_SIZE * sizeof(char));
+ path_buff_p = &path_buff[0];
+
+ path_buff_p += sprintf(path_buff_p, " %s", all_paths[mp->pindex[i]].dev);
+ bitmap[i] = 1;
+
+ for (k = i + 1; k <= mp->npaths; k++) {
+ if (bitmap[k])
+ continue;
+ if (0 == strcmp(all_paths[mp->pindex[i]].serial,
+ all_paths[mp->pindex[k]].serial)) {
+ path_buff_p += sprintf(path_buff_p, " %s", all_paths[mp->pindex[k]].dev);
+ bitmap[k] = 1;
+ path_count++;
+ }
+ }
+ pg_buff_p += sprintf(pg_buff_p, " round-robin %i 0%s",
+ path_count, path_buff);
+ }
+ sprintf(str, " %i%s", pg_count, pg_buff);
+ free(bitmap);
+}
+
+static int
+dm_simplecmd(int task, const char *name) {
+ int r = 0;
+ struct dm_task *dmt;
+
+ if (!(dmt = dm_task_create(task)))
+ return 0;
+
+ if (!dm_task_set_name(dmt, name))
+ goto out;
+
+ r = dm_task_run(dmt);
+
+ out:
+ dm_task_destroy(dmt);
+ return r;
+}
+
+static int
+dm_addmap(int task, const char *name, const char *params, long size) {
+ struct dm_task *dmt;
+
+ if (!(dmt = dm_task_create(task)))
+ return 0;
+
+ if (!dm_task_set_name(dmt, name))
+ goto addout;
+
+ if (!dm_task_add_target(dmt, 0, size, DM_TARGET, params))
+ goto addout;
+
+ if (!dm_task_run(dmt))
+ goto addout;
+
+ addout:
+ dm_task_destroy(dmt);
+ return 1;
+}
+
+static int
+setup_map(struct env * conf, struct path * all_paths,
+ struct multipath * mp, int index, int op)
+{
+ char params[255];
+ char * params_p;
+ int i, np;
+
+ /* defaults for multipath target */
+ char * dm_ps_name = "round-robin";
+ int dm_ps_nr_args = 0;
+
+ params_p = &params[0];
+
+ np = 0;
+ for (i=0; i<=mp[index].npaths; i++) {
+ if (0 == all_paths[PINDEX(index,i)].sg_id.scsi_type)
+ np++;
+ }
+
+ if (np < 1)
+ return 0;
+
+ if ((all_paths[PINDEX(index,0)].iopolicy == MULTIBUS &&
+ conf->iopolicy == -1) || conf->iopolicy == MULTIBUS) {
+ params_p += sprintf(params_p, " 1 %s %i %i",
+ dm_ps_name, np, dm_ps_nr_args);
+
+ for (i=0; i<=mp[index].npaths; i++) {
+ if (0 != all_paths[PINDEX(index,i)].sg_id.scsi_type)
+ continue;
+ params_p += sprintf(params_p, " %s",
+ all_paths[PINDEX(index,i)].dev);
+ }
+ }
+
+ if ((all_paths[PINDEX(index,0)].iopolicy == FAILOVER &&
+ conf->iopolicy == -1) || conf->iopolicy == FAILOVER) {
+ params_p += sprintf(params_p, " %i", mp[index].npaths + 1);
+ for (i=0; i<=mp[index].npaths; i++) {
+ if (0 != all_paths[PINDEX(index,i)].sg_id.scsi_type)
+ continue;
+ params_p += sprintf(params_p, " %s ",
+ dm_ps_name);
+ params_p += sprintf(params_p, "1 %i",
+ dm_ps_nr_args);
+ params_p += sprintf(params_p, " %s",
+ all_paths[PINDEX(index,i)].dev);
+ }
+ }
+
+ if ((all_paths[PINDEX(index,0)].iopolicy == GROUP_BY_SERIAL &&
+ conf->iopolicy == -1) || conf->iopolicy == GROUP_BY_SERIAL) {
+ group_by_serial(&mp[index], all_paths, params_p);
+ }
+
+ if ((all_paths[PINDEX(index,0)].iopolicy == GROUP_BY_TUR &&
+ conf->iopolicy == -1) || conf->iopolicy == GROUP_BY_TUR) {
+ group_by_tur(&mp[index], all_paths, params_p);
+ }
+
+ if (mp[index].size < 0)
+ return 0;
+
+ if (!conf->quiet) {
+ if (op == DM_DEVICE_RELOAD)
+ printf("U:");
+ if (op == DM_DEVICE_CREATE)
+ printf("N:");
+ printf("%s:0 %li %s %s\n",
+ mp[index].wwid, mp[index].size, DM_TARGET, params);
+ }
+
+ if (op == DM_DEVICE_RELOAD)
+ dm_simplecmd(DM_DEVICE_SUSPEND, mp[index].wwid);
+
+ dm_addmap(op, mp[index].wwid, params, mp[index].size);
+
+ if (op == DM_DEVICE_RELOAD)
+ dm_simplecmd(DM_DEVICE_RESUME, mp[index].wwid);
+
+ return 1;
+}
+
+static int
+map_present(char * str)
+{
+ int r = 0;
+ struct dm_task *dmt;
+ struct dm_names *names;
+ unsigned next = 0;
+
+ if (!(dmt = dm_task_create(DM_DEVICE_LIST)))
+ return 0;
+
+ if (!dm_task_run(dmt))
+ goto out;
+
+ if (!(names = dm_task_get_names(dmt)))
+ goto out;
+
+ if (!names->dev) {
+ goto out;
+ }
+
+ do {
+ if (0 == strcmp(names->name, str))
+ r = 1;
+ next = names->next;
+ names = (void *) names + next;
+ } while (next);
+
+ out:
+ dm_task_destroy(dmt);
+ return r;
+}
+
+static void
+signal_daemon (void)
+{
+ FILE *file;
+ pid_t pid;
+ char *buf;
+
+ buf = malloc (8);
+
+ file = fopen (PIDFILE, "r");
+
+ if (!file) {
+ fprintf(stderr, "cannot signal daemon, pidfile not found\n");
+ return;
+ }
+
+ buf = fgets (buf, 8, file);
+ fclose (file);
+
+ pid = (pid_t) atol (buf);
+ free (buf);
+
+ kill(pid, SIGHUP);
+}
+
+static int
+filepresent(char * run) {
+ struct stat buf;
+
+ if(!stat(run, &buf))
+ return 1;
+ return 0;
+}
+
+static void
+usage(char * progname)
+{
+ fprintf(stderr, VERSION_STRING);
+ fprintf(stderr, "Usage: %s [-v|-q] [-d] [-m max_devs]\n",
+ progname);
+ fprintf(stderr, " [-p failover|multibus|group_by_serial]\n" \
+ " [device]\n" \
+ "\n" \
+ "\t-v\t\tverbose, print all paths and multipaths\n" \
+ "\t-q\t\tquiet, no output at all\n" \
+ "\t-d\t\tdry run, do not create or update devmaps\n" \
+ "\t-m max_devs\tscan {max_devs} devices at most\n" \
+ "\n" \
+ "\t-p policy\tforce maps to specified policy :\n" \
+ "\t failover\t\t- 1 path per priority group\n" \
+ "\t multibus\t\t- all paths in 1 priority group\n" \
+ "\t group_by_serial\t- 1 priority group per serial\n" \
+ "\t group_by_tur\t\t- 1 priority group per TUR state\n" \
+ "\n" \
+ "\t-D maj min\tlimit scope to the device's multipath\n" \
+ "\t\t\t(major minor device reference)\n"
+ "\tdevice\t\tlimit scope to the device's multipath\n" \
+ "\t\t\t(hotplug-style $DEVPATH reference)\n"
+ );
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct multipath * mp;
+ struct path * all_paths;
+ struct scsi_dev * all_scsi_ids;
+ struct env conf;
+ int i, k, nmp;
+ int try = 0;
+
+ /* Don't run in parallel */
+ while (filepresent(RUN) && try++ < MAXTRY)
+ usleep(100000);
+
+ if (filepresent(RUN)) {
+ fprintf(stderr, "waited for to long. exiting\n");
+ exit (1);
+ }
+
+ /* Our turn */
+ if (!open(RUN, O_CREAT)) {
+ fprintf(stderr, "can't create runfile\n");
+ exit (1);
+ }
+
+ /* Default behaviour */
+ conf.max_devs = MAX_DEVS;
+ conf.dry_run = 0; /* 1 == Do not Create/Update devmaps */
+ conf.verbose = 0; /* 1 == Print all_paths and mp */
+ conf.quiet = 0; /* 1 == Do not even print devmaps */
+ conf.iopolicy = -1; /* Apply the defaults in get_unique_id() */
+ conf.major = -1;
+ conf.minor = -1;
+
+ for (i = 1; i < argc; ++i) {
+ if (0 == strcmp("-v", argv[i])) {
+ if (conf.quiet == 1)
+ usage(argv[0]);
+ conf.verbose = 1;
+ } else if (0 == strcmp("-m", argv[i])) {
+ conf.max_devs = atoi(argv[++i]);
+ if (conf.max_devs < 2)
+ usage(argv[0]);
+ } else if (0 == strcmp("-D", argv[i])) {
+ conf.major = atoi(argv[++i]);
+ conf.minor = atoi(argv[++i]);
+ } else if (0 == strcmp("-q", argv[i])) {
+ if (conf.verbose == 1)
+ usage(argv[0]);
+ conf.quiet = 1;
+ } else if (0 == strcmp("-d", argv[i]))
+ conf.dry_run = 1;
+ else if (0 == strcmp("-p", argv[i])) {
+ i++;
+ if (!strcmp(argv[i], "failover"))
+ conf.iopolicy = FAILOVER;
+ if (!strcmp(argv[i], "multibus"))
+ conf.iopolicy = MULTIBUS;
+ if (!strcmp(argv[i], "group_by_serial"))
+ conf.iopolicy = GROUP_BY_SERIAL;
+ if (!strcmp(argv[i], "group_by_tur"))
+ conf.iopolicy = GROUP_BY_TUR;
+ } else if (*argv[i] == '-') {
+ fprintf(stderr, "Unknown switch: %s\n", argv[i]);
+ usage(argv[0]);
+ } else
+ strncpy(conf.hotplugdev, argv[i], FILE_NAME_SIZE);
+ }
+
+ /* dynamic allocations */
+ mp = malloc(conf.max_devs * sizeof(struct multipath));
+ all_paths = malloc(conf.max_devs * sizeof(struct path));
+ all_scsi_ids = malloc(conf.max_devs * sizeof(struct scsi_dev));
+ if (mp == NULL || all_paths == NULL || all_scsi_ids == NULL)
+ exit(1);
+
+ if (sysfs_get_mnt_path(conf.sysfs_path, FILE_NAME_SIZE)) {
+ get_all_scsi_ids(&conf, all_scsi_ids);
+ get_all_paths_nosysfs(&conf, all_paths, all_scsi_ids);
+ } else {
+ get_all_paths_sysfs(&conf, all_paths);
+ }
+
+ nmp = coalesce_paths(&conf, mp, all_paths);
+
+ if (conf.verbose) {
+ print_all_path(&conf, all_paths);
+ fprintf(stdout, "\n");
+ print_all_mp(all_paths, mp, nmp);
+ fprintf(stdout, "\n");
+ }
+
+ if (conf.dry_run)
+ exit(0);
+
+ for (k=0; k<=nmp; k++) {
+ if (map_present(mp[k].wwid)) {
+ setup_map(&conf, all_paths, mp, k, DM_DEVICE_RELOAD);
+ } else {
+ setup_map(&conf, all_paths, mp, k, DM_DEVICE_CREATE);
+ }
+ }
+
+ /* signal multipathd that new devmaps may have come up */
+ signal_daemon();
+
+ /* free allocs */
+ free(mp);
+ free(all_paths);
+ free(all_scsi_ids);
+
+ /* release runfile */
+ unlink(RUN);
+
+ exit(0);
+}
diff --git a/extras/multipath-tools/multipath/main.h b/extras/multipath-tools/multipath/main.h
new file mode 100644
index 0000000000..ada7a7394b
--- /dev/null
+++ b/extras/multipath-tools/multipath/main.h
@@ -0,0 +1,131 @@
+/*
+ * Soft: Description here...
+ *
+ * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $
+ *
+ * Author: Copyright (C) 2003 Christophe Varoqui
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#ifndef _MAIN_H
+#define _MAIN_H
+
+/* local includes */
+#include "sg_include.h"
+
+/* exerpt from "sg_err.h" */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SG_ERR_DRIVER_SENSE 0x08
+
+/* exerpt from "scsi.h" */
+#define SCSI_IOCTL_GET_IDLUN 0x5382
+#define SCSI_IOCTL_GET_BUS_NUMBER 0x5386
+
+/* global defs */
+#define WWID_SIZE 33
+#define SERIAL_SIZE 14
+#define MAX_DEVS 128
+#define MAX_MP MAX_DEVS / 2
+#define MAX_MP_PATHS MAX_DEVS / 4
+#define FILE_NAME_SIZE 256
+#define DEF_TIMEOUT 60000
+#define EBUFF_SZ 256
+#define TUR_CMD_LEN 6
+#define DM_TARGET "multipath"
+#define PIDFILE "/var/run/multipathd.pid"
+#define RUN "/var/run/multipath.run"
+#define MAXTRY 50
+
+/* Storage controlers cpabilities */
+#define FAILOVER 0
+#define MULTIBUS 1
+#define GROUP_BY_SERIAL 2
+#define GROUP_BY_TUR 4
+
+#define PINDEX(x,y) mp[(x)].pindex[(y)]
+
+/* global types */
+struct scsi_idlun {
+ int dev_id;
+ int host_unique_id;
+ int host_no;
+};
+
+struct sg_id {
+ int host_no;
+ int channel;
+ int scsi_id;
+ int lun;
+ int scsi_type;
+ short h_cmd_per_lun;
+ short d_queue_depth;
+ int unused1;
+ int unused2;
+};
+
+struct scsi_dev {
+ char dev[FILE_NAME_SIZE];
+ struct scsi_idlun scsi_id;
+ int host_no;
+};
+
+struct path {
+ char dev[FILE_NAME_SIZE];
+ char sg_dev[FILE_NAME_SIZE];
+ struct scsi_idlun scsi_id;
+ struct sg_id sg_id;
+ char wwid[WWID_SIZE];
+ char vendor_id[8];
+ char product_id[16];
+ char rev[4];
+ char serial[SERIAL_SIZE];
+ int iopolicy;
+ int tur;
+};
+
+struct multipath {
+ char wwid[WWID_SIZE];
+ int npaths;
+ long size;
+ int pindex[MAX_MP_PATHS];
+};
+
+struct env {
+ int max_devs;
+ int verbose;
+ int quiet;
+ int dry_run;
+ int iopolicy;
+ int with_sysfs;
+ int major;
+ int minor;
+ char sysfs_path[FILE_NAME_SIZE];
+ char hotplugdev[FILE_NAME_SIZE];
+};
+
+/* Build version */
+#define PROG "multipath"
+
+#define VERSION_CODE 0x000012
+#define DATE_CODE 0x021504
+
+#define MULTIPATH_VERSION(version) \
+ (version >> 16) & 0xFF, \
+ (version >> 8) & 0xFF, \
+ version & 0xFF
+
+#define VERSION_STRING PROG" v%d.%d.%d (%.2d/%.2d, 20%.2d)\n", \
+ MULTIPATH_VERSION(VERSION_CODE), \
+ MULTIPATH_VERSION(DATE_CODE)
+
+#endif
diff --git a/extras/multipath-tools/multipath/multipath.8 b/extras/multipath-tools/multipath/multipath.8
new file mode 100644
index 0000000000..0856ca8dfc
--- /dev/null
+++ b/extras/multipath-tools/multipath/multipath.8
@@ -0,0 +1,66 @@
+.TH MULTIPATH 8 "February 2004" "" "Linux Administrator's Manual"
+.SH NAME
+multipath \- Device mapper target autoconfig
+.SH SYNOPSIS
+.B multipath
+.RB [\| \-v | \-q \|]
+.RB [\| \-d \|]
+.RB [\| \-i\ \c
+.IR int \|]
+.RB [\| \-m\ \c
+.IR max_devs \|]
+.RB [\| \-p\ \c
+.BR failover | multibus | group_by_serial \|]
+.RB [\| device \|]
+.SH DESCRIPTION
+.B multipath
+is used to detect multiple paths to devices for fail-over or performance reasons and coalesces them.
+.SH OPTIONS
+.TP
+.B \-v
+verbose, print all paths and multipaths
+.TP
+.B \-q
+quiet, no output at all
+.TP
+.B \-d
+dry run, do not create or update devmaps
+.TP
+.BI \-i " int"
+multipath target param: polling interval
+.TP
+.BI \-m " max_devs"
+scan
+.I max_devs
+devices at most
+.TP
+.BI \-D " major minor"
+update only the devmap the path pointed by
+.I major minor
+is in
+.TP
+.BI \-p " policy"
+force maps to specified policy:
+.RS 1.2i
+.TP 1.2i
+.B failover
+1 path per priority group
+.TP
+.B multibus
+all paths in 1 priority group
+.TP
+.B group_by_serial
+1 priority group per serial
+.RE
+.TP
+.BI device
+update only the devmap the path pointed by
+.I device
+is in
+.SH "SEE ALSO"
+.BR udev (8),
+.BR dmsetup (8)
+.BR hotplug (8)
+.SH AUTHORS
+.B multipath
+was developed by Christophe Varoqui, <christophe.varoqui@free.fr> and others.
diff --git a/extras/multipath-tools/multipath/multipath.hotplug b/extras/multipath-tools/multipath/multipath.hotplug
new file mode 100644
index 0000000000..5c411d1edf
--- /dev/null
+++ b/extras/multipath-tools/multipath/multipath.hotplug
@@ -0,0 +1,7 @@
+#!/bin/sh
+. /etc/hotplug/hotplug.functions
+
+# wait for sysfs
+sleep 1
+
+mesg `/sbin/multipath $DEVPATH`
diff --git a/extras/multipath-tools/multipath/sg_err.h b/extras/multipath-tools/multipath/sg_err.h
new file mode 100644
index 0000000000..ef57b5ce38
--- /dev/null
+++ b/extras/multipath-tools/multipath/sg_err.h
@@ -0,0 +1,162 @@
+#ifndef SG_ERR_H
+#define SG_ERR_H
+
+/* Feel free to copy and modify this GPL-ed code into your applications. */
+
+/* Version 0.90 (20030519)
+*/
+
+
+/* Some of the following error/status codes are exchanged between the
+ various layers of the SCSI sub-system in Linux and should never
+ reach the user. They are placed here for completeness. What appears
+ here is copied from drivers/scsi/scsi.h which is not visible in
+ the user space. */
+
+#ifndef SCSI_CHECK_CONDITION
+/* Following are the "true" SCSI status codes. Linux has traditionally
+ used a 1 bit right and masked version of these. So now CHECK_CONDITION
+ and friends (in <scsi/scsi.h>) are deprecated. */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_CONDITION_MET 0x4
+#define SCSI_BUSY 0x8
+#define SCSI_IMMEDIATE 0x10
+#define SCSI_IMMEDIATE_CONDITION_MET 0x14
+#define SCSI_RESERVATION_CONFLICT 0x18
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SCSI_TASK_SET_FULL 0x28
+#define SCSI_ACA_ACTIVE 0x30
+#define SCSI_TASK_ABORTED 0x40
+#endif
+
+/* The following are 'host_status' codes */
+#ifndef DID_OK
+#define DID_OK 0x00
+#endif
+#ifndef DID_NO_CONNECT
+#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03 /* Timed out for some other reason */
+#define DID_BAD_TARGET 0x04 /* Bad target (id?) */
+#define DID_ABORT 0x05 /* Told to abort for some other reason */
+#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */
+#define DID_ERROR 0x07 /* Internal error */
+#define DID_RESET 0x08 /* Reset by somebody */
+#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */
+#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */
+#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */
+#endif
+
+/* These defines are to isolate applictaions from kernel define changes */
+#define SG_ERR_DID_OK DID_OK
+#define SG_ERR_DID_NO_CONNECT DID_NO_CONNECT
+#define SG_ERR_DID_BUS_BUSY DID_BUS_BUSY
+#define SG_ERR_DID_TIME_OUT DID_TIME_OUT
+#define SG_ERR_DID_BAD_TARGET DID_BAD_TARGET
+#define SG_ERR_DID_ABORT DID_ABORT
+#define SG_ERR_DID_PARITY DID_PARITY
+#define SG_ERR_DID_ERROR DID_ERROR
+#define SG_ERR_DID_RESET DID_RESET
+#define SG_ERR_DID_BAD_INTR DID_BAD_INTR
+#define SG_ERR_DID_PASSTHROUGH DID_PASSTHROUGH
+#define SG_ERR_DID_SOFT_ERROR DID_SOFT_ERROR
+
+/* The following are 'driver_status' codes */
+#ifndef DRIVER_OK
+#define DRIVER_OK 0x00
+#endif
+#ifndef DRIVER_BUSY
+#define DRIVER_BUSY 0x01
+#define DRIVER_SOFT 0x02
+#define DRIVER_MEDIA 0x03
+#define DRIVER_ERROR 0x04
+#define DRIVER_INVALID 0x05
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_HARD 0x07
+#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */
+
+/* Following "suggests" are "or-ed" with one of previous 8 entries */
+#define SUGGEST_RETRY 0x10
+#define SUGGEST_ABORT 0x20
+#define SUGGEST_REMAP 0x30
+#define SUGGEST_DIE 0x40
+#define SUGGEST_SENSE 0x80
+#define SUGGEST_IS_OK 0xff
+#endif
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+
+/* These defines are to isolate applictaions from kernel define changes */
+#define SG_ERR_DRIVER_OK DRIVER_OK
+#define SG_ERR_DRIVER_BUSY DRIVER_BUSY
+#define SG_ERR_DRIVER_SOFT DRIVER_SOFT
+#define SG_ERR_DRIVER_MEDIA DRIVER_MEDIA
+#define SG_ERR_DRIVER_ERROR DRIVER_ERROR
+#define SG_ERR_DRIVER_INVALID DRIVER_INVALID
+#define SG_ERR_DRIVER_TIMEOUT DRIVER_TIMEOUT
+#define SG_ERR_DRIVER_HARD DRIVER_HARD
+#define SG_ERR_DRIVER_SENSE DRIVER_SENSE
+#define SG_ERR_SUGGEST_RETRY SUGGEST_RETRY
+#define SG_ERR_SUGGEST_ABORT SUGGEST_ABORT
+#define SG_ERR_SUGGEST_REMAP SUGGEST_REMAP
+#define SG_ERR_SUGGEST_DIE SUGGEST_DIE
+#define SG_ERR_SUGGEST_SENSE SUGGEST_SENSE
+#define SG_ERR_SUGGEST_IS_OK SUGGEST_IS_OK
+#define SG_ERR_DRIVER_MASK DRIVER_MASK
+#define SG_ERR_SUGGEST_MASK SUGGEST_MASK
+
+
+
+/* The following "print" functions send ACSII to stdout */
+extern void sg_print_command(const unsigned char * command);
+extern void sg_print_sense(const char * leadin,
+ const unsigned char * sense_buffer, int sb_len);
+extern void sg_print_status(int masked_status);
+extern void sg_print_scsi_status(int scsi_status);
+extern void sg_print_host_status(int host_status);
+extern void sg_print_driver_status(int driver_status);
+
+/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings
+ else it prints to standard output and returns 0. */
+extern int sg_chk_n_print(const char * leadin, int masked_status,
+ int host_status, int driver_status,
+ const unsigned char * sense_buffer, int sb_len);
+
+/* The following function declaration is for the sg version 3 driver.
+ Only version 3 sg_err.c defines it. */
+struct sg_io_hdr;
+extern int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp);
+
+
+/* The following "category" function returns one of the following */
+#define SG_ERR_CAT_CLEAN 0 /* No errors or other information */
+#define SG_ERR_CAT_MEDIA_CHANGED 1 /* interpreted from sense buffer */
+#define SG_ERR_CAT_RESET 2 /* interpreted from sense buffer */
+#define SG_ERR_CAT_TIMEOUT 3
+#define SG_ERR_CAT_RECOVERED 4 /* Successful command after recovered err */
+#define SG_ERR_CAT_SENSE 98 /* Something else is in the sense buffer */
+#define SG_ERR_CAT_OTHER 99 /* Some other error/warning has occurred */
+
+extern int sg_err_category(int masked_status, int host_status,
+ int driver_status, const unsigned char * sense_buffer,
+ int sb_len);
+
+extern int sg_err_category_new(int scsi_status, int host_status,
+ int driver_status, const unsigned char * sense_buffer,
+ int sb_len);
+
+/* The following function declaration is for the sg version 3 driver.
+ Only version 3 sg_err.c defines it. */
+extern int sg_err_category3(struct sg_io_hdr * hp);
+
+/* Returns length of SCSI command given the opcode (first byte) */
+extern int sg_get_command_size(unsigned char opcode);
+
+extern void sg_get_command_name(unsigned char opcode, int buff_len,
+ char * buff);
+
+#endif
diff --git a/extras/multipath-tools/multipath/sg_include.h b/extras/multipath-tools/multipath/sg_include.h
new file mode 100644
index 0000000000..460506826e
--- /dev/null
+++ b/extras/multipath-tools/multipath/sg_include.h
@@ -0,0 +1,43 @@
+#ifdef SG_KERNEL_INCLUDES
+ #define __user
+ typedef unsigned char u8;
+ #include "/usr/src/linux/include/scsi/sg.h"
+ #include "/usr/src/linux/include/scsi/scsi.h"
+#else
+ #ifdef SG_TRICK_GNU_INCLUDES
+ #include <linux/../scsi/sg.h>
+ #include <linux/../scsi/scsi.h>
+ #else
+ #include <scsi/sg.h>
+ #endif
+#endif
+
+/*
+ Getting the correct include files for the sg interface can be an ordeal.
+ In a perfect world, one would just write:
+ #include <scsi/sg.h>
+ #include <scsi/scsi.h>
+ This would include the files found in the /usr/include/scsi directory.
+ Those files are maintained with the GNU library which may or may not
+ agree with the kernel and version of sg driver that is running. Any
+ many cases this will not matter. However in some it might, for example
+ glibc 2.1's include files match the sg driver found in the lk 2.2
+ series. Hence if glibc 2.1 is used with lk 2.4 then the additional
+ sg v3 interface will not be visible.
+ If this is a problem then defining SG_KERNEL_INCLUDES will access the
+ kernel supplied header files (assuming they are in the normal place).
+ The GNU library maintainers and various kernel people don't like
+ this approach (but it does work).
+ The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and
+ was used) prior to glibc 2.2 . Prior to that version /usr/include/linux
+ was a symbolic link to /usr/src/linux/include/linux .
+
+ There are other approaches if this include "mixup" causes pain. These
+ would involve include files being copied or symbolic links being
+ introduced.
+
+ Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES
+ nor SG_TRICK_GNU_INCLUDES is defined.
+
+ dpg 20010415, 20030522
+*/
diff --git a/extras/multipath-tools/multipath/unused.c b/extras/multipath-tools/multipath/unused.c
new file mode 100644
index 0000000000..08144cf717
--- /dev/null
+++ b/extras/multipath-tools/multipath/unused.c
@@ -0,0 +1,95 @@
+static int
+get_serial (int fd, char * str)
+{
+ char buff[MX_ALLOC_LEN + 1];
+ int len;
+
+ if (0 == do_inq(fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, 0)) {
+ len = buff[3];
+ if (len > 0) {
+ memcpy(str, buff + 4, len);
+ buff[len] = '\0';
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int
+do_tur(int fd)
+{
+ unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 };
+ struct sg_io_hdr io_hdr;
+ unsigned char sense_buffer[32];
+
+ memset(&io_hdr, 0, sizeof (struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof (turCmdBlk);
+ io_hdr.mx_sb_len = sizeof (sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = turCmdBlk;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000;
+ io_hdr.pack_id = 0;
+ if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+ close(fd);
+ return 0;
+ }
+ if (io_hdr.info & SG_INFO_OK_MASK) {
+ return 0;
+ }
+ return 1;
+}
+
+static int
+del_map(char * str) {
+ struct dm_task *dmt;
+
+ if (!(dmt = dm_task_create(DM_DEVICE_REMOVE)))
+ return 0;
+ if (!dm_task_set_name(dmt, str))
+ goto delout;
+ if (!dm_task_run(dmt))
+ goto delout;
+
+ printf("Deleted device map : %s\n", str);
+
+ delout:
+ dm_task_destroy(dmt);
+ return 1;
+}
+
+get_table(const char * str)
+{
+ int r = 0;
+ struct dm_task *dmt;
+ void *next = NULL;
+ uint64_t start, length;
+ char *target_type = NULL;
+ char *params;
+
+ if (!(dmt = dm_task_create(DM_DEVICE_TABLE)))
+ return 0;
+
+ if (!dm_task_set_name(dmt, str))
+ goto out;
+
+ if (!dm_task_run(dmt))
+ goto out;
+
+ do {
+ next = dm_get_next_target(dmt, next, &start, &length,
+ &target_type, &params);
+ if (target_type) {
+ printf("%" PRIu64 " %" PRIu64 " %s %s\n",
+ start, length, target_type, params);
+ }
+ } while (next);
+
+ r = 1;
+
+ out:
+ dm_task_destroy(dmt);
+ return r;
+
+}