]> err.no Git - systemd/commitdiff
[PATCH] add scsi_id "extra" program from Patrick Mansfield <patmans@us.ibm.com>
authorgreg@kroah.com <greg@kroah.com>
Thu, 13 Nov 2003 14:34:36 +0000 (06:34 -0800)
committerGreg KH <gregkh@suse.de>
Wed, 27 Apr 2005 04:06:24 +0000 (21:06 -0700)
extras/scsi_id/COPYING [new file with mode: 0644]
extras/scsi_id/Makefile [new file with mode: 0644]
extras/scsi_id/README [new file with mode: 0644]
extras/scsi_id/TODO [new file with mode: 0644]
extras/scsi_id/VERSION [new file with mode: 0644]
extras/scsi_id/scsi.h [new file with mode: 0644]
extras/scsi_id/scsi_id.c [new file with mode: 0644]
extras/scsi_id/scsi_id.config [new file with mode: 0644]
extras/scsi_id/scsi_id.h [new file with mode: 0644]
extras/scsi_id/scsi_serial.c [new file with mode: 0644]

diff --git a/extras/scsi_id/COPYING b/extras/scsi_id/COPYING
new file mode 100644 (file)
index 0000000..60549be
--- /dev/null
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/extras/scsi_id/Makefile b/extras/scsi_id/Makefile
new file mode 100644 (file)
index 0000000..5ad2bcf
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2003 IBM
+#
+# 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; version 2 of the License.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+VERSION=0.1
+
+prefix =
+sbindir =      ${prefix}/sbin
+
+INSTALL  = /usr/bin/install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_DATA  = ${INSTALL} -m 644
+
+CFLAGS=-DVERSION=\"$(VERSION)\" $(DEBUG) -Wall
+
+PROG=scsi_id
+
+LIBSYSFS=-lsysfs
+STRIP=-s
+LDFLAGS=$(STRIP) --static
+
+OBJS= scsi_id.o \
+       scsi_serial.o \
+
+all:   $(PROG)
+
+install: all
+       $(INSTALL_PROGRAM) -D $(PROG) $(sbindir)/$(PROG)
+       
+uninstall:
+       -rm $(sbindir)/$(PROG)
+
+$(OBJS): scsi_id.h scsi.h
+
+clean:
+       rm -f $(PROG) $(OBJS)
+
+$(PROG):       $(OBJS)
+       $(CC) $(OBJS) $(LDFLAGS) $(LIBSYSFS) -o $(PROG)
diff --git a/extras/scsi_id/README b/extras/scsi_id/README
new file mode 100644 (file)
index 0000000..b13cf1e
--- /dev/null
@@ -0,0 +1,19 @@
+scsi_id - generate a SCSI unique identifier for a given SCSI device
+
+Primarily for use with udev callout config entries. This could also be
+used by a multi-path configuration tool that requires SCSI id's.
+
+Requires:
+
+- Linux kernel 2.6
+
+- libsysfs 
+
+No man page yet.
+
+libsysfs 0_2_0 was not installing libsysfs.h or dlist.h, manually copy
+those files to /usr/include/sys before compiling.
+
+Build via make and make install.
+
+Please send questions, comments or patches to patmans@us.ibm.com.
diff --git a/extras/scsi_id/TODO b/extras/scsi_id/TODO
new file mode 100644 (file)
index 0000000..ba52101
--- /dev/null
@@ -0,0 +1,16 @@
+- Investigate shrinking build size: use klibc or uClibc, or copy whatever
+  udev does
+
+- write a man page
+
+- send in kernel patch for REQ_BLOCK_PC, to always set sd and sr set
+  retries (scmd->allowed) to <= 1
+
+- Pull SG_IO code into one .c file.
+- implement callout to device specific serial id code.  The "-c prog" is
+  not implemented.
+
+  This needs an implementation of a device specific callout before it can
+  be completed. Someone with hardware requiring this needs to send in a
+  patch.
diff --git a/extras/scsi_id/VERSION b/extras/scsi_id/VERSION
new file mode 100644 (file)
index 0000000..49d5957
--- /dev/null
@@ -0,0 +1 @@
+0.1
diff --git a/extras/scsi_id/scsi.h b/extras/scsi_id/scsi.h
new file mode 100644 (file)
index 0000000..780e001
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * scsi.h
+ *
+ * General scsi and linux scsi specific defines and structs.
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ *  This library is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as
+ *  published by the Free Software Foundation; either version 2.1 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ *  USA
+ */
+
+#include <scsi/scsi.h>
+
+struct scsi_ioctl_command {
+       unsigned int inlen;     /* excluding scsi command length */
+       unsigned int outlen;
+       unsigned char data[1];
+       /* on input, scsi command starts here then opt. data */
+};
+
+/*
+ * Default 5 second timeout
+ */
+#define DEF_TIMEOUT    5000
+
+#define SENSE_BUFF_LEN 32
+
+/*
+ * SCSI INQUIRY vendor and model (really product) lengths.
+ */
+#define VENDOR_LENGTH  8
+#define        MODEL_LENGTH    16
+
+#define INQUIRY_CMD     0x12
+#define INQUIRY_CMDLEN  6
+
+/*
+ * INQUIRY VPD page 0x83 identifier descriptor related values. Reference the
+ * SCSI Primary Commands specification for details.
+ */
+
+/*
+ * id type values of id descriptors. These are assumed to fit in 4 bits.
+ */
+#define SCSI_ID_VENDOR_SPECIFIC        0
+#define SCSI_ID_T10_VENDOR     1
+#define SCSI_ID_EUI_64         2
+#define SCSI_ID_NAA            3
+
+/*
+ * Supported NAA values. These fit in 4 bits, so the "don't care" value
+ * cannot conflict with real values.
+ */
+#define        SCSI_ID_NAA_DONT_CARE           0xff
+#define        SCSI_ID_NAA_IEEE_REG            5
+#define        SCSI_ID_NAA_IEEE_REG_EXTENDED   6
+
+/*
+ * Supported Code Set values.
+ */
+#define        SCSI_ID_BINARY  1
+#define        SCSI_ID_ASCII   2
+
+struct scsi_id_search_values {
+       u_char  id_type;
+       u_char  naa_type;
+       u_char  code_set;
+};
+
+/*
+ * 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
diff --git a/extras/scsi_id/scsi_id.c b/extras/scsi_id/scsi_id.c
new file mode 100644 (file)
index 0000000..d34d928
--- /dev/null
@@ -0,0 +1,827 @@
+/*
+ * scsi_id.c
+ *
+ * Main section of the scsi_id program
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ *  This library is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as
+ *  published by the Free Software Foundation; either version 2.1 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ *  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/libsysfs.h>
+#include "scsi_id.h"
+
+#ifndef VERSION
+#warning No version
+#define VERSION        "unknown"
+#endif
+
+/*
+ * temporary names for mknod.
+ */
+#define TMP_DIR        "/tmp"
+#define TMP_PREFIX "scsi"
+
+#define CONFIG_FILE "/etc/scsi_id.config"
+
+#define        MAX_NAME_LEN    72
+
+#define        MAX_SERIAL_LEN  128
+
+static const char short_options[] = "bc:d:ef:gip:s:vV";
+static const struct option long_options[] = {
+       {"broken",      no_argument,            NULL,   'b'}, /* also per dev */
+       {"callout",     required_argument,      NULL,   'c'}, /* also per dev */
+       {"device",      required_argument,      NULL,   'd'},
+       {"stderr",      no_argument,            NULL,   'e'},
+       {"file",        required_argument,      NULL,   'f'},
+       {"good",        no_argument,            NULL,   'g'}, /* also per dev */
+       {"busid",       no_argument,            NULL,   'i'}, /* also per dev */
+       {"page",        required_argument,      NULL,   'p'}, /* also per dev */
+       {"devpath",     required_argument,      NULL,   's'},
+       {"verbose",     no_argument,            NULL,   'v'},
+       {"version",     no_argument,            NULL,   'V'},
+       {0, 0, 0, 0}
+};
+/*
+ * Just duplicate per dev options.
+ */
+static const char dev_short_options[] = "bc:gp:";
+static const struct option dev_long_options[] = {
+       {"broken",      no_argument,            NULL,   'b'}, /* also per dev */
+       {"callout",     required_argument,      NULL,   'c'}, /* also per dev */
+       {"good",        no_argument,            NULL,   'g'}, /* also per dev */
+       {"page",        required_argument,      NULL,   'p'}, /* also per dev */
+       {0, 0, 0, 0}
+};
+
+char sysfs_mnt_path[SYSFS_PATH_MAX];
+
+static int all_good;
+static char *default_callout;
+static int dev_specified;
+static int sys_specified;
+static char config_file[MAX_NAME_LEN] = CONFIG_FILE;
+static int display_bus_id;
+static int default_page_code;
+static int use_stderr;
+static int debug;
+static int hotplug_mode;
+
+void log_message (int level, const char *format, ...)
+{
+       va_list args;
+
+       if (!debug && level == LOG_DEBUG)
+               return;
+
+       va_start (args, format);
+       if (!hotplug_mode || use_stderr) {
+               vfprintf(stderr, format, args);
+       } else {
+               static int logging_init = 0;
+               if (!logging_init) {
+                       openlog ("scsi_id", LOG_PID, LOG_DAEMON);
+                       logging_init = 1;
+               }
+
+               vsyslog(level, format, args);
+       }
+       va_end (args);
+       return;
+}
+
+static int sysfs_get_actual_dev(const char *sysfs_path, char *dev, int len)
+{
+       dprintf("%s\n", sysfs_path);
+       strncpy(dev, sysfs_path, len);
+       strncat(dev, "/device", len);
+       if (sysfs_get_link(dev, dev, len)) {
+               if (!hotplug_mode)
+                       log_message(LOG_WARNING, "%s: %s\n", dev,
+                                   strerror(errno));
+               return -1;
+       }
+       return 0;
+}
+
+/*
+ * sysfs_is_bus: Given the sysfs_path to a device, return 1 if sysfs_path
+ * is on bus, 0 if not on bus, and < 0 on error
+ */
+static int sysfs_is_bus(const char *sysfs_path, const char *bus)
+{
+       char bus_dev_name[SYSFS_PATH_MAX];
+       char bus_id[SYSFS_NAME_LEN];
+       struct stat stat_buf;
+       ino_t dev_inode;
+
+       dprintf("%s\n", sysfs_path);
+
+       if (sysfs_get_name_from_path(sysfs_path, bus_id, SYSFS_NAME_LEN))
+               return -1;
+
+       snprintf(bus_dev_name, MAX_NAME_LEN, "%s/%s/%s/%s/%s", sysfs_mnt_path,
+                SYSFS_BUS_DIR, bus, SYSFS_DEVICES_NAME, bus_id);
+
+       if (stat(sysfs_path, &stat_buf))
+               return -1;
+       dev_inode = stat_buf.st_ino;
+
+       if (stat(bus_dev_name, &stat_buf)) {
+               if (errno == ENOENT)
+                       return 0;
+               else
+                       return -1;
+       }
+       if (dev_inode == stat_buf.st_ino)
+               return 1;
+       else
+               return 0;
+}
+
+static int get_major_minor(const char *devpath, int *major, int *minor)
+{
+       struct sysfs_class_device *class_dev;
+       char dev_value[SYSFS_NAME_LEN];
+       char *dev;
+
+       dprintf("%s\n", devpath);
+       class_dev = sysfs_open_class_device(devpath);
+       if (!class_dev) {
+               log_message(LOG_WARNING, "open class %s failed: %s\n", devpath,
+                           strerror(errno));
+               return -1;
+       }
+
+       dev = sysfs_get_attr(class_dev, "dev");
+       if (dev)
+               strncpy(dev_value, dev, SYSFS_NAME_LEN);
+       sysfs_close_class_device(class_dev);
+       if (!dev) {
+               /*
+                * XXX This happens a lot, since sg has no dev attr.
+                * Someday change this back to a LOG_WARNING.
+                */
+               log_message(LOG_DEBUG, "%s could not get dev attribute: %s\n",
+                       devpath, strerror(errno));
+               return -1;
+       }
+       dev = NULL;
+
+       dprintf("dev %s", dev_value); /* dev_value has a trailing \n */
+       if (sscanf(dev_value, "%u:%u", major, minor) != 2) {
+               log_message(LOG_WARNING, "%s: invalid dev major/minor\n",
+                           devpath);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int create_tmp_dev(const char *devpath, char *tmpdev, int dev_type)
+{
+       int major, minor;
+
+       dprintf("(%s)\n", devpath);
+
+       if (get_major_minor(devpath, &major, &minor))
+               return -1;
+       snprintf(tmpdev, MAX_NAME_LEN, "%s/%s-maj%d-min%d-%u",
+                TMP_DIR, TMP_PREFIX, major, minor, getpid());
+
+       dprintf("tmpdev '%s'\n", tmpdev);
+
+       if (mknod(tmpdev, 0600 | dev_type, makedev(major, minor))) {
+               log_message(LOG_WARNING, "mknod failed: %s\n", strerror(errno));
+               return -1;
+       }
+       return 0;
+}
+
+static int has_sysfs_prefix(const char *path, const char *prefix)
+{
+       char match[MAX_NAME_LEN];
+
+       strncpy(match, sysfs_mnt_path, MAX_NAME_LEN);
+       strncat(match, prefix, MAX_NAME_LEN);
+       if (strncmp(path, match, strlen(match)) == 0)
+               return 1;
+       else
+               return 0;
+}
+
+/*
+ * get_value:
+ *
+ * buf points to an '=' followed by a quoted string ("foo") or a string ending
+ * with a space or ','.
+ *
+ * Return a pointer to the NUL terminated string, returns NULL if no
+ * matches.
+ */
+static char *get_value(char **buffer)
+{
+       static char *quote_string = "\"\n";
+       static char *comma_string = ",\n";
+       char *val;
+       char *end;
+
+       if (**buffer == '"') {
+               /*
+                * skip leading quote, terminate when quote seen
+                */
+               (*buffer)++;
+               end = quote_string;
+       } else {
+               end = comma_string;
+       }
+       val = strsep(buffer, end);
+       if (val && end == quote_string)
+               /*
+                * skip trailing quote
+                */
+               (*buffer)++;
+
+       while (isspace(**buffer))
+               (*buffer)++;
+
+       return val;
+}
+
+static int argc_count(char *opts)
+{
+       int i = 0;
+       while (*opts != '\0')
+               if (*opts++ == ' ')
+                       i++;
+       return i;
+}
+
+/*
+ * get_file_options:
+ *
+ * If vendor == NULL, find a line in the config file with only "OPTIONS=";
+ * if vendor and model are set find the first OPTIONS line in the config
+ * file that matches. Set argc and argv to match the OPTIONS string.
+ *
+ * vendor and model can end in '\n'.
+ */
+static int get_file_options(char *vendor, char *model, int *argc,
+                           char ***newargv)
+{
+       char buffer[256];
+       FILE *fd;
+       char *buf;
+       char *str1;
+       char *vendor_in, *model_in, *options_in; /* read in from file */
+       int lineno;
+       int c;
+       int retval = 0;
+       static char *prog_string = "arg0";
+
+       dprintf("vendor='%s'; model='%s'\n", vendor, model);
+       fd = fopen(config_file, "r");
+       if (fd == NULL) {
+               dprintf("can't open %s\n", config_file);
+               if (errno == ENOENT) {
+                       return 1;
+               } else {
+                       log_message(LOG_WARNING, "can't open %s: %s\n",
+                               config_file, strerror(errno));
+                       return -1;
+               }
+       }
+
+       *newargv = NULL;
+       lineno = 0;
+
+       while (1) {
+               vendor_in = model_in = options_in = NULL;
+
+               buf = fgets(buffer, sizeof(buffer), fd);
+               if (buf == NULL)
+                       break;
+               lineno++;
+
+               while (isspace(*buf))
+                       buf++;
+
+               if (*buf == '\0')
+                       /*
+                        * blank or all whitespace line
+                        */
+                       continue;
+
+               if (*buf == '#')
+                       /*
+                        * comment line
+                        */
+                       continue;
+
+#ifdef LOTS
+               dprintf("lineno %d: '%s'\n", lineno, buf);
+#endif
+               str1 = strsep(&buf, "=");
+               if (str1 && strcasecmp(str1, "VENDOR") == 0) {
+                       str1 = get_value(&buf);
+                       if (!str1) {
+                               retval = -1;
+                               break;
+                       }
+                       vendor_in = str1;
+
+                       str1 = strsep(&buf, "=");
+                       if (str1 && strcasecmp(str1, "MODEL") == 0) {
+                               str1 = get_value(&buf);
+                               if (!str1) {
+                                       retval = -1;
+                                       break;
+                               }
+                               model_in = str1;
+                               str1 = strsep(&buf, "=");
+                       }
+               }
+
+               if (str1 && strcasecmp(str1, "OPTIONS") == 0) {
+                       str1 = get_value(&buf);
+                       if (!str1) {
+                               retval = -1;
+                               break;
+                       }
+                       options_in = str1;
+               }
+               dprintf("config file line %d:"
+                       " vendor '%s'; model '%s'; options '%s'\n",
+                       lineno, vendor_in, model_in, options_in);
+               /*
+                * Only allow: [vendor=foo[,model=bar]]options=stuff
+                */
+               if (!options_in || (!vendor_in && model_in)) {
+                       log_message(LOG_WARNING,
+                                   "Error parsing config file line %d '%s'\n",
+                                   lineno, buffer);
+                       retval = -1;
+                       break;
+               }
+               if (vendor == NULL) {
+                       if (vendor_in == NULL) {
+                               dprintf("matched global option\n");
+                               break;
+                       }
+               } else if ((vendor_in && strncmp(vendor, vendor_in,
+                                                strlen(vendor_in)) == 0) &&
+                          (!model_in || (strncmp(model, model_in,
+                                                 strlen(model_in)) == 0))) {
+                               /*
+                                * Matched vendor and optionally model.
+                                *
+                                * Note: a short vendor_in or model_in can
+                                * give a partial match (that is FOO
+                                * matches FOOBAR).
+                                */
+                               dprintf("matched vendor/model\n");
+                               break;
+               } else {
+                       dprintf("no match\n");
+               }
+       }
+
+       if (retval == 0) {
+               if (vendor_in != NULL || model_in != NULL ||
+                   options_in != NULL) {
+                       /*
+                        * Something matched. Allocate newargv, and store
+                        * values found in options_in.
+                        */
+                       c = argc_count(options_in) + 2;
+                       *newargv = calloc(c, sizeof(**newargv));
+                       if (!*newargv) {
+                               log_message(LOG_WARNING,
+                                           "Can't allocate memory\n");
+                               retval = -1;
+                       } else {
+                               *argc = c;
+                               c = 0;
+                               (*newargv)[c] = prog_string; /* nothing */
+                               for (c = 1; c < *argc; c++)
+                                       (*newargv)[c] = strsep(&options_in, " ");
+                       }
+               } else {
+                       /*
+                        * No matches.
+                        */
+                       retval = 1;
+               }
+       }
+       fclose(fd);
+       return retval;
+}
+
+static int set_options(int argc, char **argv, const char *short_opts,
+                      const struct option *long_opts, char *target,
+                      char *maj_min_dev)
+{
+       int option;
+       int option_ind;
+
+       /*
+        * optind is a global extern used by getopt_long. Since we can
+        * call set_options twice (once for command line, and once for
+        * config file) we have to reset this back to 0.
+        */
+       optind = 0;
+       while (1) {
+               option = getopt_long(argc, argv, short_options, long_options,
+                                    &option_ind);
+               if (option == -1)
+                       break;
+
+               if (optarg)
+                       dprintf("option '%c' arg '%s'\n", option, optarg);
+               else
+                       dprintf("option '%c'\n", option);
+
+               switch (option) {
+               case 'b':
+                       all_good = 0;
+                       break;
+
+               case 'c':
+                       default_callout = optarg;
+                       break;
+
+               case 'd':
+                       dev_specified = 1;
+                       strncpy(maj_min_dev, optarg, MAX_NAME_LEN);
+                       break;
+
+               case 'e':
+                       use_stderr = 1;
+                       break;
+
+               case 'f':
+                       strncpy(config_file, optarg, MAX_NAME_LEN);
+                       break;
+
+               case 'g':
+                       all_good = 1;
+                       break;
+
+               case 'i':
+                       display_bus_id = 1;
+                       break;
+
+               case 'p':
+                       if (strcmp(optarg, "0x80") == 0) {
+                               default_page_code = 0x80;
+                       } else if (strcmp(optarg, "0x83") == 0) {
+                               default_page_code = 0x83;
+                       } else {
+                               log_message(LOG_WARNING,
+                                           "Unknown page code '%s'\n", optarg);
+                               return -1;
+                       }
+                       break;
+
+               case 's':
+                       sys_specified = 1;
+                       strncpy(target, sysfs_mnt_path, MAX_NAME_LEN);
+                       strncat(target, optarg, MAX_NAME_LEN);
+                       break;
+
+               case 'v':
+                       debug++;
+                       break;
+
+               case 'V':
+                       log_message(LOG_WARNING, "scsi_id version: %s\n",
+                                   VERSION);
+                       exit(0);
+                       break;
+
+               default:
+                       log_message(LOG_WARNING,
+                                   "Unknown or bad option '%c' (0x%x)\n",
+                                   option, option);
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+static int per_dev_options(struct sysfs_class_device *scsi_dev, int *good_bad,
+                          int *page_code, char *callout)
+{
+       int retval;
+       int newargc;
+       char **newargv = NULL;
+       char *vendor;
+       char *model;
+       int option;
+       int option_ind;
+
+
+       *good_bad = all_good;
+       *page_code = default_page_code;
+       if (default_callout && (callout != default_callout))
+               strncpy(callout, default_callout, MAX_NAME_LEN);
+       else
+               callout[0] = '\0';
+
+       vendor = sysfs_get_attr(scsi_dev, "vendor");
+       if (!vendor) {
+               log_message(LOG_WARNING, "%s: no vendor attribute\n",
+                           scsi_dev->name);
+               return -1;
+       }
+
+       model = sysfs_get_attr(scsi_dev, "model");
+       if (!vendor) {
+               log_message(LOG_WARNING, "%s: no model attribute\n",
+                           scsi_dev->name);
+               return -1;
+       }
+
+       retval = get_file_options(vendor, model, &newargc, &newargv);
+
+       optind = 0; /* global extern, reset to 0 */
+       while (retval == 0) {
+               option = getopt_long(newargc, newargv, dev_short_options,
+                                    dev_long_options, &option_ind);
+               if (option == -1)
+                       break;
+
+               if (optarg)
+                       dprintf("option '%c' arg '%s'\n", option, optarg);
+               else
+                       dprintf("option '%c'\n", option);
+
+               switch (option) {
+               case 'b':
+                       *good_bad = 0;
+                       break;
+
+               case 'c':
+                       strncpy(callout, default_callout, MAX_NAME_LEN);
+                       break;
+
+               case 'g':
+                       *good_bad = 1;
+                       break;
+
+               case 'p':
+                       if (strcmp(optarg, "0x80") == 0) {
+                               *page_code = 0x80;
+                       } else if (strcmp(optarg, "0x83") == 0) {
+                               *page_code = 0x83;
+                       } else {
+                               log_message(LOG_WARNING,
+                                           "Unknown page code '%s'\n", optarg);
+                               retval = -1;
+                       }
+                       break;
+
+               default:
+                       log_message(LOG_WARNING,
+                                   "Unknown or bad option '%c' (0x%x)\n",
+                                   option, option);
+                       retval = -1;
+                       break;
+               }
+       }
+
+       if (newargv)
+               free(newargv);
+       return retval;
+}
+
+/*
+ * scsi_id: try to get an id, if one is found, printf it to stdout.
+ * returns a value passed to exit() - 0 if printed an id, else 1. This
+ * could be expanded, for example, if we want to report a failure like no
+ * memory etc. return 2, and return 1 for expected cases (like broken
+ * device found) that do not print an id.
+ */
+static int scsi_id(const char *target_path, char *maj_min_dev)
+{
+       int retval;
+       int dev_type = 0;
+       char full_dev_path[MAX_NAME_LEN];
+       char serial[MAX_SERIAL_LEN];
+       struct sysfs_class_device *scsi_dev; /* of scsi_device full_dev_path */
+       int good_dev;
+       int page_code;
+       char callout[MAX_NAME_LEN];
+
+       dprintf("target_path %s\n", target_path);
+
+       /*
+        * Ugly: depend on the sysfs path to tell us whether this is a
+        * block or char device. This should probably be encoded in the
+        * "dev" along with the major/minor.
+        */
+       if (has_sysfs_prefix(target_path, "/block")) {
+               dev_type = S_IFBLK;
+       } else if (has_sysfs_prefix(target_path, "/class")) {
+               dev_type = S_IFCHR;
+       } else {
+               if (!hotplug_mode) {
+                       log_message(LOG_WARNING,
+                                   "Non block or class device '%s'\n",
+                                   target_path);
+                       return 1;
+               } else {
+                       /*
+                        * Expected in some cases.
+                        */
+                       dprintf("Non block or class device\n");
+                       return 0;
+               }
+       }
+
+       if (sysfs_get_actual_dev(target_path, full_dev_path, MAX_NAME_LEN))
+               return 1;
+
+       dprintf("full_dev_path %s\n", full_dev_path);
+
+       /*
+        * Allow only scsi devices (those that have a matching device
+        * under /bus/scsi/devices).
+        *
+        * Other block devices can support SG IO, but only ide-cd does, so
+        * for now, don't bother with anything else.
+        */
+       retval = sysfs_is_bus(full_dev_path, "scsi");
+       if (retval == 0) {
+               if (hotplug_mode)
+                       /*
+                        * Expected in some cases.
+                        */
+                       dprintf("%s is not a scsi device\n", target_path);
+               else
+                       log_message(LOG_WARNING, "%s is not a scsi device\n",
+                                   target_path);
+               return 1;
+       } else if (retval < 0) {
+               log_message(LOG_WARNING, "sysfs_is_bus failed: %s\n",
+                       strerror(errno));
+               return 1;
+       }
+
+       /*
+        * mknod a temp dev to communicate with the device.
+        */
+       if (!dev_specified && create_tmp_dev(target_path, maj_min_dev,
+                                            dev_type)) {
+               dprintf("create_tmp_dev failed\n");
+               return 1;
+       }
+
+       scsi_dev = sysfs_open_class_device(full_dev_path);
+       if (!scsi_dev) {
+               log_message(LOG_WARNING, "open class %s failed: %s\n",
+                           full_dev_path, strerror(errno));
+               return 1;
+       }
+
+       /*
+        * Get any per device (vendor + model) options from the config
+        * file.
+        */
+       retval = per_dev_options(scsi_dev, &good_dev, &page_code, callout);
+       dprintf("per dev options: good %d; page code 0x%x; callout '%s'\n",
+               good_dev, page_code, callout);
+
+       if (!good_dev) {
+               retval = 1;
+       } else if (callout[0] != '\0') {
+               /*
+                * exec vendor callout, pass it only the "name" to be used
+                * for error messages, and the dev to open.
+                *
+                * This won't work if we need to pass on the original
+                * command line (when not hotplug mode) since the option
+                * parsing and per dev parsing modify the argv's.
+                *
+                * XXX Not implemented yet. And not fully tested ;-)
+                */
+               retval = 1;
+       } else if (scsi_get_serial(scsi_dev, maj_min_dev, page_code,
+                                  serial, MAX_SERIAL_LEN)) {
+               retval = 1;
+       } else {
+               retval = 0;
+       }
+       if (!retval) {
+               if (display_bus_id)
+                       printf("%s ", scsi_dev->name);
+               printf("%s", serial);
+               if (!hotplug_mode)
+                       printf("\n");
+               dprintf("%s\n", serial);
+               retval = 0;
+       }
+       fflush(stdout);
+       sysfs_close_class_device(scsi_dev);
+
+       if (!dev_specified)
+               unlink(maj_min_dev);
+
+       return retval;
+}
+
+int main(int argc, char **argv)
+{
+       int retval;
+       char *devpath;
+       char target_path[MAX_NAME_LEN];
+       char maj_min_dev[MAX_NAME_LEN];
+       int newargc;
+       char **newargv;
+
+       if (getenv("DEBUG"))
+               debug++;
+
+       if ((argc == 2) && (argv[1][0] != '-')) {
+               hotplug_mode = 1;
+               dprintf("hotplug assumed\n");
+       }
+
+       dprintf("argc is %d\n", argc);
+       if (sysfs_get_mnt_path(sysfs_mnt_path, MAX_NAME_LEN)) {
+               log_message(LOG_WARNING, "sysfs_get_mnt_path failed: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       if (hotplug_mode) {
+               /*
+                * There is a kernel race creating attributes, if called
+                * directly, uncomment the sleep.
+                */
+               /* sleep(1); */
+
+               devpath = getenv("DEVPATH");
+               if (!devpath) {
+                       log_message(LOG_WARNING, "DEVPATH is not set\n");
+                       exit(1);
+               }
+               sys_specified = 1;
+
+               strncpy(target_path, sysfs_mnt_path, MAX_NAME_LEN);
+               strncat(target_path, devpath, MAX_NAME_LEN);
+       } else {
+               if (set_options(argc, argv, short_options, long_options,
+                               target_path, maj_min_dev) < 0)
+                       exit(1);
+       }
+
+       /*
+        * Override any command line options set via the config file. This
+        * is the only way to set options when in hotplug mode.
+        */
+       newargv = NULL;
+       retval = get_file_options(NULL, NULL, &newargc, &newargv);
+       if (retval < 0) {
+               exit(1);
+       } else if (newargv && (retval == 0)) {
+               if (set_options(newargc, newargv, short_options, long_options,
+                               target_path, maj_min_dev) < 0)
+                       exit(1);
+               free(newargv);
+       }
+
+       if (!sys_specified) {
+               log_message(LOG_WARNING, "-s must be specified\n");
+               exit(1);
+       }
+
+       retval = scsi_id(target_path, maj_min_dev);
+       exit(retval);
+}
diff --git a/extras/scsi_id/scsi_id.config b/extras/scsi_id/scsi_id.config
new file mode 100644 (file)
index 0000000..4fdb89e
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# Informational and example scsi_id.config file for use with scsi_id. 
+#
+
+# General syntax is:
+#
+# lower or upper case has no affect on the left side. Quotes (") are
+# required if you need spaces in values. Model is the same as the SCSI
+# INQUIRY product identification field. Per the SCSI INQUIRY, the vendor
+# is limited to 8 bytes, model to 16 bytes.
+#
+# The first maching line found is used. Short matches match longer ones,
+# if you do not want such a match space fill the extra bytes. If no model
+# is specified, only the vendor string need match.
+#
+# The "option" line is searched when scsi_id first starts up (for use with
+# hotplug during boot).
+#
+# options=<any scsi_id command line options>
+#
+# vendor=string[,model=string],options=<per-device scsi_id options>
+
+#
+# If you normally don't need id's, black list everyone:
+#
+options=-b
+
+#
+# Then white list devices on your system that have correct and useful id's:
+#
+vendor=someone, model=nicedrive, options=-g
+
+# If you have all good devices on your system use, mark all as good:
+
+## options=-g
+
+# Then black list any offenders. Missing entries here could be dangerous
+# if you rely on the id for naming or multi-path configuration!
+
+## vendor=ELBONIA, model=borken, options=-b
diff --git a/extras/scsi_id/scsi_id.h b/extras/scsi_id/scsi_id.h
new file mode 100644 (file)
index 0000000..8be492b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * scsi_id.h
+ *
+ * General defines and such for scsi_id
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ *  This library is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as
+ *  published by the Free Software Foundation; either version 2.1 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ *  USA
+ */
+
+#define dprintf(format, arg...) \
+       log_message(LOG_DEBUG, "%s: " format, __FUNCTION__, ## arg)
+
+#define        MAX_NAME_LEN    72
+#define OFFSET (2 * sizeof(unsigned int))
+
+static inline char *sysfs_get_attr(struct sysfs_class_device *dev,
+                                   const char *attr)
+{
+       return sysfs_get_value_from_attributes(dev->directory->attributes,
+                                              attr);
+}
+
+extern int scsi_get_serial (struct sysfs_class_device *scsi_dev,
+                           const char *devname, int page_code, char *serial,
+                           int len);
+extern void log_message (int level, const char *format, ...)
+       __attribute__ ((format (printf, 2, 3)));
+
diff --git a/extras/scsi_id/scsi_serial.c b/extras/scsi_id/scsi_serial.c
new file mode 100644 (file)
index 0000000..302429c
--- /dev/null
@@ -0,0 +1,735 @@
+/*
+ * scsi_serial.c
+ *
+ * Code related to requesting and getting an id from a scsi device
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ *  This library is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as
+ *  published by the Free Software Foundation; either version 2.1 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ *  USA
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <scsi/sg.h>
+#include <sys/libsysfs.h>
+#include "scsi_id.h"
+#include "scsi.h"
+
+/*
+ * A priority based list of id, naa, and binary/ascii for the identifier
+ * descriptor in VPD page 0x83.
+ *
+ * Brute force search for a match starting with the first value in the
+ * following id_search_list. This is not a performance issue, since there
+ * is normally one or some small number of descriptors.
+ */
+static const struct scsi_id_search_values id_search_list[] = {
+       { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG_EXTENDED,  SCSI_ID_BINARY },
+       { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG_EXTENDED,  SCSI_ID_ASCII },
+       { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG,   SCSI_ID_BINARY },
+       { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG,   SCSI_ID_ASCII },
+       /*
+        * Devices already exist using NAA values that are now marked
+        * reserved. These should not conflict with other values, or it is
+        * a bug in the device. As long as we find the IEEE extended one
+        * first, we really don't care what other ones are used. Using
+        * don't care here means that a device that returns multiple
+        * non-IEEE descriptors in a random order will get different
+        * names.
+        */
+       { SCSI_ID_NAA,  SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
+       { SCSI_ID_NAA,  SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
+       { SCSI_ID_EUI_64,       SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
+       { SCSI_ID_EUI_64,       SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
+       { SCSI_ID_T10_VENDOR,   SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
+       { SCSI_ID_T10_VENDOR,   SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
+       { SCSI_ID_VENDOR_SPECIFIC,      SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
+       { SCSI_ID_VENDOR_SPECIFIC,      SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
+};
+
+static const char hex_str[]="0123456789abcdef";
+
+/*
+ * XXX maybe move all these to an sg_io.c file.
+ *
+ * From here ...
+ */
+
+/*
+ * Values returned in the result/status, only the ones used by the code
+ * are used here.
+ */
+
+#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 DRIVER_TIMEOUT 0x06
+#define DRIVER_SENSE 0x08       /* Sense_buffer has been set */
+
+/* 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 in the sense buffer */
+#define SG_ERR_CAT_OTHER       99     /* Some other error/warning */
+
+static int sg_err_category_new(int scsi_status, int msg_status, int
+                              host_status, int driver_status, const
+                              unsigned char *sense_buffer, int sb_len)
+{
+       scsi_status &= 0x7e;
+
+       /*
+        * XXX change to return only two values - failed or OK.
+        */
+
+       /*
+        * checks msg_status
+        */
+       if (!scsi_status && !msg_status && !host_status && !driver_status)
+               return SG_ERR_CAT_CLEAN;
+
+       if ((scsi_status == SCSI_CHECK_CONDITION) ||
+           (scsi_status == SCSI_COMMAND_TERMINATED) ||
+           ((driver_status & 0xf) == DRIVER_SENSE)) {
+               if (sense_buffer && (sb_len > 2)) {
+                       int sense_key;
+                       unsigned char asc;
+
+                       if (sense_buffer[0] & 0x2) {
+                               sense_key = sense_buffer[1] & 0xf;
+                               asc = sense_buffer[2];
+                       } else {
+                               sense_key = sense_buffer[2] & 0xf;
+                               asc = (sb_len > 12) ? sense_buffer[12] : 0;
+                       }
+
+                       if (sense_key == RECOVERED_ERROR)
+                               return SG_ERR_CAT_RECOVERED;
+                       else if (sense_key == UNIT_ATTENTION) {
+                               if (0x28 == asc)
+                                       return SG_ERR_CAT_MEDIA_CHANGED;
+                               if (0x29 == asc)
+                                       return SG_ERR_CAT_RESET;
+                       }
+               }
+               return SG_ERR_CAT_SENSE;
+       }
+       if (!host_status) {
+               if ((host_status == DID_NO_CONNECT) ||
+                   (host_status == DID_BUS_BUSY) ||
+                   (host_status == DID_TIME_OUT))
+                       return SG_ERR_CAT_TIMEOUT;
+       }
+       if (!driver_status) {
+               if (driver_status == DRIVER_TIMEOUT)
+                       return SG_ERR_CAT_TIMEOUT;
+       }
+       return SG_ERR_CAT_OTHER;
+}
+
+static int sg_err_category3(struct sg_io_hdr *hp)
+{
+       return sg_err_category_new(hp->status, hp->msg_status,
+                                  hp->host_status, hp->driver_status,
+                                  hp->sbp, hp->sb_len_wr);
+}
+
+static int scsi_dump_sense(struct sysfs_class_device *scsi_dev,
+                          struct sg_io_hdr *io)
+{
+       unsigned char *sense_buffer;
+       int s;
+       int sb_len;
+       int code;
+       int sense_class;
+       int sense_key;
+       int descriptor_format;
+       int asc, ascq;
+#ifdef DUMP_SENSE
+       char out_buffer[256];
+       int i, j;
+#endif
+
+       /*
+        * Figure out and print the sense key, asc and ascq.
+        *
+        * If you want to suppress these for a particular drive model, add
+        * a black list entry in the scsi_id config file.
+        *
+        * XXX We probably need to: lookup the sense/asc/ascq in a retry
+        * table, and if found return 1 (after dumping the sense, asc, and
+        * ascq). So, if/when we get something like a power on/reset,
+        * we'll retry the command.
+        */
+
+       dprintf("got check condition\n");
+
+       sb_len = io->sb_len_wr;
+       if (sb_len < 1) {
+               log_message(LOG_WARNING, "%s: sense buffer empty\n",
+                           scsi_dev->name);
+               return -1;
+       }
+
+       sense_buffer = io->sbp;
+       sense_class = (sense_buffer[0] >> 4) & 0x07;
+       code = sense_buffer[0] & 0xf;
+
+       if (sense_class == 7) {
+               /*
+                * extended sense data.
+                */
+               s = sense_buffer[7] + 8;
+               if (sb_len < s) {
+                       log_message(LOG_WARNING,
+                                   "%s: sense buffer too small %d bytes,"
+                                   " %d bytes too short\n", scsi_dev->name,
+                                   sb_len, s - sb_len);
+                       return -1;
+               }
+               if ((code == 0x0) || (code == 0x1)) {
+                       descriptor_format = 0;
+                       sense_key = sense_buffer[2] & 0xf;
+                       if (s < 14) {
+                               /*
+                                * Possible?
+                                */
+                               log_message(LOG_WARNING, "%s: sense result too"
+                                           " small %d bytes\n",
+                                           scsi_dev->name, s);
+                               return -1;
+                       }
+                       asc = sense_buffer[12];
+                       ascq = sense_buffer[13];
+               } else if ((code == 0x2) || (code == 0x3)) {
+                       descriptor_format = 1;
+                       sense_key = sense_buffer[1] & 0xf;
+                       asc = sense_buffer[2];
+                       ascq = sense_buffer[3];
+               } else {
+                       log_message(LOG_WARNING,
+                                   "%s: invalid sense code 0x%x\n",
+                                   scsi_dev->name, code);
+                       return -1;
+               }
+               log_message(LOG_WARNING,
+                           "%s: sense key 0x%x ASC 0x%x ASCQ 0x%x\n",
+                           scsi_dev->name, sense_key, asc, ascq);
+       } else {
+               if (sb_len < 4) {
+                       log_message(LOG_WARNING,
+                                   "%s: sense buffer too small %d bytes, %d bytes too short\n",
+                                   scsi_dev->name, sb_len, 4 - sb_len);
+                       return -1;
+               }
+
+               if (sense_buffer[0] < 15)
+                       log_message(LOG_WARNING, "%s: old sense key: 0x%x\n",
+                                   scsi_dev->name, sense_buffer[0] & 0x0f);
+               else
+                       log_message(LOG_WARNING, "%s: sense = %2x %2x\n",
+                                   scsi_dev->name,  sense_buffer[0],
+                                   sense_buffer[2]);
+               log_message(LOG_WARNING,
+                           "%s: non-extended sense class %d code 0x%0x ",
+                           scsi_dev->name, sense_class, code);
+
+       }
+
+#ifdef DUMP_SENSE
+       for (i = 0, j = 0; (i < s) && (j < 254); i++) {
+               dprintf("i %d, j %d\n", i, j);
+               out_buffer[j++] = hex_str[(sense_buffer[i] & 0xf0) >> 4];
+               out_buffer[j++] = hex_str[sense_buffer[i] & 0x0f];
+               out_buffer[j++] = ' ';
+       }
+       out_buffer[j] = '\0';
+       log_message(LOG_WARNING, "%s: sense dump:\n", scsi_dev->name);
+       log_message(LOG_WARNING, "%s: %s\n", scsi_dev->name, out_buffer);
+
+#endif
+       return -1;
+}
+
+static int scsi_dump(struct sysfs_class_device *scsi_dev, struct sg_io_hdr *io)
+{
+       if (!io->status && !io->host_status && !io->msg_status &&
+           !io->driver_status) {
+               /*
+                * Impossible, should not be called.
+                */
+               log_message(LOG_WARNING, "%s: called with no error\n",
+                           __FUNCTION__);
+               return -1;
+       }
+
+       log_message(LOG_WARNING, "%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x\n",
+                   scsi_dev->name, io->driver_status, io->host_status,
+                   io->msg_status, io->status);
+       if (io->status == SCSI_CHECK_CONDITION)
+               return scsi_dump_sense(scsi_dev, io);
+       else
+               return -1;
+}
+
+static int scsi_inquiry(struct sysfs_class_device *scsi_dev, int fd,
+                       unsigned char evpd, unsigned char page, unsigned
+                       char *buf, unsigned int buflen)
+{
+       unsigned char inq_cmd[INQUIRY_CMDLEN] =
+               { INQUIRY_CMD, evpd, page, 0, buflen, 0 };
+       unsigned char sense[SENSE_BUFF_LEN];
+       struct sg_io_hdr io_hdr;
+       int retval;
+       unsigned char *inq;
+       unsigned char *buffer;
+       int retry = 3; /* rather random */
+
+       if (buflen > 255) {
+               log_message(LOG_WARNING, "buflen %d too long\n", buflen);
+               return -1;
+       }
+       inq = malloc(OFFSET + sizeof (inq_cmd) + 512);
+       memset(inq, 0, OFFSET + sizeof (inq_cmd) + 512);
+       buffer = inq + OFFSET;
+
+resend:
+       memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+       io_hdr.interface_id = 'S';
+       io_hdr.cmd_len = sizeof(inq_cmd);
+       io_hdr.mx_sb_len = sizeof(sense);
+       io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+       io_hdr.dxfer_len = buflen;
+       io_hdr.dxferp = buffer;
+       io_hdr.cmdp = inq_cmd;
+       io_hdr.sbp = sense;
+       io_hdr.timeout = DEF_TIMEOUT;
+
+       if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+               log_message(LOG_WARNING, "%s ioctl failed: %s\n",
+                           scsi_dev->name, strerror(errno));
+               return -1;
+       }
+
+       retval = sg_err_category3(&io_hdr);
+
+       switch (retval) {
+               case SG_ERR_CAT_CLEAN:
+               case SG_ERR_CAT_RECOVERED:
+                       retval = 0;
+                       break;
+
+               default:
+                       retval = scsi_dump(scsi_dev, &io_hdr);
+       }
+
+       if (!retval) {
+               retval = buflen;
+               memcpy(buf, buffer, retval);
+       } else if (retval > 0) {
+               if (--retry > 0) {
+                       dprintf("%s: Retrying ...\n", scsi_dev->name);
+                       goto resend;
+               }
+               retval = -1;
+       }
+
+       free(inq);
+       return retval;
+}
+
+/*
+ * XXX maybe move all these to an sg_io.c file.
+ *
+ * Ending here.
+ */
+
+int do_scsi_page0_inquiry(struct sysfs_class_device *scsi_dev, int fd,
+                         char *buffer, int len)
+{
+       int retval;
+       char *vendor;
+
+       memset(buffer, 0, len);
+       retval = scsi_inquiry(scsi_dev, fd, 1, 0x0, buffer, len);
+       if (retval < 0)
+               return 1;
+
+       if (buffer[1] != 0) {
+               log_message(LOG_WARNING, "%s: page 0 not available.\n",
+                           scsi_dev->name);
+               return 1;
+       }
+       if (buffer[3] > len) {
+               log_message(LOG_WARNING, "%s: page 0 buffer too long %d",
+                          scsi_dev->name,  buffer[3]);
+               return 1;
+       }
+
+       /*
+        * Following check is based on code once included in the 2.5.x
+        * kernel.
+        *
+        * Some ill behaved devices return the standard inquiry here
+        * rather than the evpd data, snoop the data to verify.
+        */
+       if (buffer[3] > MODEL_LENGTH) {
+               /*
+                * If the vendor id appears in the page assume the page is
+                * invalid.
+                */
+               vendor = sysfs_get_attr(scsi_dev, "vendor");
+               if (!vendor) {
+                       log_message(LOG_WARNING, "%s: no vendor attribute\n",
+                                   scsi_dev->name);
+                       return 1;
+               }
+               if (!strncmp(&buffer[VENDOR_LENGTH], vendor, VENDOR_LENGTH)) {
+                       log_message(LOG_WARNING, "%s invalid page0 data\n",
+                                   scsi_dev->name);
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/*
+ * The caller checks that serial is long enough to include the vendor +
+ * model.
+ */
+static int prepend_vendor_model(struct sysfs_class_device *scsi_dev,
+                               char *serial)
+{
+       char *attr;
+       int ind;
+
+       attr = sysfs_get_attr(scsi_dev, "vendor");
+       if (!attr) {
+               log_message(LOG_WARNING, "%s: no vendor attribute\n",
+                           scsi_dev->name);
+               return 1;
+       }
+       strncpy(serial, attr, VENDOR_LENGTH);
+       ind = strlen(serial) - 1;
+       /*
+        * Remove sysfs added newlines.
+        */
+       if (serial[ind] == '\n')
+               serial[ind] = '\0';
+
+       attr = sysfs_get_attr(scsi_dev, "model");
+       if (!attr) {
+               log_message(LOG_WARNING, "%s: no model attribute\n",
+                           scsi_dev->name);
+               return 1;
+       }
+       strncat(serial, attr, MODEL_LENGTH);
+       ind = strlen(serial) - 1;
+       if (serial[ind] == '\n')
+               serial[ind] = '\0';
+       else
+               ind++;
+
+       /*
+        * This is not a complete check, since we are using strncat/cpy
+        * above, ind will never be too large.
+        */
+       if (ind != (VENDOR_LENGTH + MODEL_LENGTH)) {
+               log_message(LOG_WARNING, "%s: expected length %d, got length %d\n",
+                           scsi_dev->name, (VENDOR_LENGTH + MODEL_LENGTH),
+                           ind);
+               return 1;
+       }
+       return ind;
+}
+
+/**
+ * check_fill_0x83_id - check the page 0x83 id, if OK allocate and fill
+ * serial number.
+ **/
+static int check_fill_0x83_id(struct sysfs_class_device *scsi_dev,
+                             char *page_83,
+                             const struct scsi_id_search_values *id_search,
+                             char *serial, int max_len)
+{
+       int i, j, len;
+
+       /*
+        * ASSOCIATION must be with the device (value 0)
+        */
+       if ((page_83[1] & 0x30) != 0)
+               return 1;
+
+       if ((page_83[1] & 0x0f) != id_search->id_type)
+               return 1;
+
+       /*
+        * Possibly check NAA sub-type.
+        */
+       if ((id_search->naa_type != SCSI_ID_NAA_DONT_CARE) &&
+           (id_search->naa_type != (page_83[4] & 0xf0) >> 4))
+               return 1;
+
+       /*
+        * Check for matching code set - ASCII or BINARY.
+        */
+       if ((page_83[0] & 0x0f) != id_search->code_set)
+               return 1;
+
+       /*
+        * page_83[3]: identifier length
+        */
+       len = page_83[3];
+       if ((page_83[0] & 0x0f) != SCSI_ID_ASCII)
+               /*
+                * If not ASCII, use two bytes for each binary value.
+                */
+               len *= 2;
+
+               /*
+        * Add one byte for the NUL termination, and one for the id_type.
+        */
+       len += 2;
+       if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+               len += VENDOR_LENGTH + MODEL_LENGTH;
+
+       if (max_len < len) {
+               log_message(LOG_WARNING, "%s: length %d too short - need %d\n",
+                           scsi_dev->name, max_len, len);
+               return 1;
+       }
+
+       serial[0] = hex_str[id_search->id_type];
+
+       /*
+        * Prepend the vendor and model before the id since if it is not
+        * unique across all vendors and models.
+        */
+       if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+               if (prepend_vendor_model(scsi_dev, &serial[1]) < 0) {
+                       dprintf("prepend failed\n");
+                       return 1;
+               }
+
+       i = 4; /* offset to the start of the identifier */
+       j = strlen(serial);
+       if ((page_83[0] & 0x0f) == SCSI_ID_ASCII) {
+               /*
+                * ASCII descriptor.
+                */
+               while (i < (4 + page_83[3]))
+                       serial[j++] = page_83[i++];
+       } else {
+               /*
+                * Binary descriptor, convert to ASCII, using two bytes of
+                * ASCII for each byte in the page_83.
+                */
+               while (i < (4 + page_83[3])) {
+                       serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
+                       serial[j++] = hex_str[page_83[i] & 0x0f];
+                       i++;
+               }
+       }
+       return 0;
+}
+
+static int do_scsi_page83_inquiry(struct sysfs_class_device *scsi_dev, int fd,
+                                 char *serial, int len)
+{
+       int retval;
+       int id_ind, j;
+       unsigned char page_83[256];
+
+       memset(page_83, 0, 256);
+       retval = scsi_inquiry(scsi_dev, fd, 1, 0x83, page_83, 255);
+       if (retval < 0)
+               return 1;
+
+       if (page_83[1] != 0x83) {
+               log_message(LOG_WARNING, "%s: Invalid page 0x83\n",
+                           scsi_dev->name);
+               return 1;
+       }
+
+       /*
+        * Search for a match in the prioritized id_search_list.
+        */
+       for (id_ind = 0;
+            id_ind < sizeof(id_search_list)/sizeof(id_search_list[0]);
+            id_ind++) {
+               /*
+                * Examine each descriptor returned. There is normally only
+                * one or a small number of descriptors.
+                */
+               for (j = 4; j <= page_83[3] + 3;
+                       j += page_83[j + 3] + 4) {
+                       retval = check_fill_0x83_id(scsi_dev, &page_83[j],
+                                                   &id_search_list[id_ind],
+                                                   serial, len);
+                       dprintf("%s id desc %d/%d/%d\n", scsi_dev->name,
+                               id_search_list[id_ind].id_type,
+                               id_search_list[id_ind].naa_type,
+                               id_search_list[id_ind].code_set);
+                       if (!retval) {
+                               dprintf("       used\n");
+                               return retval;
+                       } else if (retval < 0) {
+                               dprintf("       failed\n");
+                               return retval;
+                       } else {
+                               dprintf("       not used\n");
+                       }
+               }
+       }
+       return 1;
+}
+
+int do_scsi_page80_inquiry(struct sysfs_class_device *scsi_dev, int fd,
+                          char *serial, int max_len)
+{
+       int retval;
+       int ser_ind;
+       int i;
+       int len;
+       unsigned char buf[256];
+
+       memset(buf, 0, 256);
+       retval = scsi_inquiry(scsi_dev, fd, 1, 0x80, buf, 255);
+       if (retval < 0)
+               return retval;
+
+       if (buf[1] != 0x80) {
+               log_message(LOG_WARNING, "%s: Invalid page 0x80\n",
+                           scsi_dev->name);
+               return 1;
+       }
+
+       len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3];
+       if (max_len < len) {
+               log_message(LOG_WARNING, "%s: length %d too short - need %d\n",
+                           scsi_dev->name, max_len, len);
+               return 1;
+       }
+       /*
+        * Prepend 'S' to avoid unlikely collision with page 0x83 vendor
+        * specific type where we prepend '0' + vendor + model.
+        */
+       serial[0] = 'S';
+       ser_ind = prepend_vendor_model(scsi_dev, &serial[1]);
+       if (ser_ind < 0)
+               return 1;
+       len = buf[3];
+       for (i = 4; i < len + 4; i++, ser_ind++)
+               serial[ser_ind] = buf[i];
+       return 0;
+}
+
+int scsi_get_serial (struct sysfs_class_device *scsi_dev, const char *devname,
+                    int page_code, char *serial, int len)
+{
+       unsigned char page0[256];
+       int fd;
+       int ind;
+       int retval;
+
+       if (len > 255) {
+       }
+       memset(serial, 0, len);
+       dprintf("opening %s\n", devname);
+       fd = open(devname, O_RDONLY);
+       if (fd < 0) {
+               log_message(LOG_WARNING, "%s cannot open %s: %s\n",
+                           scsi_dev->name, devname, strerror(errno));
+               return 1;
+       }
+
+       if (page_code == 0x80) {
+               if (do_scsi_page80_inquiry(scsi_dev, fd, serial, len)) {
+                       retval = 1;
+                       goto completed;
+               } else  {
+                       retval = 0;
+                       goto completed;
+               }
+       } else if (page_code == 0x83) {
+               if (do_scsi_page83_inquiry(scsi_dev, fd, serial, len)) {
+                       retval = 1;
+                       goto completed;
+               } else  {
+                       retval = 0;
+                       goto completed;
+               }
+       } else if (page_code != 0x00) {
+               log_message(LOG_WARNING, "%s unsupported page code 0x%d\n",
+                           scsi_dev->name, page_code);
+               return 1;
+       }
+
+       /*
+        * Get page 0, the page of the pages. By default, try from best to
+        * worst of supported pages: 0x83 then 0x80.
+        */
+       if (do_scsi_page0_inquiry(scsi_dev, fd, page0, 255)) {
+               /*
+                * Don't try anything else. Black list if a specific page
+                * should be used for this vendor+model, or maybe have an
+                * optional fall-back to page 0x80 or page 0x83.
+                */
+               retval = 1;
+               goto completed;
+       }
+
+       dprintf("%s: Checking page0\n", scsi_dev->name);
+
+       for (ind = 4; ind <= page0[3] + 3; ind++)
+               if (page0[ind] == 0x83)
+                       if (!do_scsi_page83_inquiry(scsi_dev, fd, serial,
+                                                   len)) {
+                               /*
+                                * Success
+                                */
+                               retval = 0;
+                               goto completed;
+                       }
+
+       for (ind = 4; ind <= page0[3] + 3; ind++)
+               if (page0[ind] == 0x80)
+                       if (!do_scsi_page80_inquiry(scsi_dev, fd, serial,
+                                                   len)) {
+                               /*
+                                * Success
+                                */
+                               retval = 0;
+                               goto completed;
+                       }
+       retval = 1;
+completed:
+       if (close(fd) < 0)
+               log_message(LOG_WARNING, "close failed: %s", strerror(errno));
+       return retval;
+}