summaryrefslogtreecommitdiff
path: root/extras
diff options
context:
space:
mode:
authorgreg@kroah.com <greg@kroah.com>2003-11-13 06:34:36 -0800
committerGreg KH <gregkh@suse.de>2005-04-26 21:06:24 -0700
commitc521693b54956c1f2dd0c0947c819b8570f6edaa (patch)
treee96aa55e1072c8378af0242a22f5317d64615c81 /extras
parent04a091d47e32d6480b99424e41db093b013dfaf5 (diff)
downloadsystemd-c521693b54956c1f2dd0c0947c819b8570f6edaa.tar.gz
[PATCH] add scsi_id "extra" program from Patrick Mansfield <patmans@us.ibm.com>
Diffstat (limited to 'extras')
-rw-r--r--extras/scsi_id/COPYING340
-rw-r--r--extras/scsi_id/Makefile51
-rw-r--r--extras/scsi_id/README19
-rw-r--r--extras/scsi_id/TODO16
-rw-r--r--extras/scsi_id/VERSION1
-rw-r--r--extras/scsi_id/scsi.h96
-rw-r--r--extras/scsi_id/scsi_id.c827
-rw-r--r--extras/scsi_id/scsi_id.config40
-rw-r--r--extras/scsi_id/scsi_id.h42
-rw-r--r--extras/scsi_id/scsi_serial.c735
10 files changed, 2167 insertions, 0 deletions
diff --git a/extras/scsi_id/COPYING b/extras/scsi_id/COPYING
new file mode 100644
index 0000000000..60549be514
--- /dev/null
+++ b/extras/scsi_id/COPYING
@@ -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.
+
+ 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.)
+
+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.
+
+ 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.
+
+ 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
+
+ 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
index 0000000000..5ad2bcf3e1
--- /dev/null
+++ b/extras/scsi_id/Makefile
@@ -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
index 0000000000..b13cf1e50a
--- /dev/null
+++ b/extras/scsi_id/README
@@ -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
index 0000000000..ba52101431
--- /dev/null
+++ b/extras/scsi_id/TODO
@@ -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
index 0000000000..49d59571fb
--- /dev/null
+++ b/extras/scsi_id/VERSION
@@ -0,0 +1 @@
+0.1
diff --git a/extras/scsi_id/scsi.h b/extras/scsi_id/scsi.h
new file mode 100644
index 0000000000..780e001576
--- /dev/null
+++ b/extras/scsi_id/scsi.h
@@ -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
index 0000000000..d34d9284e5
--- /dev/null
+++ b/extras/scsi_id/scsi_id.c
@@ -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
index 0000000000..4fdb89e28a
--- /dev/null
+++ b/extras/scsi_id/scsi_id.config
@@ -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
index 0000000000..8be492b75a
--- /dev/null
+++ b/extras/scsi_id/scsi_id.h
@@ -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
index 0000000000..302429c0d4
--- /dev/null
+++ b/extras/scsi_id/scsi_serial.c
@@ -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;
+}