From: greg@kroah.com Date: Thu, 11 Mar 2004 06:40:39 +0000 (-0800) Subject: [PATCH] Added multipath-tools 0.1.1 release X-Git-Tag: 022~18 X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a3b37a073d52ff01d4ef023a10f13316da4c9966;p=systemd [PATCH] Added multipath-tools 0.1.1 release --- diff --git a/extras/multipath-tools/AUTHOR b/extras/multipath-tools/AUTHOR new file mode 100644 index 00000000..4e8eeef5 --- /dev/null +++ b/extras/multipath-tools/AUTHOR @@ -0,0 +1 @@ +Christophe Varoqui, diff --git a/extras/multipath-tools/COPYING b/extras/multipath-tools/COPYING new file mode 100644 index 00000000..9e31bbf0 --- /dev/null +++ b/extras/multipath-tools/COPYING @@ -0,0 +1,483 @@ + + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 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. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, 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 companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library 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 + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/extras/multipath-tools/ChangeLog b/extras/multipath-tools/ChangeLog new file mode 100644 index 00000000..0807a426 --- /dev/null +++ b/extras/multipath-tools/ChangeLog @@ -0,0 +1,137 @@ +2004-03-06 multipath-tools-0.1.1 + * include dlist.h in multipath main.c (PM Hahn) + * typo in hotplug script (PM Hahn) + * pass -9 opt to gzip for manpages (PM Hahn) +2004-03-05 multipath-tools-0.1.0 + * add the group_by_tur policy + * add the multipathd daemon for pathchecking & DM hot-reconfig + * multipath doesn't run twice + * massive cleanups, and code restructuring + * Avoid Kernel Bug when passing too small a buffer in do_inq() + * Sync with 2.6.3-udm4 target synthax (no more PG prio) +2004-02-21 multipath-018 + * From the Debian SID inclusion review (Philipp Matthias Hahn) + * use DESTDIR install prefix in the Makefile + * add man pages for devmap_name & multipath + * correct libsysfs.h includes + * fork the hotplug script in its own shell + * Sync with the kernel device mapper code as of 2.6.3-udm3 + ie. Remove the test interval parameter and its uses + * Remove superfluous scsi parameter passed from hotplug + * Add the man pages to the [un]install targets +2004-02-17 multipath-017 + * remove the restrictive -f flag. + Introduce a more generic "-m iopolicy" one. + * remove useless "int with_sysfs" in env struct +2004-02-04 multipath-016 + * add a GROUP_BY_SERIAL flag. This should be useful for + controlers that activate they spare paths on simple IO + submition with a penalty. The StorageWorks HW defaults to + this mode, even if the MULTIBUS mode is OK. + * remove unused sg_err.c + * big restructuring : split devinfo.c from main.c. Export : + * void basename (char *, char *); + * int get_serial (int, char *); + * int get_lun_strings (char *, char *, char *, char *); + * int get_evpd_wwid(char *, char *); + * long get_disk_size (char *); + * stop passing struct env as param + * add devmap_name proggy for udev to name devmaps as per their + internal DM name and not only by their sysfs enum name (dm-*) + The corresponding udev.rules line is : + KERNEL="dm-[0-9]*", PROGRAM="/sbin/devmap_name %M %m", NAME="%k", SYMLINK="%c" + * remove make_dm_node fn & call. Rely on udev for this. + * don't rely on the linux symlink in the udev/klibc dir since + udev build doesn't use it anymore. This corrects build breakage +2004-01-19 multipath-013 + * update the DM target synthax to the 2.6.0-udm5 style +2003-12-29 multipath-012 + * check hotplug event refers to a block device; if not exit early + * refresh doc + * add the uninstall target in Makefile +2003-12-22 multipath-010 + * tweak the install target in Makefile + * stop passing fds as argument : this change enable a strict + segregation of ugly 2.4 code + * sysfs version of get_lun_strings() + * be careful about the return of get_unique_id() since errors + formerly caught up by if(open()) in the caller fn are now returned + by get_unique_id() + * send get_serial() in unused.c + * introduce dm-simplecmd for RESUME & SUSPEND requests + * split add_map() in setup_map() & dm-addmap() + * setup_map() correctly submits "SUSPEND-RELOAD-RESUME or CREATE" + sequences instead of the bogus "RELOAD or CREATE" + * don't print .sg_dev if equal to .dev (2.6) in print_path() + * since the kernel code handles defective paths, remove all + code to cope with them : + * move do_tur() to unused.c + * remove .state from path struct + * remove .state settings & conditionals + * add a cmdline switch to force maps to failover mode, + ie 1 path per priority group + * add default policies to the whitelist array (spread io == + MULTIBUS / io forced to 1 path == FAILOVER) + * move get_disk_size() call out of add_map() to coalesce() + * comment tricky coalesce() fn + * bogus unsused.c file renamed to unused.c +2003-12-20 multipath-010 + * big ChangeLog update + * start to give a little control over target params : + introduce cmdline arg -i to control polling interval + * cope with hotplug-style calling convention : + ie "multipath scsi $DEVPATH" ... to avoid messing with + online maps not concerned by an event + * example hotplug agent to drop in /etc/hotplug.d/scsi + * revert the run & resched patch : unless someone proves me + wrong, this was overdesigned + * move commented out functions in unused.c + * update multipath target params to "udm[23] style" + * mp target now supports nr_path == 1, so do we + * add gratuitous free() + * push version forward +2003-12-15 multipath-009 + * Make the HW-specific get_unique_id switch pretty + * Prepare to field-test by whitelisting all known fibre array, + try to fetch WWID from the standard EVPD 0x83 off 8 for everyone + * configure the multipath target with round-robin path selector and + conservative default for a start (udm1 style) : + yes it makes this release the firstreally useful one. + * temporarily disable map creation for single path device + due to current restrictive defaults in the kernel target. + Sistina should work it out. + * correct the strncmp logic in blacklist function. + * update the Makefiles to autodetect libgcc.a & gcc includes + "ulibc-style". Factorisation of udevdirs & others niceties + * drop a hint about absent /dev/sd? on failed open() + * implement a reschedule flag in /var/run. + Last thing the prog do before exit is check if a call to multipath + was done (but canceled by /var/run/multipath.run check) during its + execution. If so restart themain loop. + * implement a blacklist of sysfs bdev to not bother with for now + (hd,md, dm, sr, scd, ram, raw). + This avoid sending SG_IO to unappropiate devices. + * Adds a /var/run/multipath.run handling to avoid simultaneous runs. + * Remove a commented-out "printf" + * drop a libdevmapper copy in extras/multipath; + maybe discussions w/Sistina folks will bring a better solution in the future. + * drop a putchar usage in libdevmapper to compile cleanly with klibc + * drop another such usage of my own in main.c + * massage the Makefile to compile libdevmapper against klibc + * use "ld" to produce the binary rather than "gcc -static" + * stop being stupid w/ uneeded major, minor & dev in main.c:dm_mk_node() + * reverse to creating striped target for now because the multipath target + is more hairy than expected initialy + * push the version code to 009 to be in synch w/ udev +2003-11-27 multipath-007 + * removes sg_err.[ch] deps + * makes sure the core code play nice with klibc + * port the sysfs calls to dlist helpers + * links against udev's sysfs (need libsysfs.a & dlist.a) + * finally define DM_TARGET as "multipath" as Joe posted the code today (not tested yet) + * push version forward (do you want it in sync with udev version?) +2003-11-19 + * merged in udev-006 tree +2003-09-18 Christophe Varoqui + * multipath 0.0.1 released. + * Initial release. diff --git a/extras/multipath-tools/Makefile b/extras/multipath-tools/Makefile new file mode 100644 index 00000000..2f21f0d1 --- /dev/null +++ b/extras/multipath-tools/Makefile @@ -0,0 +1,41 @@ +# Makefile +# +# Copyright (C) 2003 Christophe Varoqui, + +SUBDIRS = libdevmapper devmap_name multipath multipathd + +recurse: + @for dir in $(SUBDIRS); do\ + $(MAKE) -C $$dir ; \ + done + +recurse_clean: + @for dir in $(SUBDIRS); do\ + $(MAKE) -C $$dir clean ; \ + done + +recurse_install: + @for dir in $(SUBDIRS); do\ + $(MAKE) -C $$dir install ; \ + done + +recurse_uninstall: + @for dir in $(SUBDIRS); do\ + $(MAKE) -C $$dir uninstall ; \ + done + +all: recurse + @echo "" + @echo "Make complete" + +clean: recurse_clean + @echo "" + @echo "Make complete" + +install: recurse_install + @echo "" + @echo "Make complete" + +uninstall: recurse_uninstall + @echo "" + @echo "Make complete" diff --git a/extras/multipath-tools/README b/extras/multipath-tools/README new file mode 100644 index 00000000..80945db4 --- /dev/null +++ b/extras/multipath-tools/README @@ -0,0 +1,90 @@ +Dependancies : +============== + +o libdevmapper : comes with device-mapper-XXXX.tar.gz + See www.sistina.com + This lib has been dropped in the multipath tree +o libsysfs : comes with sysutils or udev + See ftp.kernel.org/pub/linux/utils/kernel/hotplug/ +o Linux kernel 2.6.0 with udm5 patchset + http://people.sistina.com/~thornber/dm/ +o udev + See ftp.kernel.org/pub/linux/utils/kernel/hotplug/ + +How it works : +============== + +Fill the all_paths array. Each path store this info : + +struct path { + char dev[FILE_NAME_SIZE]; + char sg_dev[FILE_NAME_SIZE]; + struct scsi_idlun scsi_id; + struct sg_id sg_id; + int state; + char wwid[WWID_SIZE]; +}; + +scsi_id, sg_dev and sg_id are only really useful for 2.4 +kernels, for which SG cmnds must go through sg devs. +In 2.5+ we have the nice opportunity to send SG cmnds +through SCSI bdevs. + +For 2.4 compat, we pivot on idlun tupple to map sg devs +to SCSI bdevs. + +2.4 does not do device enumeration, so we must scan a +defined number of sg devs and scsi bdevs. Good enough. +In 2.5+, we rely on libsysfs (sysutils) to access to +sysfs device enums. + +the wwid is retrieved by a switch fonction. Only White +Listed HW can filled this field. For now every FC array +HW listed in kernel's devinfo.c is White Listed, assuming +the WWID is stored is the SCSI-3 standard 0x83 EVPD page. + +When all_paths is filled, we coalesce the paths and store +the result in mp array. Each mp is a struct like this : + +struct multipath { + char wwid[WWID_SIZE]; + int npaths; + int pindex[MAX_MP_PATHS]; +}; + +When mp is filled, the device maps are fed to the kernel +through libdevmapper. + +The naming of the corresponding block device is handeld +by udev with the help of the devmap_name proggy. It is +called by the following rule in /etc/udev/udev.rules : +KERNEL="dm-[0-9]*", PROGRAM="/sbin/devmap_name %M %m", \ +NAME="%k", SYMLINK="%c" + +Notes : +======= + +o On 2.4, make sure you have enough /dev/sg* nodes + (/dev/MAKEDEV if necesary). Same goes for /dev/sd* + +o path coalescing relies on a path unique id being found. + This unique id, lacking a standard method, is vendor + specific. A switch function (get_unique_id) is present + and an example function is provided for storageworks + arrays (get_evpd_wwid). Feel free to enrich + with hardware you have at hand :) + +o The kernel does NOT manage properly ghosts paths + with StorageWorks HW. Seems nobody cares after a load + of posts to linux-scsi. + +o 2.4.21 version of DM does not like even segment size. + if you enconter pbs with this, upgrade DM. + +Credits : +========= + +o Heavy cut'n paste from sg_utils. Thanks goes to D. + Gilbert. +o Light cut'n paste from dmsetup. Thanks Joe Thornber. +o Greg KH for the nice sysfs API. diff --git a/extras/multipath-tools/VERSION b/extras/multipath-tools/VERSION new file mode 100644 index 00000000..bbdeab62 --- /dev/null +++ b/extras/multipath-tools/VERSION @@ -0,0 +1 @@ +0.0.5 diff --git a/extras/multipath-tools/devmap_name/Makefile b/extras/multipath-tools/devmap_name/Makefile new file mode 100644 index 00000000..a9683341 --- /dev/null +++ b/extras/multipath-tools/devmap_name/Makefile @@ -0,0 +1,47 @@ +# Makefile +# +# Copyright (C) 2003 Christophe Varoqui, + +EXEC = devmap_name + +prefix = +exec_prefix = ${prefix} +bindir = ${exec_prefix}/sbin +udevdir = ../../.. +klibcdir = $(udevdir)/klibc +mandir = /usr/share/man/man8 +libdmdir = ../libdevmapper + +CC = gcc +GZIP = /bin/gzip -9 -c + +GCCINCDIR := ${shell $(CC) -print-search-dirs | sed -ne "s/install: \(.*\)/\1include/gp"} +KERNEL_DIR = /lib/modules/${shell uname -r}/build +CFLAGS = -pipe -g -O2 -Wall -Wunused -Wstrict-prototypes -nostdinc \ + -I$(klibcdir)/klibc/include -I$(klibcdir)/klibc/include/bits32 \ + -I$(GCCINCDIR) -I$(KERNEL_DIR)/include -I$(sysfsdir) -I. + +OBJS = devmap_name.o +CRT0 = $(klibcdir)/klibc/crt0.o +LIB = $(klibcdir)/klibc/libc.a +LIBGCC := $(shell $(CC) -print-libgcc-file-name ) + +DMOBJS = $(libdmdir)/libdm-common.o $(libdmdir)/ioctl/libdevmapper.o + +$(EXEC): $(OBJS) + $(LD) -o $(EXEC) $(CRT0) $(OBJS) $(DMOBJS) $(LIB) $(LIBGCC) + strip $(EXEC) + $(GZIP) $(EXEC).8 > $(EXEC).8.gz + +clean: + rm -f core *.o $(EXEC) *.gz + +install: + install -d $(DESTDIR)$(bindir) + install -m 755 $(EXEC) $(DESTDIR)$(bindir)/ + install -d $(DESTDIR)$(mandir) + install -m 644 $(EXEC).8.gz $(DESTDIR)$(mandir) + +uninstall: + rm $(DESTDIR)$(bindir)/$(EXEC) + rm $(DESTDIR)$(mandir)/$(EXEC).8.gz diff --git a/extras/multipath-tools/devmap_name/devmap_name.8 b/extras/multipath-tools/devmap_name/devmap_name.8 new file mode 100644 index 00000000..f4f03c3e --- /dev/null +++ b/extras/multipath-tools/devmap_name/devmap_name.8 @@ -0,0 +1,30 @@ +.TH DEVMAP_NAME 8 "February 2004" "" "Linux Administrator's Manual" +.SH NAME +devmap_name \- Query device-mapper name +.SH SYNOPSIS +.BI devmap_name " major minor" +.SH DESCRIPTION +.B devmap_name +queries the device-mapper for the name for the device +specified by +.I major +and +.I minor +number. +.br +.B devmap_name +can be called from +.B udev +by the following rule in +.IR /etc/udev/udev.rules : +.sp +.nf +KERNEL="dm-[0-9]*", PROGRAM="/sbin/devmap_name %M %m", \\ + NAME="%k", SYMLINK="%c" +.fi +.SH "SEE ALSO" +.BR udev (8), +.BR dmsetup (8) +.SH AUTHORS +.B devmap_name +was developed by Christophe Varoqui, and others. diff --git a/extras/multipath-tools/devmap_name/devmap_name.c b/extras/multipath-tools/devmap_name/devmap_name.c new file mode 100644 index 00000000..0932e4f8 --- /dev/null +++ b/extras/multipath-tools/devmap_name/devmap_name.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "../libdevmapper/libdevmapper.h" + +static void usage(char * progname) { + fprintf(stderr, "usage : %s major minor\n", progname); + exit(1); +} + +int main(int argc, char **argv) +{ + int r = 0; + struct dm_names *names; + unsigned next = 0; + int major, minor; + + /* sanity check */ + if (argc != 3) + usage(argv[0]); + + major = atoi(argv[1]); + minor = atoi(argv[2]); + + struct dm_task *dmt; + + if (!(dmt = dm_task_create(DM_DEVICE_LIST))) + return 0; + + if (!dm_task_run(dmt)) + goto out; + + if (!(names = dm_task_get_names(dmt))) + goto out; + + if (!names->dev) { + printf("No devices found\n"); + goto out; + } + + do { + names = (void *) names + next; + if ((int) MAJOR(names->dev) == major && + (int) MINOR(names->dev) == minor) { + printf("%s\n", names->name); + goto out; + } + next = names->next; + } while (next); + + /* No correspondance found */ + r = 1; + + out: + dm_task_destroy(dmt); + return r; +} + diff --git a/extras/multipath-tools/libdevmapper/Makefile b/extras/multipath-tools/libdevmapper/Makefile new file mode 100644 index 00000000..445263ca --- /dev/null +++ b/extras/multipath-tools/libdevmapper/Makefile @@ -0,0 +1,28 @@ +# Makefile +# +# Copyright (C) 2003 Christophe Varoqui, + +CC = gcc +udevdir = ../../.. +klibcdir = $(udevdir)/klibc + +CC = gcc +GCCINCDIR := ${shell $(CC) -print-search-dirs | sed -ne "s/install: \(.*\)/\1include/gp"} +KERNEL_DIR = /lib/modules/${shell uname -r}/build + +CFLAGS = -pipe -g -O2 -Wall -Wunused -Wstrict-prototypes -nostdinc \ + -I$(klibcdir)/klibc/include -I$(klibcdir)/klibc/include/bits32 \ + -I$(GCCINCDIR) -I$(KERNEL_DIR)/include -I. -Iioctl + +OBJS = ioctl/libdevmapper.o libdm-common.o + +all: $(OBJS) + @echo "" + @echo "Make complete" + +clean: + rm -f core *.o ioctl/*.o ioctl/*.so + +install: + +uninstall: diff --git a/extras/multipath-tools/libdevmapper/ioctl/libdevmapper.c b/extras/multipath-tools/libdevmapper/ioctl/libdevmapper.c new file mode 100644 index 00000000..ac7ba0c8 --- /dev/null +++ b/extras/multipath-tools/libdevmapper/ioctl/libdevmapper.c @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#include "libdm-targets.h" +#include "libdm-common.h" + +#ifdef DM_COMPAT +# include "libdm-compat.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef linux +# include +# include +# include +#else +# define MAJOR(x) major((x)) +# define MINOR(x) minor((x)) +# define MKDEV(x,y) makedev((x),(y)) +#endif + +/* + * Ensure build compatibility. + * The hard-coded versions here are the highest present + * in the _cmd_data arrays. + */ + +#if !((DM_VERSION_MAJOR == 1 && DM_VERSION_MINOR >= 0) || \ + (DM_VERSION_MAJOR == 4 && DM_VERSION_MINOR >= 0)) +#error The version of dm-ioctl.h included is incompatible. +#endif + +/* dm major version no for running kernel */ +static int _dm_version = DM_VERSION_MAJOR; +static int _log_suppress = 0; + +static int _control_fd = -1; +static int _version_checked = 0; +static int _version_ok = 1; + +/* + * Support both old and new major numbers to ease the transition. + * Clumsy, but only temporary. + */ +#if DM_VERSION_MAJOR == 4 && defined(DM_COMPAT) +const int _dm_compat = 1; +#else +const int _dm_compat = 0; +#endif + + +/* *INDENT-OFF* */ +static struct cmd_data _cmd_data_v4[] = { + {"create", DM_DEV_CREATE, {4, 0, 0}}, + {"reload", DM_TABLE_LOAD, {4, 0, 0}}, + {"remove", DM_DEV_REMOVE, {4, 0, 0}}, + {"remove_all", DM_REMOVE_ALL, {4, 0, 0}}, + {"suspend", DM_DEV_SUSPEND, {4, 0, 0}}, + {"resume", DM_DEV_SUSPEND, {4, 0, 0}}, + {"info", DM_DEV_STATUS, {4, 0, 0}}, + {"deps", DM_TABLE_DEPS, {4, 0, 0}}, + {"rename", DM_DEV_RENAME, {4, 0, 0}}, + {"version", DM_VERSION, {4, 0, 0}}, + {"status", DM_TABLE_STATUS, {4, 0, 0}}, + {"table", DM_TABLE_STATUS, {4, 0, 0}}, + {"waitevent", DM_DEV_WAIT, {4, 0, 0}}, + {"names", DM_LIST_DEVICES, {4, 0, 0}}, + {"clear", DM_TABLE_CLEAR, {4, 0, 0}}, + {"mknodes", DM_DEV_STATUS, {4, 0, 0}}, +}; +/* *INDENT-ON* */ + +#define ALIGNMENT_V1 sizeof(int) +#define ALIGNMENT 8 + +/* FIXME Rejig library to record & use errno instead */ +#ifndef DM_EXISTS_FLAG +# define DM_EXISTS_FLAG 0x00000004 +#endif + +static void *_align(void *ptr, unsigned int a) +{ + register unsigned long agn = --a; + + return (void *) (((unsigned long) ptr + agn) & ~agn); +} + +static int _open_control(void) +{ + char control[PATH_MAX]; + + if (_control_fd != -1) + return 1; + + snprintf(control, sizeof(control), "%s/control", dm_dir()); + + if ((_control_fd = open(control, O_RDWR)) < 0) { + log_error("%s: open failed: %s", control, strerror(errno)); + log_error("Is device-mapper driver missing from kernel?"); + return 0; + } + + return 1; +} + +void dm_task_destroy(struct dm_task *dmt) +{ + struct target *t, *n; + + for (t = dmt->head; t; t = n) { + n = t->next; + free(t->params); + free(t->type); + free(t); + } + + if (dmt->dev_name) + free(dmt->dev_name); + + if (dmt->newname) + free(dmt->newname); + + if (dmt->dmi.v4) + free(dmt->dmi.v4); + + if (dmt->uuid) + free(dmt->uuid); + + free(dmt); +} + +/* + * Protocol Version 1 compatibility functions. + */ + +#ifdef DM_COMPAT + +static int _dm_task_get_driver_version_v1(struct dm_task *dmt, char *version, + size_t size) +{ + unsigned int *v; + + if (!dmt->dmi.v1) { + version[0] = '\0'; + return 0; + } + + v = dmt->dmi.v1->version; + snprintf(version, size, "%u.%u.%u", v[0], v[1], v[2]); + return 1; +} + +/* Unmarshall the target info returned from a status call */ +static int _unmarshal_status_v1(struct dm_task *dmt, struct dm_ioctl_v1 *dmi) +{ + char *outbuf = (char *) dmi + dmi->data_start; + char *outptr = outbuf; + int32_t i; + struct dm_target_spec_v1 *spec; + + for (i = 0; i < dmi->target_count; i++) { + spec = (struct dm_target_spec_v1 *) outptr; + + if (!dm_task_add_target(dmt, spec->sector_start, + (uint64_t) spec->length, + spec->target_type, + outptr + sizeof(*spec))) + return 0; + + outptr = outbuf + spec->next; + } + + return 1; +} + +static int _dm_format_dev_v1(char *buf, int bufsize, uint32_t dev_major, + uint32_t dev_minor) +{ + int r; + + if (bufsize < 8) + return 0; + + r = snprintf(buf, bufsize, "%03x:%03x", dev_major, dev_minor); + if (r < 0 || r > bufsize - 1) + return 0; + + return 1; +} + +static int _dm_task_get_info_v1(struct dm_task *dmt, struct dm_info *info) +{ + if (!dmt->dmi.v1) + return 0; + + memset(info, 0, sizeof(*info)); + + info->exists = dmt->dmi.v1->flags & DM_EXISTS_FLAG ? 1 : 0; + if (!info->exists) + return 1; + + info->suspended = dmt->dmi.v1->flags & DM_SUSPEND_FLAG ? 1 : 0; + info->read_only = dmt->dmi.v1->flags & DM_READONLY_FLAG ? 1 : 0; + info->target_count = dmt->dmi.v1->target_count; + info->open_count = dmt->dmi.v1->open_count; + info->event_nr = 0; + info->major = MAJOR(dmt->dmi.v1->dev); + info->minor = MINOR(dmt->dmi.v1->dev); + info->live_table = 1; + info->inactive_table = 0; + + return 1; +} + +static const char *_dm_task_get_name_v1(struct dm_task *dmt) +{ + return (dmt->dmi.v1->name); +} + +static const char *_dm_task_get_uuid_v1(struct dm_task *dmt) +{ + return (dmt->dmi.v1->uuid); +} + +static struct dm_deps *_dm_task_get_deps_v1(struct dm_task *dmt) +{ + log_error("deps version 1 no longer supported by libdevmapper"); + return NULL; +} + +static struct dm_names *_dm_task_get_names_v1(struct dm_task *dmt) +{ + return (struct dm_names *) (((void *) dmt->dmi.v1) + + dmt->dmi.v1->data_start); +} + +static void *_add_target_v1(struct target *t, void *out, void *end) +{ + void *out_sp = out; + struct dm_target_spec_v1 sp; + size_t sp_size = sizeof(struct dm_target_spec_v1); + int len; + const char no_space[] = "Ran out of memory building ioctl parameter"; + + out += sp_size; + if (out >= end) { + log_error(no_space); + return NULL; + } + + sp.status = 0; + sp.sector_start = t->start; + sp.length = t->length; + strncpy(sp.target_type, t->type, sizeof(sp.target_type)); + + len = strlen(t->params); + + if ((out + len + 1) >= end) { + log_error(no_space); + + log_error("t->params= '%s'", t->params); + return NULL; + } + strcpy((char *) out, t->params); + out += len + 1; + + /* align next block */ + out = _align(out, ALIGNMENT_V1); + + sp.next = out - out_sp; + + memcpy(out_sp, &sp, sp_size); + + return out; +} + +static struct dm_ioctl_v1 *_flatten_v1(struct dm_task *dmt) +{ + const size_t min_size = 16 * 1024; + const int (*version)[3]; + + struct dm_ioctl_v1 *dmi; + struct target *t; + size_t len = sizeof(struct dm_ioctl_v1); + void *b, *e; + int count = 0; + + for (t = dmt->head; t; t = t->next) { + len += sizeof(struct dm_target_spec_v1); + len += strlen(t->params) + 1 + ALIGNMENT_V1; + count++; + } + + if (count && dmt->newname) { + log_error("targets and newname are incompatible"); + return NULL; + } + + if (dmt->newname) + len += strlen(dmt->newname) + 1; + + /* + * Give len a minimum size so that we have space to store + * dependencies or status information. + */ + if (len < min_size) + len = min_size; + + if (!(dmi = malloc(len))) + return NULL; + + memset(dmi, 0, len); + + version = &_cmd_data_v1[dmt->type].version; + + dmi->version[0] = (*version)[0]; + dmi->version[1] = (*version)[1]; + dmi->version[2] = (*version)[2]; + + dmi->data_size = len; + dmi->data_start = sizeof(struct dm_ioctl_v1); + + if (dmt->dev_name) + strncpy(dmi->name, dmt->dev_name, sizeof(dmi->name)); + + if (dmt->type == DM_DEVICE_SUSPEND) + dmi->flags |= DM_SUSPEND_FLAG; + if (dmt->read_only) + dmi->flags |= DM_READONLY_FLAG; + + if (dmt->minor >= 0) { + if (dmt->major <= 0) { + log_error("Missing major number for persistent device"); + return NULL; + } + dmi->flags |= DM_PERSISTENT_DEV_FLAG; + dmi->dev = MKDEV(dmt->major, dmt->minor); + } + + if (dmt->uuid) + strncpy(dmi->uuid, dmt->uuid, sizeof(dmi->uuid)); + + dmi->target_count = count; + + b = (void *) (dmi + 1); + e = (void *) ((char *) dmi + len); + + for (t = dmt->head; t; t = t->next) + if (!(b = _add_target_v1(t, b, e))) + goto bad; + + if (dmt->newname) + strcpy(b, dmt->newname); + + return dmi; + + bad: + free(dmi); + return NULL; +} + +static int _dm_names_v1(struct dm_ioctl_v1 *dmi) +{ + const char *dev_dir = dm_dir(); + int r = 1, len; + const char *name; + struct dirent *dirent; + DIR *d; + struct dm_names *names, *old_names = NULL; + void *end = (void *) dmi + dmi->data_size; + struct stat buf; + char path[PATH_MAX]; + + if (!(d = opendir(dev_dir))) { + log_error("%s: opendir failed: %s", dev_dir, strerror(errno)); + return 0; + } + + names = (struct dm_names *) ((void *) dmi + dmi->data_start); + + names->dev = 0; /* Flags no data */ + + while ((dirent = readdir(d))) { + name = dirent->d_name; + + if (name[0] == '.' || !strcmp(name, "control")) + continue; + + if (old_names) + old_names->next = (uint32_t) ((void *) names - + (void *) old_names); + snprintf(path, sizeof(path), "%s/%s", dev_dir, name); + if (stat(path, &buf)) { + log_error("%s: stat failed: %s", path, strerror(errno)); + continue; + } + if (!S_ISBLK(buf.st_mode)) + continue; + names->dev = (uint64_t) buf.st_rdev; + names->next = 0; + len = strlen(name); + if (((void *) (names + 1) + len + 1) >= end) { + log_error("Insufficient buffer space for device list"); + r = 0; + break; + } + + strcpy(names->name, name); + + old_names = names; + names = _align((void *) ++names + len + 1, ALIGNMENT); + } + + if (closedir(d)) + log_error("%s: closedir failed: %s", dev_dir, strerror(errno)); + + return r; +} + +static int _dm_task_run_v1(struct dm_task *dmt) +{ + struct dm_ioctl_v1 *dmi; + unsigned int command; + + dmi = _flatten_v1(dmt); + if (!dmi) { + log_error("Couldn't create ioctl argument"); + return 0; + } + + if (!_open_control()) + return 0; + + if ((unsigned) dmt->type >= + (sizeof(_cmd_data_v1) / sizeof(*_cmd_data_v1))) { + log_error("Internal error: unknown device-mapper task %d", + dmt->type); + goto bad; + } + + command = _cmd_data_v1[dmt->type].cmd; + + if (dmt->type == DM_DEVICE_TABLE) + dmi->flags |= DM_STATUS_TABLE_FLAG; + + log_debug("dm %s %s %s %s", _cmd_data_v1[dmt->type].name, dmi->name, + dmi->uuid, dmt->newname ? dmt->newname : ""); + if (dmt->type == DM_DEVICE_LIST) { + if (!_dm_names_v1(dmi)) + goto bad; + } else if (ioctl(_control_fd, command, dmi) < 0) { + if (_log_suppress) + log_verbose("device-mapper ioctl cmd %d failed: %s", + _IOC_NR(command), strerror(errno)); + else + log_error("device-mapper ioctl cmd %d failed: %s", + _IOC_NR(command), strerror(errno)); + goto bad; + } + + switch (dmt->type) { + case DM_DEVICE_CREATE: + add_dev_node(dmt->dev_name, MAJOR(dmi->dev), MINOR(dmi->dev)); + break; + + case DM_DEVICE_REMOVE: + rm_dev_node(dmt->dev_name); + break; + + case DM_DEVICE_RENAME: + rename_dev_node(dmt->dev_name, dmt->newname); + break; + + case DM_DEVICE_MKNODES: + if (dmi->flags & DM_EXISTS_FLAG) + add_dev_node(dmt->dev_name, MAJOR(dmi->dev), + MINOR(dmi->dev)); + else + rm_dev_node(dmt->dev_name); + break; + + case DM_DEVICE_STATUS: + case DM_DEVICE_TABLE: + if (!_unmarshal_status_v1(dmt, dmi)) + goto bad; + break; + + case DM_DEVICE_SUSPEND: + case DM_DEVICE_RESUME: + dmt->type = DM_DEVICE_INFO; + if (!dm_task_run(dmt)) + goto bad; + free(dmi); /* We'll use what info returned */ + return 1; + } + + dmt->dmi.v1 = dmi; + return 1; + + bad: + free(dmi); + return 0; +} + +#endif + +/* + * Protocol Version 4 functions. + */ + +int dm_task_get_driver_version(struct dm_task *dmt, char *version, size_t size) +{ + unsigned int *v; + +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_driver_version_v1(dmt, version, size); +#endif + + if (!dmt->dmi.v4) { + version[0] = '\0'; + return 0; + } + + v = dmt->dmi.v4->version; + snprintf(version, size, "%u.%u.%u", v[0], v[1], v[2]); + return 1; +} + +static int _check_version(char *version, size_t size, int log_suppress) +{ + struct dm_task *task; + int r; + + if (!(task = dm_task_create(DM_DEVICE_VERSION))) { + log_error("Failed to get device-mapper version"); + version[0] = '\0'; + return 0; + } + + if (log_suppress) + _log_suppress = 1; + + r = dm_task_run(task); + dm_task_get_driver_version(task, version, size); + dm_task_destroy(task); + _log_suppress = 0; + + return r; +} + +/* + * Find out device-mapper's major version number the first time + * this is called and whether or not we support it. + */ +int dm_check_version(void) +{ + char libversion[64], dmversion[64]; + const char *compat = ""; + + if (_version_checked) + return _version_ok; + + _version_checked = 1; + + if (_check_version(dmversion, sizeof(dmversion), _dm_compat)) + return 1; + + if (!_dm_compat) + goto bad; + + log_verbose("device-mapper ioctl protocol version %d failed. " + "Trying protocol version 1.", _dm_version); + _dm_version = 1; + if (_check_version(dmversion, sizeof(dmversion), 0)) { + log_verbose("Using device-mapper ioctl protocol version 1"); + return 1; + } + + compat = "(compat)"; + + dm_get_library_version(libversion, sizeof(libversion)); + + log_error("Incompatible libdevmapper %s%s and kernel driver %s", + libversion, compat, dmversion); + + bad: + _version_ok = 0; + return 0; +} + +void *dm_get_next_target(struct dm_task *dmt, void *next, + uint64_t *start, uint64_t *length, + char **target_type, char **params) +{ + struct target *t = (struct target *) next; + + if (!t) + t = dmt->head; + + if (!t) + return NULL; + + *start = t->start; + *length = t->length; + *target_type = t->type; + *params = t->params; + + return t->next; +} + +/* Unmarshall the target info returned from a status call */ +static int _unmarshal_status(struct dm_task *dmt, struct dm_ioctl *dmi) +{ + char *outbuf = (char *) dmi + dmi->data_start; + char *outptr = outbuf; + uint32_t i; + struct dm_target_spec *spec; + + for (i = 0; i < dmi->target_count; i++) { + spec = (struct dm_target_spec *) outptr; + if (!dm_task_add_target(dmt, spec->sector_start, + spec->length, + spec->target_type, + outptr + sizeof(*spec))) + return 0; + + outptr = outbuf + spec->next; + } + + return 1; +} + +int dm_format_dev(char *buf, int bufsize, uint32_t dev_major, + uint32_t dev_minor) +{ + int r; + +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_format_dev_v1(buf, bufsize, dev_major, dev_minor); +#endif + + if (bufsize < 8) + return 0; + + r = snprintf(buf, bufsize, "%03u:%03u", dev_major, dev_minor); + if (r < 0 || r > bufsize - 1) + return 0; + + return 1; +} + +int dm_task_get_info(struct dm_task *dmt, struct dm_info *info) +{ +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_info_v1(dmt, info); +#endif + + if (!dmt->dmi.v4) + return 0; + + memset(info, 0, sizeof(*info)); + + info->exists = dmt->dmi.v4->flags & DM_EXISTS_FLAG ? 1 : 0; + if (!info->exists) + return 1; + + info->suspended = dmt->dmi.v4->flags & DM_SUSPEND_FLAG ? 1 : 0; + info->read_only = dmt->dmi.v4->flags & DM_READONLY_FLAG ? 1 : 0; + info->live_table = dmt->dmi.v4->flags & DM_ACTIVE_PRESENT_FLAG ? 1 : 0; + info->inactive_table = dmt->dmi.v4->flags & DM_INACTIVE_PRESENT_FLAG ? + 1 : 0; + info->target_count = dmt->dmi.v4->target_count; + info->open_count = dmt->dmi.v4->open_count; + info->event_nr = dmt->dmi.v4->event_nr; + info->major = MAJOR(dmt->dmi.v4->dev); + info->minor = MINOR(dmt->dmi.v4->dev); + + return 1; +} + +const char *dm_task_get_name(struct dm_task *dmt) +{ +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_name_v1(dmt); +#endif + + return (dmt->dmi.v4->name); +} + +const char *dm_task_get_uuid(struct dm_task *dmt) +{ +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_uuid_v1(dmt); +#endif + + return (dmt->dmi.v4->uuid); +} + +struct dm_deps *dm_task_get_deps(struct dm_task *dmt) +{ +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_deps_v1(dmt); +#endif + + return (struct dm_deps *) (((void *) dmt->dmi.v4) + + dmt->dmi.v4->data_start); +} + +struct dm_names *dm_task_get_names(struct dm_task *dmt) +{ +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_get_names_v1(dmt); +#endif + + return (struct dm_names *) (((void *) dmt->dmi.v4) + + dmt->dmi.v4->data_start); +} + +int dm_task_set_ro(struct dm_task *dmt) +{ + dmt->read_only = 1; + return 1; +} + +int dm_task_set_newname(struct dm_task *dmt, const char *newname) +{ + if (!(dmt->newname = strdup(newname))) { + log_error("dm_task_set_newname: strdup(%s) failed", newname); + return 0; + } + + return 1; +} + +int dm_task_set_event_nr(struct dm_task *dmt, uint32_t event_nr) +{ + dmt->event_nr = event_nr; + + return 1; +} + +struct target *create_target(uint64_t start, uint64_t len, const char *type, + const char *params) +{ + struct target *t = malloc(sizeof(*t)); + + if (!t) { + log_error("create_target: malloc(%d) failed", sizeof(*t)); + return NULL; + } + + memset(t, 0, sizeof(*t)); + + if (!(t->params = strdup(params))) { + log_error("create_target: strdup(params) failed"); + goto bad; + } + + if (!(t->type = strdup(type))) { + log_error("create_target: strdup(type) failed"); + goto bad; + } + + t->start = start; + t->length = len; + return t; + + bad: + free(t->params); + free(t->type); + free(t); + return NULL; +} + +static void *_add_target(struct target *t, void *out, void *end) +{ + void *out_sp = out; + struct dm_target_spec sp; + size_t sp_size = sizeof(struct dm_target_spec); + int len; + const char no_space[] = "Ran out of memory building ioctl parameter"; + + out += sp_size; + if (out >= end) { + log_error(no_space); + return NULL; + } + + sp.status = 0; + sp.sector_start = t->start; + sp.length = t->length; + strncpy(sp.target_type, t->type, sizeof(sp.target_type)); + + len = strlen(t->params); + + if ((out + len + 1) >= end) { + log_error(no_space); + + log_error("t->params= '%s'", t->params); + return NULL; + } + strcpy((char *) out, t->params); + out += len + 1; + + /* align next block */ + out = _align(out, ALIGNMENT); + + sp.next = out - out_sp; + memcpy(out_sp, &sp, sp_size); + + return out; +} + +static struct dm_ioctl *_flatten(struct dm_task *dmt) +{ + const size_t min_size = 16 * 1024; + const int (*version)[3]; + + struct dm_ioctl *dmi; + struct target *t; + size_t len = sizeof(struct dm_ioctl); + void *b, *e; + int count = 0; + + for (t = dmt->head; t; t = t->next) { + len += sizeof(struct dm_target_spec); + len += strlen(t->params) + 1 + ALIGNMENT; + count++; + } + + if (count && dmt->newname) { + log_error("targets and newname are incompatible"); + return NULL; + } + + if (dmt->newname) + len += strlen(dmt->newname) + 1; + + /* + * Give len a minimum size so that we have space to store + * dependencies or status information. + */ + if (len < min_size) + len = min_size; + + if (!(dmi = malloc(len))) + return NULL; + + memset(dmi, 0, len); + + version = &_cmd_data_v4[dmt->type].version; + + dmi->version[0] = (*version)[0]; + dmi->version[1] = (*version)[1]; + dmi->version[2] = (*version)[2]; + + dmi->data_size = len; + dmi->data_start = sizeof(struct dm_ioctl); + + if (dmt->dev_name) + strncpy(dmi->name, dmt->dev_name, sizeof(dmi->name)); + + if (dmt->type == DM_DEVICE_SUSPEND) + dmi->flags |= DM_SUSPEND_FLAG; + if (dmt->read_only) + dmi->flags |= DM_READONLY_FLAG; + + if (dmt->minor >= 0) { + if (dmt->major <= 0) { + log_error("Missing major number for persistent device"); + return NULL; + } + dmi->flags |= DM_PERSISTENT_DEV_FLAG; + dmi->dev = MKDEV(dmt->major, dmt->minor); + } + + if (dmt->uuid) + strncpy(dmi->uuid, dmt->uuid, sizeof(dmi->uuid)); + + dmi->target_count = count; + dmi->event_nr = dmt->event_nr; + + b = (void *) (dmi + 1); + e = (void *) ((char *) dmi + len); + + for (t = dmt->head; t; t = t->next) + if (!(b = _add_target(t, b, e))) + goto bad; + + if (dmt->newname) + strcpy(b, dmt->newname); + + return dmi; + + bad: + free(dmi); + return NULL; +} + +static int _create_and_load_v4(struct dm_task *dmt) +{ + struct dm_task *task; + int r; + + /* Use new task struct to create the device */ + if (!(task = dm_task_create(DM_DEVICE_CREATE))) { + log_error("Failed to create device-mapper task struct"); + return 0; + } + + /* Copy across relevant fields */ + if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) { + dm_task_destroy(task); + return 0; + } + + if (dmt->uuid && !dm_task_set_uuid(task, dmt->uuid)) { + dm_task_destroy(task); + return 0; + } + + task->major = dmt->major; + task->minor = dmt->minor; + + r = dm_task_run(task); + dm_task_destroy(task); + if (!r) + return r; + + /* Next load the table */ + if (!(task = dm_task_create(DM_DEVICE_RELOAD))) { + log_error("Failed to create device-mapper task struct"); + return 0; + } + + /* Copy across relevant fields */ + if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) { + dm_task_destroy(task); + return 0; + } + + task->read_only = dmt->read_only; + task->head = dmt->head; + task->tail = dmt->tail; + + r = dm_task_run(task); + + task->head = NULL; + task->tail = NULL; + dm_task_destroy(task); + if (!r) + return r; + + /* Use the original structure last so the info will be correct */ + dmt->type = DM_DEVICE_RESUME; + dmt->uuid = NULL; + free(dmt->uuid); + + r = dm_task_run(dmt); + + return r; +} + +int dm_task_run(struct dm_task *dmt) +{ + struct dm_ioctl *dmi = NULL; + unsigned int command; + +#ifdef DM_COMPAT + if (_dm_version == 1) + return _dm_task_run_v1(dmt); +#endif + + if ((unsigned) dmt->type >= + (sizeof(_cmd_data_v4) / sizeof(*_cmd_data_v4))) { + log_error("Internal error: unknown device-mapper task %d", + dmt->type); + goto bad; + } + + command = _cmd_data_v4[dmt->type].cmd; + + /* Old-style creation had a table supplied */ + if (dmt->type == DM_DEVICE_CREATE && dmt->head) + return _create_and_load_v4(dmt); + + if (!_open_control()) + return 0; + + dmi = _flatten(dmt); + if (!dmi) { + log_error("Couldn't create ioctl argument"); + return 0; + } + + if (dmt->type == DM_DEVICE_TABLE) + dmi->flags |= DM_STATUS_TABLE_FLAG; + + dmi->flags |= DM_EXISTS_FLAG; /* FIXME */ + log_debug("dm %s %s %s %s", _cmd_data_v4[dmt->type].name, dmi->name, + dmi->uuid, dmt->newname ? dmt->newname : ""); + if (ioctl(_control_fd, command, dmi) < 0) { + if (errno == ENXIO && ((dmt->type == DM_DEVICE_INFO) || + (dmt->type == DM_DEVICE_MKNODES))) { + dmi->flags &= ~DM_EXISTS_FLAG; /* FIXME */ + goto ignore_error; + } + if (_log_suppress) + log_verbose("device-mapper ioctl cmd %d failed: %s", + _IOC_NR(command), strerror(errno)); + else + log_error("device-mapper ioctl cmd %d failed: %s", + _IOC_NR(command), strerror(errno)); + goto bad; + } + + ignore_error: + switch (dmt->type) { + case DM_DEVICE_CREATE: + add_dev_node(dmt->dev_name, MAJOR(dmi->dev), MINOR(dmi->dev)); + break; + + case DM_DEVICE_REMOVE: + rm_dev_node(dmt->dev_name); + break; + + case DM_DEVICE_RENAME: + rename_dev_node(dmt->dev_name, dmt->newname); + break; + + case DM_DEVICE_MKNODES: + if (dmi->flags & DM_EXISTS_FLAG) + add_dev_node(dmt->dev_name, MAJOR(dmi->dev), + MINOR(dmi->dev)); + else + rm_dev_node(dmt->dev_name); + break; + + case DM_DEVICE_STATUS: + case DM_DEVICE_TABLE: + case DM_DEVICE_WAITEVENT: + if (!_unmarshal_status(dmt, dmi)) + goto bad; + break; + } + + dmt->dmi.v4 = dmi; + return 1; + + bad: + free(dmi); + return 0; +} + +void dm_lib_release(void) +{ + if (_control_fd != -1) { + close(_control_fd); + _control_fd = -1; + } + update_devs(); +} + +void dm_lib_exit(void) +{ + if (_control_fd != -1) { + close(_control_fd); + _control_fd = -1; + } + _version_ok = 1; + _version_checked = 0; +} diff --git a/extras/multipath-tools/libdevmapper/ioctl/libdm-compat.h b/extras/multipath-tools/libdevmapper/ioctl/libdm-compat.h new file mode 100644 index 00000000..af7a9f1f --- /dev/null +++ b/extras/multipath-tools/libdevmapper/ioctl/libdm-compat.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#ifndef _LINUX_LIBDM_COMPAT_H +#define _LINUX_LIBDM_COMPAT_H + +#include +#include +#include +#include + +struct dm_task; +struct dm_info; + +/* + * Old versions of structures for backwards compatibility. + */ + +struct dm_ioctl_v1 { + uint32_t version[3]; /* in/out */ + uint32_t data_size; /* total size of data passed in + * including this struct */ + + uint32_t data_start; /* offset to start of data + * relative to start of this struct */ + + int32_t target_count; /* in/out */ + int32_t open_count; /* out */ + uint32_t flags; /* in/out */ + + __kernel_dev_t dev; /* in/out */ + + char name[DM_NAME_LEN]; /* device name */ + char uuid[DM_UUID_LEN]; /* unique identifier for + * the block device */ +}; + +struct dm_target_spec_v1 { + int32_t status; /* used when reading from kernel only */ + uint64_t sector_start; + uint32_t length; + uint32_t next; + + char target_type[DM_MAX_TYPE_NAME]; + +}; + +struct dm_target_deps_v1 { + uint32_t count; + + __kernel_dev_t dev[0]; /* out */ +}; + +enum { + /* Top level cmds */ + DM_VERSION_CMD_V1 = 0, + DM_REMOVE_ALL_CMD_V1, + + /* device level cmds */ + DM_DEV_CREATE_CMD_V1, + DM_DEV_REMOVE_CMD_V1, + DM_DEV_RELOAD_CMD_V1, + DM_DEV_RENAME_CMD_V1, + DM_DEV_SUSPEND_CMD_V1, + DM_DEV_DEPS_CMD_V1, + DM_DEV_STATUS_CMD_V1, + + /* target level cmds */ + DM_TARGET_STATUS_CMD_V1, + DM_TARGET_WAIT_CMD_V1, +}; + +#define DM_VERSION_V1 _IOWR(DM_IOCTL, DM_VERSION_CMD_V1, struct dm_ioctl) +#define DM_REMOVE_ALL_V1 _IOWR(DM_IOCTL, DM_REMOVE_ALL_CMD_V1, struct dm_ioctl) + +#define DM_DEV_CREATE_V1 _IOWR(DM_IOCTL, DM_DEV_CREATE_CMD_V1, struct dm_ioctl) +#define DM_DEV_REMOVE_V1 _IOWR(DM_IOCTL, DM_DEV_REMOVE_CMD_V1, struct dm_ioctl) +#define DM_DEV_RELOAD_V1 _IOWR(DM_IOCTL, DM_DEV_RELOAD_CMD_V1, struct dm_ioctl) +#define DM_DEV_SUSPEND_V1 _IOWR(DM_IOCTL, DM_DEV_SUSPEND_CMD_V1, struct dm_ioctl) +#define DM_DEV_RENAME_V1 _IOWR(DM_IOCTL, DM_DEV_RENAME_CMD_V1, struct dm_ioctl) +#define DM_DEV_DEPS_V1 _IOWR(DM_IOCTL, DM_DEV_DEPS_CMD_V1, struct dm_ioctl) +#define DM_DEV_STATUS_V1 _IOWR(DM_IOCTL, DM_DEV_STATUS_CMD_V1, struct dm_ioctl) + +#define DM_TARGET_STATUS_V1 _IOWR(DM_IOCTL, DM_TARGET_STATUS_CMD_V1, struct dm_ioctl) +#define DM_TARGET_WAIT_V1 _IOWR(DM_IOCTL, DM_TARGET_WAIT_CMD_V1, struct dm_ioctl) + +/* *INDENT-OFF* */ +static struct cmd_data _cmd_data_v1[] = { + { "create", DM_DEV_CREATE_V1, {1, 0, 0} }, + { "reload", DM_DEV_RELOAD_V1, {1, 0, 0} }, + { "remove", DM_DEV_REMOVE_V1, {1, 0, 0} }, + { "remove_all", DM_REMOVE_ALL_V1, {1, 0, 0} }, + { "suspend", DM_DEV_SUSPEND_V1, {1, 0, 0} }, + { "resume", DM_DEV_SUSPEND_V1, {1, 0, 0} }, + { "info", DM_DEV_STATUS_V1, {1, 0, 0} }, + { "deps", DM_DEV_DEPS_V1, {1, 0, 0} }, + { "rename", DM_DEV_RENAME_V1, {1, 0, 0} }, + { "version", DM_VERSION_V1, {1, 0, 0} }, + { "status", DM_TARGET_STATUS_V1, {1, 0, 0} }, + { "table", DM_TARGET_STATUS_V1, {1, 0, 0} }, + { "waitevent", DM_TARGET_WAIT_V1, {1, 0, 0} }, + { "names", 0, {4, 0, 0} }, + { "clear", 0, {4, 0, 0} }, + { "mknodes", 0, {4, 0, 0} }, +}; +/* *INDENT-ON* */ + +#endif diff --git a/extras/multipath-tools/libdevmapper/ioctl/libdm-targets.h b/extras/multipath-tools/libdevmapper/ioctl/libdm-targets.h new file mode 100644 index 00000000..a8c0e205 --- /dev/null +++ b/extras/multipath-tools/libdevmapper/ioctl/libdm-targets.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#ifndef LIB_DMTARGETS_H +#define LIB_DMTARGETS_H + +#include + +struct dm_ioctl; +struct dm_ioctl_v1; + +struct target { + uint64_t start; + uint64_t length; + char *type; + char *params; + + struct target *next; +}; + +struct dm_task { + int type; + char *dev_name; + + struct target *head, *tail; + + int read_only; + uint32_t event_nr; + int major; + int minor; + union { + struct dm_ioctl *v4; + struct dm_ioctl_v1 *v1; + } dmi; + char *newname; + + char *uuid; +}; + +struct cmd_data { + const char *name; + const int cmd; + const int version[3]; +}; + +int dm_check_version(void); + +#endif diff --git a/extras/multipath-tools/libdevmapper/libdevmapper.h b/extras/multipath-tools/libdevmapper/libdevmapper.h new file mode 100644 index 00000000..6549af36 --- /dev/null +++ b/extras/multipath-tools/libdevmapper/libdevmapper.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#ifndef LIB_DEVICE_MAPPER_H +#define LIB_DEVICE_MAPPER_H + +#include +#include + +#ifdef linux +# include +#endif + +/* + * Since it is quite laborious to build the ioctl + * arguments for the device-mapper people are + * encouraged to use this library. + * + * You will need to build a struct dm_task for + * each ioctl command you want to execute. + */ + +typedef void (*dm_log_fn) (int level, const char *file, int line, + const char *f, ...); + +/* + * The library user may wish to register their own + * logging function, by default errors go to + * stderr. + */ +void dm_log_init(dm_log_fn fn); +void dm_log_init_verbose(int level); + +enum { + DM_DEVICE_CREATE, + DM_DEVICE_RELOAD, + DM_DEVICE_REMOVE, + DM_DEVICE_REMOVE_ALL, + + DM_DEVICE_SUSPEND, + DM_DEVICE_RESUME, + + DM_DEVICE_INFO, + DM_DEVICE_DEPS, + DM_DEVICE_RENAME, + + DM_DEVICE_VERSION, + + DM_DEVICE_STATUS, + DM_DEVICE_TABLE, + DM_DEVICE_WAITEVENT, + + DM_DEVICE_LIST, + + DM_DEVICE_CLEAR, + + DM_DEVICE_MKNODES +}; + +struct dm_task; + +struct dm_task *dm_task_create(int type); +void dm_task_destroy(struct dm_task *dmt); + +int dm_task_set_name(struct dm_task *dmt, const char *name); +int dm_task_set_uuid(struct dm_task *dmt, const char *uuid); + +/* + * Retrieve attributes after an info. + */ +struct dm_info { + int exists; + int suspended; + int live_table; + int inactive_table; + int32_t open_count; + uint32_t event_nr; + uint32_t major; + uint32_t minor; /* minor device number */ + int read_only; /* 0:read-write; 1:read-only */ + + int32_t target_count; +}; + +struct dm_deps { + uint32_t count; + uint32_t filler; + uint64_t device[0]; +}; + +struct dm_names { + uint64_t dev; + uint32_t next; /* Offset to next struct from start of this struct */ + char name[0]; +}; + +int dm_get_library_version(char *version, size_t size); +int dm_task_get_driver_version(struct dm_task *dmt, char *version, size_t size); +int dm_task_get_info(struct dm_task *dmt, struct dm_info *dmi); +const char *dm_task_get_name(struct dm_task *dmt); +const char *dm_task_get_uuid(struct dm_task *dmt); + +struct dm_deps *dm_task_get_deps(struct dm_task *dmt); +struct dm_names *dm_task_get_names(struct dm_task *dmt); + +int dm_task_set_ro(struct dm_task *dmt); +int dm_task_set_newname(struct dm_task *dmt, const char *newname); +int dm_task_set_minor(struct dm_task *dmt, int minor); +int dm_task_set_major(struct dm_task *dmt, int major); +int dm_task_set_event_nr(struct dm_task *dmt, uint32_t event_nr); + +/* + * Use these to prepare for a create or reload. + */ +int dm_task_add_target(struct dm_task *dmt, + uint64_t start, + uint64_t size, const char *ttype, const char *params); + +/* + * Format major/minor numbers correctly for input to driver + */ +int dm_format_dev(char *buf, int bufsize, uint32_t dev_major, uint32_t dev_minor); + +/* Use this to retrive target information returned from a STATUS call */ +void *dm_get_next_target(struct dm_task *dmt, + void *next, uint64_t *start, uint64_t *length, + char **target_type, char **params); + +/* + * Call this to actually run the ioctl. + */ +int dm_task_run(struct dm_task *dmt); + +/* + * Configure the device-mapper directory + */ +int dm_set_dev_dir(const char *dir); +const char *dm_dir(void); + +/* Release library resources */ +void dm_lib_release(void); +void dm_lib_exit(void); + +#endif /* LIB_DEVICE_MAPPER_H */ diff --git a/extras/multipath-tools/libdevmapper/libdm-common.c b/extras/multipath-tools/libdevmapper/libdm-common.c new file mode 100644 index 00000000..b0affd1e --- /dev/null +++ b/extras/multipath-tools/libdevmapper/libdm-common.c @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#include "libdm-targets.h" +#include "libdm-common.h" +#include "list.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEV_DIR "/dev/" + +static char _dm_dir[PATH_MAX] = DEV_DIR DM_DIR; + +static int _verbose = 0; + +/* + * Library users can provide their own logging + * function. + */ +static void _default_log(int level, const char *file, int line, + const char *f, ...) +{ + va_list ap; + + if (level > _LOG_WARN && !_verbose) + return; + + va_start(ap, f); + + if (level < _LOG_WARN) + vfprintf(stderr, f, ap); + else + vprintf(f, ap); + + va_end(ap); + + if (level < _LOG_WARN) + fprintf(stderr, "\n"); + else + fprintf(stdout, "\n"); +} + +dm_log_fn _log = _default_log; + +void dm_log_init(dm_log_fn fn) +{ + _log = fn; +} + +void dm_log_init_verbose(int level) +{ + _verbose = level; +} + +static void _build_dev_path(char *buffer, size_t len, const char *dev_name) +{ + /* If there's a /, assume caller knows what they're doing */ + if (strchr(dev_name, '/')) + snprintf(buffer, len, "%s", dev_name); + else + snprintf(buffer, len, "%s/%s", _dm_dir, dev_name); +} + +int dm_get_library_version(char *version, size_t size) +{ + strncpy(version, DM_LIB_VERSION, size); + return 1; +} + +struct dm_task *dm_task_create(int type) +{ + struct dm_task *dmt = malloc(sizeof(*dmt)); + + if (!dm_check_version()) + return NULL; + + if (!dmt) { + log_error("dm_task_create: malloc(%d) failed", sizeof(*dmt)); + return NULL; + } + + memset(dmt, 0, sizeof(*dmt)); + + dmt->type = type; + dmt->minor = -1; + dmt->major = -1; + + return dmt; +} + +int dm_task_set_name(struct dm_task *dmt, const char *name) +{ + char *pos; + char path[PATH_MAX]; + struct stat st1, st2; + + if (dmt->dev_name) { + free(dmt->dev_name); + dmt->dev_name = NULL; + } + + /* If path was supplied, remove it if it points to the same device + * as its last component. + */ + if ((pos = strrchr(name, '/'))) { + snprintf(path, sizeof(path), "%s/%s", _dm_dir, pos + 1); + + if (stat(name, &st1) || stat(path, &st2) || + !(st1.st_dev == st2.st_dev)) { + log_error("dm_task_set_name: Device %s not found", + name); + return 0; + } + + name = pos + 1; + } + + if (!(dmt->dev_name = strdup(name))) { + log_error("dm_task_set_name: strdup(%s) failed", name); + return 0; + } + + return 1; +} + +int dm_task_set_uuid(struct dm_task *dmt, const char *uuid) +{ + if (dmt->uuid) { + free(dmt->uuid); + dmt->uuid = NULL; + } + + if (!(dmt->uuid = strdup(uuid))) { + log_error("dm_task_set_uuid: strdup(%s) failed", uuid); + return 0; + } + + return 1; +} + +int dm_task_set_major(struct dm_task *dmt, int major) +{ + dmt->major = major; + log_debug("Setting major: %d", dmt->major); + + return 1; +} + +int dm_task_set_minor(struct dm_task *dmt, int minor) +{ + dmt->minor = minor; + log_debug("Setting minor: %d", dmt->minor); + + return 1; +} + +int dm_task_add_target(struct dm_task *dmt, uint64_t start, uint64_t size, + const char *ttype, const char *params) +{ + struct target *t = create_target(start, size, ttype, params); + + if (!t) + return 0; + + if (!dmt->head) + dmt->head = dmt->tail = t; + else { + dmt->tail->next = t; + dmt->tail = t; + } + + return 1; +} + +static int _add_dev_node(const char *dev_name, uint32_t major, uint32_t minor) +{ + char path[PATH_MAX]; + struct stat info; + dev_t dev = MKDEV(major, minor); + + _build_dev_path(path, sizeof(path), dev_name); + + if (stat(path, &info) >= 0) { + if (!S_ISBLK(info.st_mode)) { + log_error("A non-block device file at '%s' " + "is already present", path); + return 0; + } + + if (info.st_rdev == dev) + return 1; + + if (unlink(path) < 0) { + log_error("Unable to unlink device node for '%s'", + dev_name); + return 0; + } + } + + if (mknod(path, S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP, dev) < 0) { + log_error("Unable to make device node for '%s'", dev_name); + return 0; + } + + return 1; +} + +static int _rename_dev_node(const char *old_name, const char *new_name) +{ + char oldpath[PATH_MAX]; + char newpath[PATH_MAX]; + struct stat info; + + _build_dev_path(oldpath, sizeof(oldpath), old_name); + _build_dev_path(newpath, sizeof(newpath), new_name); + + if (stat(newpath, &info) == 0) { + if (!S_ISBLK(info.st_mode)) { + log_error("A non-block device file at '%s' " + "is already present", newpath); + return 0; + } + + if (unlink(newpath) < 0) { + if (errno == EPERM) { + /* devfs, entry has already been renamed */ + return 1; + } + log_error("Unable to unlink device node for '%s'", + new_name); + return 0; + } + } + + if (rename(oldpath, newpath) < 0) { + log_error("Unable to rename device node from '%s' to '%s'", + old_name, new_name); + return 0; + } + + return 1; +} + +static int _rm_dev_node(const char *dev_name) +{ + char path[PATH_MAX]; + struct stat info; + + _build_dev_path(path, sizeof(path), dev_name); + + if (stat(path, &info) < 0) + return 1; + + if (unlink(path) < 0) { + log_error("Unable to unlink device node for '%s'", dev_name); + return 0; + } + + return 1; +} + +typedef enum { + NODE_ADD, + NODE_DEL, + NODE_RENAME +} node_op_t; + +static int _do_node_op(node_op_t type, const char *dev_name, uint32_t major, + uint32_t minor, const char *old_name) +{ + switch (type) { + case NODE_ADD: + return _add_dev_node(dev_name, major, minor); + case NODE_DEL: + return _rm_dev_node(dev_name); + case NODE_RENAME: + return _rename_dev_node(old_name, dev_name); + } + + return 1; +} + +static LIST_INIT(_node_ops); + +struct node_op_parms { + struct list list; + node_op_t type; + char *dev_name; + uint32_t major; + uint32_t minor; + char *old_name; + char names[0]; +}; + +static void _store_str(char **pos, char **ptr, const char *str) +{ + strcpy(*pos, str); + *ptr = *pos; + *pos += strlen(*ptr) + 1; +} + +static int _stack_node_op(node_op_t type, const char *dev_name, uint32_t major, + uint32_t minor, const char *old_name) +{ + struct node_op_parms *nop; + size_t len = strlen(dev_name) + strlen(old_name) + 2; + char *pos; + + if (!(nop = malloc(sizeof(*nop) + len))) { + log_error("Insufficient memory to stack mknod operation"); + return 0; + } + + pos = nop->names; + nop->type = type; + nop->major = major; + nop->minor = minor; + + _store_str(&pos, &nop->dev_name, dev_name); + _store_str(&pos, &nop->old_name, old_name); + + list_add(&_node_ops, &nop->list); + + return 1; +} + +static void _pop_node_ops(void) +{ + struct list *noph, *nopht; + struct node_op_parms *nop; + + list_iterate_safe(noph, nopht, &_node_ops) { + nop = list_item(noph, struct node_op_parms); + _do_node_op(nop->type, nop->dev_name, nop->major, nop->minor, + nop->old_name); + list_del(&nop->list); + free(nop); + } +} + +int add_dev_node(const char *dev_name, uint32_t major, uint32_t minor) +{ + return _stack_node_op(NODE_ADD, dev_name, major, minor, ""); +} + +int rename_dev_node(const char *old_name, const char *new_name) +{ + return _stack_node_op(NODE_RENAME, new_name, 0, 0, old_name); +} + +int rm_dev_node(const char *dev_name) +{ + return _stack_node_op(NODE_DEL, dev_name, 0, 0, ""); +} + +void update_devs(void) +{ + _pop_node_ops(); +} + +int dm_set_dev_dir(const char *dir) +{ + snprintf(_dm_dir, sizeof(_dm_dir), "%s%s", dir, DM_DIR); + return 1; +} + +const char *dm_dir(void) +{ + return _dm_dir; +} diff --git a/extras/multipath-tools/libdevmapper/libdm-common.h b/extras/multipath-tools/libdevmapper/libdm-common.h new file mode 100644 index 00000000..2b712428 --- /dev/null +++ b/extras/multipath-tools/libdevmapper/libdm-common.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#ifndef LIB_DMCOMMON_H +#define LIB_DMCOMMON_H + +#include "libdevmapper.h" + +#define _LOG_DEBUG 7 +#define _LOG_INFO 6 +#define _LOG_NOTICE 5 +#define _LOG_WARN 4 +#define _LOG_ERR 3 +#define _LOG_FATAL 2 + +extern dm_log_fn _log; + +#define log_error(msg, x...) _log(_LOG_ERR, __FILE__, __LINE__, msg, ## x) +#define log_print(msg, x...) _log(_LOG_WARN, __FILE__, __LINE__, msg, ## x) +#define log_verbose(msg, x...) _log(_LOG_NOTICE, __FILE__, __LINE__, msg, ## x) +#define log_very_verbose(msg, x...) _log(_LOG_INFO, __FILE__, __LINE__, msg, ## x) +#define log_debug(msg, x...) _log(_LOG_DEBUG, __FILE__, __LINE__, msg, ## x) + +struct target *create_target(uint64_t start, + uint64_t len, + const char *type, const char *params); + +int add_dev_node(const char *dev_name, uint32_t minor, uint32_t major); +int rm_dev_node(const char *dev_name); +int rename_dev_node(const char *old_name, const char *new_name); +void update_devs(void); + +#define DM_LIB_VERSION "1.00.07-ioctl (2003-11-21)" + +#endif diff --git a/extras/multipath-tools/libdevmapper/list.h b/extras/multipath-tools/libdevmapper/list.h new file mode 100644 index 00000000..df3d32ae --- /dev/null +++ b/extras/multipath-tools/libdevmapper/list.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2001 Sistina Software + * + * This file is released under the LGPL. + */ + +#ifndef _LVM_LIST_H +#define _LVM_LIST_H + +#include + +struct list { + struct list *n, *p; +}; + +#define LIST_INIT(name) struct list name = { &(name), &(name) } + +static inline void list_init(struct list *head) +{ + head->n = head->p = head; +} + +static inline void list_add(struct list *head, struct list *elem) +{ + assert(head->n); + + elem->n = head; + elem->p = head->p; + + head->p->n = elem; + head->p = elem; +} + +static inline void list_add_h(struct list *head, struct list *elem) +{ + assert(head->n); + + elem->n = head->n; + elem->p = head; + + head->n->p = elem; + head->n = elem; +} + +static inline void list_del(struct list *elem) +{ + elem->n->p = elem->p; + elem->p->n = elem->n; +} + +static inline int list_empty(struct list *head) +{ + return head->n == head; +} + +static inline int list_end(struct list *head, struct list *elem) +{ + return elem->n == head; +} + +static inline struct list *list_next(struct list *head, struct list *elem) +{ + return (list_end(head, elem) ? NULL : elem->n); +} + +#define list_iterate(v, head) \ + for (v = (head)->n; v != head; v = v->n) + +#define list_uniterate(v, head, start) \ + for (v = (start)->p; v != head; v = v->p) + +#define list_iterate_safe(v, t, head) \ + for (v = (head)->n, t = v->n; v != head; v = t, t = v->n) + +static inline unsigned int list_size(const struct list *head) +{ + unsigned int s = 0; + const struct list *v; + + list_iterate(v, head) + s++; + + return s; +} + +#define list_item(v, t) \ + ((t *)((uintptr_t)(v) - (uintptr_t)&((t *) 0)->list)) + +#define list_struct_base(v, t, h) \ + ((t *)((uintptr_t)(v) - (uintptr_t)&((t *) 0)->h)) + +/* Given a known element in a known structure, locate another */ +#define struct_field(v, t, e, f) \ + (((t *)((uintptr_t)(v) - (uintptr_t)&((t *) 0)->e))->f) + +/* Given a known element in a known structure, locate the list head */ +#define list_head(v, t, e) struct_field(v, t, e, list) + +#endif diff --git a/extras/multipath-tools/multipath/Makefile b/extras/multipath-tools/multipath/Makefile new file mode 100644 index 00000000..4121f4fc --- /dev/null +++ b/extras/multipath-tools/multipath/Makefile @@ -0,0 +1,60 @@ +# Makefile +# +# Copyright (C) 2003 Christophe Varoqui, + +EXEC = multipath + +prefix = +exec_prefix = ${prefix} +bindir = ${exec_prefix}/sbin +udevdir = ../../.. +klibcdir = $(udevdir)/klibc +sysfsdir = $(udevdir)/libsysfs +mandir = /usr/share/man/man8 +libdmdir = ../libdevmapper +arch = i386 +klibcarch = $(klibcdir)/klibc/arch/$(arch)/include + +CC = gcc +GZIP = /bin/gzip -9 -c + +GCCINCDIR := ${shell $(CC) -print-search-dirs | sed -ne "s/install: \(.*\)/\1include/gp"} +KERNEL_DIR = /lib/modules/${shell uname -r}/build +CFLAGS = -pipe -g -O2 -Wall -Wunused -Wstrict-prototypes -nostdinc \ + -I$(klibcdir)/klibc/include -I$(klibcdir)/klibc/include/bits32 \ + -I$(GCCINCDIR) -I$(KERNEL_DIR)/include -I$(sysfsdir) -I. -I$(klibcarch) + +OBJS = devinfo.o main.o +CRT0 = $(klibcdir)/klibc/crt0.o +LIB = $(klibcdir)/klibc/libc.a +LIBGCC := $(shell $(CC) -print-libgcc-file-name ) + +DMOBJS = $(libdmdir)/libdm-common.o $(libdmdir)/ioctl/libdevmapper.o +SYSFSOBJS = $(sysfsdir)/dlist.o $(sysfsdir)/sysfs_bus.o \ + $(sysfsdir)/sysfs_class.o $(sysfsdir)/sysfs_device.o \ + $(sysfsdir)/sysfs_dir.o $(sysfsdir)/sysfs_driver.o \ + $(sysfsdir)/sysfs_utils.o + +$(EXEC): $(OBJS) + $(LD) -o $(EXEC) $(CRT0) $(OBJS) $(SYSFSOBJS) $(DMOBJS) $(LIB) $(LIBGCC) + strip $(EXEC) + $(GZIP) $(EXEC).8 > $(EXEC).8.gz + +clean: + rm -f core *.o $(EXEC) *.gz + +install: + install -d $(DESTDIR)$(bindir) + install -m 755 $(EXEC) $(DESTDIR)$(bindir)/ + install -d $(DESTDIR)/etc/hotplug.d/scsi/ + install -m 755 multipath.hotplug $(DESTDIR)/etc/hotplug.d/scsi/ + install -d $(DESTDIR)$(mandir) + install -m 644 multipath.8.gz $(DESTDIR)$(mandir) + +uninstall: + rm $(DESTDIR)/etc/hotplug.d/scsi/multipath.hotplug + rm $(DESTDIR)$(bindir)/$(EXEC) + rm $(DESTDIR)$(mandir)/multipath.8.gz + +# Code dependencies +main.o: main.c main.h sg_include.h devinfo.h diff --git a/extras/multipath-tools/multipath/devinfo.c b/extras/multipath-tools/multipath/devinfo.c new file mode 100644 index 00000000..21890621 --- /dev/null +++ b/extras/multipath-tools/multipath/devinfo.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include +#include "devinfo.h" +#include "sg_include.h" + +#define FILE_NAME_SIZE 255 + +void +basename(char * str1, char * str2) +{ + char *p = str1 + (strlen(str1) - 1); + + while (*--p != '/') + continue; + strcpy(str2, ++p); +} + +static int +do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, + void *resp, int mx_resp_len, int noisy) +{ + unsigned char inqCmdBlk[INQUIRY_CMDLEN] = + { INQUIRY_CMD, 0, 0, 0, 0, 0 }; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + + if (cmddt) + inqCmdBlk[1] |= 2; + if (evpd) + inqCmdBlk[1] |= 1; + inqCmdBlk[2] = (unsigned char) pg_op; + inqCmdBlk[4] = (unsigned char) mx_resp_len; + memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof (inqCmdBlk); + io_hdr.mx_sb_len = sizeof (sense_b); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = mx_resp_len; + io_hdr.dxferp = resp; + io_hdr.cmdp = inqCmdBlk; + io_hdr.sbp = sense_b; + io_hdr.timeout = DEF_TIMEOUT; + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("SG_IO (inquiry) error"); + return -1; + } + + /* treat SG_ERR here to get rid of sg_err.[ch] */ + io_hdr.status &= 0x7e; + if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && + (0 == io_hdr.driver_status)) + return 0; + if ((SCSI_CHECK_CONDITION == io_hdr.status) || + (SCSI_COMMAND_TERMINATED == io_hdr.status) || + (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { + if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { + int sense_key; + unsigned char * sense_buffer = io_hdr.sbp; + if (sense_buffer[0] & 0x2) + sense_key = sense_buffer[1] & 0xf; + else + sense_key = sense_buffer[2] & 0xf; + if(RECOVERED_ERROR == sense_key) + return 0; + } + } + return -1; +} + +int +get_serial (char * str, char * devname) +{ + int fd; + int len; + char buff[MX_ALLOC_LEN + 1]; + + if ((fd = open(devname, O_RDONLY)) < 0) + return 0; + + if (0 == do_inq(fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, 0)) { + len = buff[3]; + if (len > 0) { + memcpy(str, buff + 4, len); + buff[len] = '\0'; + } + close(fd); + return 1; + } + close(fd); + return 0; +} + +int +get_lun_strings(char * vendor_id, char * product_id, char * rev, char * devname) +{ + int fd; + char buff[36]; + char attr_path[FILE_NAME_SIZE]; + char sysfs_path[FILE_NAME_SIZE]; + char basedev[FILE_NAME_SIZE]; + + if (0 == sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) { + /* sysfs style */ + basename(devname, basedev); + + sprintf(attr_path, "%s/block/%s/device/vendor", + sysfs_path, basedev); + if (0 > sysfs_read_attribute_value(attr_path, + vendor_id, 8)) return 0; + + sprintf(attr_path, "%s/block/%s/device/model", + sysfs_path, basedev); + if (0 > sysfs_read_attribute_value(attr_path, + product_id, 16)) return 0; + + sprintf(attr_path, "%s/block/%s/device/rev", + sysfs_path, basedev); + if (0 > sysfs_read_attribute_value(attr_path, + rev, 4)) return 0; + } else { + /* ioctl style */ + if ((fd = open(devname, O_RDONLY)) < 0) + return 0; + if (0 != do_inq(fd, 0, 0, 0, buff, 36, 1)) + return 0; + memcpy(vendor_id, &buff[8], 8); + memcpy(product_id, &buff[16], 16); + memcpy(rev, &buff[32], 4); + close(fd); + return 1; + } + return 0; +} + +static void +sprint_wwid(char * buff, const char * str) +{ + int i; + const char *p; + char *cursor; + unsigned char c; + + p = str; + cursor = buff; + for (i = 0; i <= WWID_SIZE / 2 - 1; i++) { + c = *p++; + sprintf(cursor, "%.2x", (int) (unsigned char) c); + cursor += 2; + } + buff[WWID_SIZE - 1] = '\0'; +} + +/* get EVPD page 0x83 off 8 */ +/* tested ok with StorageWorks */ +int +get_evpd_wwid(char * devname, char * wwid) +{ + int fd; + char buff[MX_ALLOC_LEN + 1]; + + if ((fd = open(devname, O_RDONLY)) < 0) + return 0; + + if (0 == do_inq(fd, 0, 1, 0x83, buff, MX_ALLOC_LEN, 1)) { + sprint_wwid(wwid, &buff[8]); + close(fd); + return 1; /* success */ + } + + close(fd); + return 0; /* not good */ +} + +long +get_disk_size (char * devname) { + long size; + int fd; + char attr_path[FILE_NAME_SIZE]; + char sysfs_path[FILE_NAME_SIZE]; + char buff[FILE_NAME_SIZE]; + char basedev[FILE_NAME_SIZE]; + + if (0 == sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) { + basename(devname, basedev); + sprintf(attr_path, "%s/block/%s/size", + sysfs_path, basedev); + if (0 > sysfs_read_attribute_value(attr_path, buff, + FILE_NAME_SIZE * sizeof(char))) + return -1; + size = atoi(buff); + return size; + } else { + if ((fd = open(devname, O_RDONLY)) < 0) + return -1; + if(!ioctl(fd, BLKGETSIZE, &size)) + return size; + } + return -1; +} + +int +do_tur(char *dev) +{ + unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; + struct sg_io_hdr io_hdr; + unsigned char sense_buffer[32]; + int fd; + + fd = open(dev, O_RDONLY); + + if (fd < 0) + return 0; + + memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof (turCmdBlk); + io_hdr.mx_sb_len = sizeof (sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = turCmdBlk; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; + io_hdr.pack_id = 0; + + if (ioctl(fd, SG_IO, &io_hdr) < 0) { + close(fd); + return 0; + } + + close(fd); + + if (io_hdr.info & SG_INFO_OK_MASK) + return 0; + + return 1; +} diff --git a/extras/multipath-tools/multipath/devinfo.h b/extras/multipath-tools/multipath/devinfo.h new file mode 100644 index 00000000..ee17aba9 --- /dev/null +++ b/extras/multipath-tools/multipath/devinfo.h @@ -0,0 +1,21 @@ +#define INQUIRY_CMDLEN 6 +#define INQUIRY_CMD 0x12 +#define SENSE_BUFF_LEN 32 +#define DEF_TIMEOUT 60000 +#define RECOVERED_ERROR 0x01 +#define MX_ALLOC_LEN 255 +#define WWID_SIZE 33 +#define BLKGETSIZE _IO(0x12,96) +#define TUR_CMD_LEN 6 + +/* exerpt from "sg_err.h" */ +#define SCSI_CHECK_CONDITION 0x2 +#define SCSI_COMMAND_TERMINATED 0x22 +#define SG_ERR_DRIVER_SENSE 0x08 + +void basename (char *, char *); +int get_serial (char *, char *); +int get_lun_strings (char *, char *, char *, char *); +int get_evpd_wwid(char *, char *); +long get_disk_size (char *); +int do_tur (char *); diff --git a/extras/multipath-tools/multipath/main.c b/extras/multipath-tools/multipath/main.c new file mode 100644 index 00000000..8f06a8f9 --- /dev/null +++ b/extras/multipath-tools/multipath/main.c @@ -0,0 +1,895 @@ +/* + * Soft: multipath device mapper target autoconfig + * + * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $ + * + * Author: Copyright (C) 2003 Christophe Varoqui + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdevmapper/libdevmapper.h" +#include "main.h" +#include "devinfo.h" + +/* White list switch */ +static int +get_unique_id(struct path * mypath) +{ + int i; + static struct { + char * vendor; + char * product; + int iopolicy; + int (*getuid) (char *, char *); + } wlist[] = { + {"COMPAQ ", "HSV110 (C)COMPAQ", GROUP_BY_TUR, &get_evpd_wwid}, + {"COMPAQ ", "MSA1000 ", GROUP_BY_TUR, &get_evpd_wwid}, + {"COMPAQ ", "MSA1000 VOLUME ", GROUP_BY_TUR, &get_evpd_wwid}, + {"DEC ", "HSG80 ", GROUP_BY_TUR, &get_evpd_wwid}, + {"HP ", "HSV100 ", GROUP_BY_TUR, &get_evpd_wwid}, + {"HP ", "A6189A ", MULTIBUS, &get_evpd_wwid}, + {"HP ", "OPEN- ", MULTIBUS, &get_evpd_wwid}, + {"DDN ", "SAN DataDirector", MULTIBUS, &get_evpd_wwid}, + {"FSC ", "CentricStor ", MULTIBUS, &get_evpd_wwid}, + {"HITACHI ", "DF400 ", MULTIBUS, &get_evpd_wwid}, + {"HITACHI ", "DF500 ", MULTIBUS, &get_evpd_wwid}, + {"HITACHI ", "DF600 ", MULTIBUS, &get_evpd_wwid}, + {"IBM ", "ProFibre 4000R ", MULTIBUS, &get_evpd_wwid}, + {"SGI ", "TP9100 ", MULTIBUS, &get_evpd_wwid}, + {"SGI ", "TP9300 ", MULTIBUS, &get_evpd_wwid}, + {"SGI ", "TP9400 ", MULTIBUS, &get_evpd_wwid}, + {"SGI ", "TP9500 ", MULTIBUS, &get_evpd_wwid}, + {NULL, NULL, 0, NULL}, + }; + + for (i = 0; wlist[i].vendor; i++) { + if (strncmp(mypath->vendor_id, wlist[i].vendor, 8) == 0 && + strncmp(mypath->product_id, wlist[i].product, 16) == 0) { + mypath->iopolicy = wlist[i].iopolicy; + if (!wlist[i].getuid(mypath->sg_dev, mypath->wwid)) + return 0; + } + } + return 1; +} + +static int +sysfsdevice2devname (char *devname, char *device) +{ + char sysfs_path[FILE_NAME_SIZE]; + char block_path[FILE_NAME_SIZE]; + char link_path[FILE_NAME_SIZE]; + int r; + + if (sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) { + fprintf(stderr, "[device] feature available with sysfs only\n"); + exit (1); + } + + sprintf(link_path, "%s%s/block", sysfs_path, device); + + r = sysfs_get_link(link_path, block_path, FILE_NAME_SIZE); + + if (r != 0) + return 1; + + sysfs_get_name_from_path(block_path, devname, FILE_NAME_SIZE); + + return 0; +} + + +static int +devt2devname (char *devname, int major, int minor) +{ + struct sysfs_directory * sdir; + struct sysfs_directory * devp; + char sysfs_path[FILE_NAME_SIZE]; + char block_path[FILE_NAME_SIZE]; + char attr_path[FILE_NAME_SIZE]; + char attr_value[16]; + char attr_ref_value[16]; + + if (sysfs_get_mnt_path(sysfs_path, FILE_NAME_SIZE)) { + fprintf(stderr, "-D feature available with sysfs only\n"); + exit (1); + } + + sprintf(attr_ref_value, "%i:%i\n", major, minor); + sprintf(block_path, "%s/block", sysfs_path); + sdir = sysfs_open_directory(block_path); + sysfs_read_directory(sdir); + + dlist_for_each_data(sdir->subdirs, devp, struct sysfs_directory) { + sprintf(attr_path, "%s/%s/dev", block_path, devp->name); + sysfs_read_attribute_value(attr_path, attr_value, 16); + + if (!strcmp(attr_value, attr_ref_value)) { + sprintf(attr_path, "%s/%s", block_path, devp->name); + sysfs_get_name_from_path(attr_path, devname, FILE_NAME_SIZE); + break; + } + } + + sysfs_close_directory(sdir); + + return 0; +} + +static int +blacklist (char * dev) { + int i; + static struct { + char * headstr; + int lengh; + } blist[] = { + {"cciss", 5}, + {"hd", 2}, + {"md", 2}, + {"dm", 2}, + {"sr", 2}, + {"scd", 3}, + {"ram", 3}, + {"raw", 3}, + {NULL, 0}, + }; + + for (i = 0; blist[i].lengh; i++) { + if (strncmp(dev, blist[i].headstr, blist[i].lengh) == 0) + return 1; + } + return 0; +} + +static int +devinfo (struct path *curpath) +{ + get_lun_strings(curpath->vendor_id, + curpath->product_id, + curpath->rev, + curpath->sg_dev); + get_serial(curpath->serial, curpath->sg_dev); + curpath->tur = do_tur(curpath->sg_dev); + if (!get_unique_id(curpath)) + return 1; + + return 0; +} + + +static int +get_all_paths_sysfs(struct env * conf, struct path * all_paths) +{ + int k=0; + struct sysfs_directory * sdir; + struct sysfs_directory * devp; + struct sysfs_link * linkp; + char refwwid[WWID_SIZE]; + char empty_buff[WWID_SIZE]; + char buff[FILE_NAME_SIZE]; + char path[FILE_NAME_SIZE]; + struct path curpath; + + memset(empty_buff, 0, WWID_SIZE); + memset(refwwid, 0, WWID_SIZE); + + /* if called from hotplug, only consider the paths that relate + to the device pointed by conf.hotplugdev */ + + if (strncmp("/devices", conf->hotplugdev, 8) == 0) { + if (sysfsdevice2devname (buff, conf->hotplugdev)) + return 0; + + sprintf(curpath.sg_dev, "/dev/%s", buff); + + if (devinfo(&curpath)) + return 0; + + strcpy(refwwid, curpath.wwid); + memset(&curpath, 0, sizeof(path)); + } + + /* if major/minor specified on the cmd line, + only consider affiliated paths */ + + if (conf->major >= 0 && conf->minor >= 0) { + if (devt2devname(buff, conf->major, conf->minor)) + return 0; + + sprintf(curpath.sg_dev, "/dev/%s", buff); + + if (devinfo(&curpath)) + return 0; + + strcpy(refwwid, curpath.wwid); + memset(&curpath, 0, sizeof(path)); + } + + + sprintf(path, "%s/block", conf->sysfs_path); + sdir = sysfs_open_directory(path); + sysfs_read_directory(sdir); + + dlist_for_each_data(sdir->subdirs, devp, struct sysfs_directory) { + if (blacklist(devp->name)) + continue; + + sysfs_read_directory(devp); + + if(devp->links == NULL) + continue; + + dlist_for_each_data(devp->links, linkp, struct sysfs_link) { + if (!strncmp(linkp->name, "device", 6)) + break; + } + + if (linkp == NULL) { + continue; + } + + basename(devp->path, buff); + sprintf(curpath.sg_dev, "/dev/%s", buff); + + if(devinfo(&curpath)) { + memset(&curpath, 0, sizeof(path)); + continue; + } + + if (memcmp(empty_buff, refwwid, WWID_SIZE) != 0 && + strncmp(curpath.wwid, refwwid, WWID_SIZE) != 0) { + memset(&curpath, 0, sizeof(path)); + continue; + } + + strcpy(all_paths[k].sg_dev, curpath.sg_dev); + strcpy(all_paths[k].dev, curpath.sg_dev); + strcpy(all_paths[k].wwid, curpath.wwid); + strcpy(all_paths[k].vendor_id, curpath.vendor_id); + strcpy(all_paths[k].product_id, curpath.product_id); + all_paths[k].iopolicy = curpath.iopolicy; + all_paths[k].tur = curpath.tur; + + /* done with curpath, zero for reuse */ + memset(&curpath, 0, sizeof(path)); + + basename(linkp->target, buff); + sscanf(buff, "%i:%i:%i:%i", + &all_paths[k].sg_id.host_no, + &all_paths[k].sg_id.channel, + &all_paths[k].sg_id.scsi_id, + &all_paths[k].sg_id.lun); + k++; + } + sysfs_close_directory(sdir); + return 0; +} + +static int +get_all_paths_nosysfs(struct env * conf, struct path * all_paths, + struct scsi_dev * all_scsi_ids) +{ + int k, i, fd; + char buff[FILE_NAME_SIZE]; + char file_name[FILE_NAME_SIZE]; + + for (k = 0; k < conf->max_devs; k++) { + strcpy(file_name, "/dev/sg"); + sprintf(buff, "%d", k); + strncat(file_name, buff, FILE_NAME_SIZE); + strcpy(all_paths[k].sg_dev, file_name); + + get_lun_strings(all_paths[k].vendor_id, + all_paths[k].product_id, + all_paths[k].rev, + all_paths[k].sg_dev); + get_serial(all_paths[k].serial, all_paths[k].sg_dev); + if (!get_unique_id(&all_paths[k])) + continue; + + if ((fd = open(all_paths[k].sg_dev, O_RDONLY)) < 0) + return 0; + + if (0 > ioctl(fd, SG_GET_SCSI_ID, &(all_paths[k].sg_id))) + printf("device %s failed on sg ioctl, skip\n", + file_name); + + close(fd); + + for (i = 0; i < conf->max_devs; i++) { + if ((all_paths[k].sg_id.host_no == + all_scsi_ids[i].host_no) + && (all_paths[k].sg_id.scsi_id == + (all_scsi_ids[i].scsi_id.dev_id & 0xff)) + && (all_paths[k].sg_id.lun == + ((all_scsi_ids[i].scsi_id.dev_id >> 8) & 0xff)) + && (all_paths[k].sg_id.channel == + ((all_scsi_ids[i].scsi_id. + dev_id >> 16) & 0xff))) { + strcpy(all_paths[k].dev, all_scsi_ids[i].dev); + break; + } + } + } + return 0; +} + +static int +get_all_scsi_ids(struct env * conf, struct scsi_dev * all_scsi_ids) +{ + int k, big, little, res, host_no, fd; + char buff[64]; + char fname[FILE_NAME_SIZE]; + struct scsi_idlun my_scsi_id; + + for (k = 0; k < conf->max_devs; k++) { + strcpy(fname, "/dev/sd"); + if (k < 26) { + buff[0] = 'a' + (char) k; + buff[1] = '\0'; + strcat(fname, buff); + } else if (k <= 255) { + /* assumes sequence goes x,y,z,aa,ab,ac etc */ + big = k / 26; + little = k - (26 * big); + big = big - 1; + + buff[0] = 'a' + (char) big; + buff[1] = 'a' + (char) little; + buff[2] = '\0'; + strcat(fname, buff); + } else + strcat(fname, "xxxx"); + + if ((fd = open(fname, O_RDONLY)) < 0) { + if (conf->verbose) + fprintf(stderr, "can't open %s. mknod ?", + fname); + continue; + } + + res = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &my_scsi_id); + if (res < 0) { + close(fd); + printf("Could not get scsi idlun\n"); + continue; + } + + res = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no); + if (res < 0) { + close(fd); + printf("Could not get host_no\n"); + continue; + } + + close(fd); + + strcpy(all_scsi_ids[k].dev, fname); + all_scsi_ids[k].scsi_id = my_scsi_id; + all_scsi_ids[k].host_no = host_no; + } + return 0; +} + +/* print_path style */ +#define ALL 0 +#define NOWWID 1 + +static void +print_path(struct path * all_paths, int k, int style) +{ + if (style != NOWWID) + printf("%s ", all_paths[k].wwid); + else + printf(" \\_"); + printf("(%i %i %i %i) ", + all_paths[k].sg_id.host_no, + all_paths[k].sg_id.channel, + all_paths[k].sg_id.scsi_id, all_paths[k].sg_id.lun); + if(0 != strcmp(all_paths[k].sg_dev, all_paths[k].dev)) + printf("%s ", all_paths[k].sg_dev); + printf("%s ", all_paths[k].dev); + printf("[%.16s]\n", all_paths[k].product_id); +} + +static void +print_all_path(struct env * conf, struct path * all_paths) +{ + int k; + char empty_buff[WWID_SIZE]; + + memset(empty_buff, 0, WWID_SIZE); + for (k = 0; k < conf->max_devs; k++) { + if (memcmp(empty_buff, all_paths[k].wwid, WWID_SIZE) == 0) + continue; + print_path(all_paths, k, ALL); + } +} + +static void +print_all_mp(struct path * all_paths, struct multipath * mp, int nmp) +{ + int k, i; + + for (k = 0; k <= nmp; k++) { + printf("%s\n", mp[k].wwid); + for (i = 0; i <= mp[k].npaths; i++) + print_path(all_paths, PINDEX(k,i), NOWWID); + } +} + +static int +coalesce_paths(struct env * conf, struct multipath * mp, + struct path * all_paths) +{ + int k, i, nmp, np, already_done; + char empty_buff[WWID_SIZE]; + + nmp = -1; + already_done = 0; + memset(empty_buff, 0, WWID_SIZE); + + for (k = 0; k < conf->max_devs - 1; k++) { + /* skip this path for some reason */ + + /* 1. if path has no unique id */ + if (memcmp(empty_buff, all_paths[k].wwid, WWID_SIZE) == 0) + continue; + + /* 2. if mp with this uid already instanciated */ + for (i = 0; i <= nmp; i++) { + if (0 == strcmp(mp[i].wwid, all_paths[k].wwid)) + already_done = 1; + } + if (already_done) { + already_done = 0; + continue; + } + + /* at this point, we know we really got a new mp */ + np = 0; + nmp++; + strcpy(mp[nmp].wwid, all_paths[k].wwid); + PINDEX(nmp,np) = k; + + if (mp[nmp].size == 0) + mp[nmp].size = get_disk_size(all_paths[k].dev); + + for (i = k + 1; i < conf->max_devs; i++) { + if (0 == strcmp(all_paths[k].wwid, all_paths[i].wwid)) { + np++; + PINDEX(nmp,np) = i; + mp[nmp].npaths = np; + } + } + } + return nmp; +} + +static void +group_by_tur(struct multipath * mp, struct path * all_paths, char * str) { + int left_path_count = 0; + int right_path_count = 0; + int i; + char left_path_buff[FILE_NAME_SIZE], right_path_buff[FILE_NAME_SIZE]; + char * left_path_buff_p = &left_path_buff[0]; + char * right_path_buff_p = &right_path_buff[0]; + + for (i = 0; i <= mp->npaths; i++) { + if (all_paths[mp->pindex[i]].tur) { + left_path_buff_p += sprintf(left_path_buff_p, " %s", all_paths[mp->pindex[i]].dev); + left_path_count++; + } else { + right_path_buff_p += sprintf(right_path_buff_p, " %s", all_paths[mp->pindex[i]].dev); + right_path_count++; + } + } + if (!left_path_count) + sprintf(str, " 1 round-robin %i 0 %s", right_path_count, right_path_buff); + else if (!right_path_count) + sprintf(str, " 1 round-robin %i 0 %s", left_path_count, left_path_buff); + else + sprintf(str, " 2 round-robin %i 0 %s round-robin %i 0 %s", + left_path_count, left_path_buff, + right_path_count, right_path_buff); +} + +static void +group_by_serial(struct multipath * mp, struct path * all_paths, char * str) { + int path_count, pg_count = 0; + int i, k; + int * bitmap; + char path_buff[FILE_NAME_SIZE]; + char pg_buff[FILE_NAME_SIZE]; + char * path_buff_p = &path_buff[0]; + char * pg_buff_p = &pg_buff[0]; + + /* init the bitmap */ + bitmap = malloc((mp->npaths + 1) * sizeof(int)); + memset(bitmap, 0, (mp->npaths + 1) * sizeof(int)); + + for (i = 0; i <= mp->npaths; i++) { + if (bitmap[i]) + continue; + + /* here, we really got a new pg */ + pg_count++; + path_count = 1; + memset(&path_buff, 0, FILE_NAME_SIZE * sizeof(char)); + path_buff_p = &path_buff[0]; + + path_buff_p += sprintf(path_buff_p, " %s", all_paths[mp->pindex[i]].dev); + bitmap[i] = 1; + + for (k = i + 1; k <= mp->npaths; k++) { + if (bitmap[k]) + continue; + if (0 == strcmp(all_paths[mp->pindex[i]].serial, + all_paths[mp->pindex[k]].serial)) { + path_buff_p += sprintf(path_buff_p, " %s", all_paths[mp->pindex[k]].dev); + bitmap[k] = 1; + path_count++; + } + } + pg_buff_p += sprintf(pg_buff_p, " round-robin %i 0%s", + path_count, path_buff); + } + sprintf(str, " %i%s", pg_count, pg_buff); + free(bitmap); +} + +static int +dm_simplecmd(int task, const char *name) { + int r = 0; + struct dm_task *dmt; + + if (!(dmt = dm_task_create(task))) + return 0; + + if (!dm_task_set_name(dmt, name)) + goto out; + + r = dm_task_run(dmt); + + out: + dm_task_destroy(dmt); + return r; +} + +static int +dm_addmap(int task, const char *name, const char *params, long size) { + struct dm_task *dmt; + + if (!(dmt = dm_task_create(task))) + return 0; + + if (!dm_task_set_name(dmt, name)) + goto addout; + + if (!dm_task_add_target(dmt, 0, size, DM_TARGET, params)) + goto addout; + + if (!dm_task_run(dmt)) + goto addout; + + addout: + dm_task_destroy(dmt); + return 1; +} + +static int +setup_map(struct env * conf, struct path * all_paths, + struct multipath * mp, int index, int op) +{ + char params[255]; + char * params_p; + int i, np; + + /* defaults for multipath target */ + char * dm_ps_name = "round-robin"; + int dm_ps_nr_args = 0; + + params_p = ¶ms[0]; + + np = 0; + for (i=0; i<=mp[index].npaths; i++) { + if (0 == all_paths[PINDEX(index,i)].sg_id.scsi_type) + np++; + } + + if (np < 1) + return 0; + + if ((all_paths[PINDEX(index,0)].iopolicy == MULTIBUS && + conf->iopolicy == -1) || conf->iopolicy == MULTIBUS) { + params_p += sprintf(params_p, " 1 %s %i %i", + dm_ps_name, np, dm_ps_nr_args); + + for (i=0; i<=mp[index].npaths; i++) { + if (0 != all_paths[PINDEX(index,i)].sg_id.scsi_type) + continue; + params_p += sprintf(params_p, " %s", + all_paths[PINDEX(index,i)].dev); + } + } + + if ((all_paths[PINDEX(index,0)].iopolicy == FAILOVER && + conf->iopolicy == -1) || conf->iopolicy == FAILOVER) { + params_p += sprintf(params_p, " %i", mp[index].npaths + 1); + for (i=0; i<=mp[index].npaths; i++) { + if (0 != all_paths[PINDEX(index,i)].sg_id.scsi_type) + continue; + params_p += sprintf(params_p, " %s ", + dm_ps_name); + params_p += sprintf(params_p, "1 %i", + dm_ps_nr_args); + params_p += sprintf(params_p, " %s", + all_paths[PINDEX(index,i)].dev); + } + } + + if ((all_paths[PINDEX(index,0)].iopolicy == GROUP_BY_SERIAL && + conf->iopolicy == -1) || conf->iopolicy == GROUP_BY_SERIAL) { + group_by_serial(&mp[index], all_paths, params_p); + } + + if ((all_paths[PINDEX(index,0)].iopolicy == GROUP_BY_TUR && + conf->iopolicy == -1) || conf->iopolicy == GROUP_BY_TUR) { + group_by_tur(&mp[index], all_paths, params_p); + } + + if (mp[index].size < 0) + return 0; + + if (!conf->quiet) { + if (op == DM_DEVICE_RELOAD) + printf("U:"); + if (op == DM_DEVICE_CREATE) + printf("N:"); + printf("%s:0 %li %s %s\n", + mp[index].wwid, mp[index].size, DM_TARGET, params); + } + + if (op == DM_DEVICE_RELOAD) + dm_simplecmd(DM_DEVICE_SUSPEND, mp[index].wwid); + + dm_addmap(op, mp[index].wwid, params, mp[index].size); + + if (op == DM_DEVICE_RELOAD) + dm_simplecmd(DM_DEVICE_RESUME, mp[index].wwid); + + return 1; +} + +static int +map_present(char * str) +{ + int r = 0; + struct dm_task *dmt; + struct dm_names *names; + unsigned next = 0; + + if (!(dmt = dm_task_create(DM_DEVICE_LIST))) + return 0; + + if (!dm_task_run(dmt)) + goto out; + + if (!(names = dm_task_get_names(dmt))) + goto out; + + if (!names->dev) { + goto out; + } + + do { + if (0 == strcmp(names->name, str)) + r = 1; + next = names->next; + names = (void *) names + next; + } while (next); + + out: + dm_task_destroy(dmt); + return r; +} + +static void +signal_daemon (void) +{ + FILE *file; + pid_t pid; + char *buf; + + buf = malloc (8); + + file = fopen (PIDFILE, "r"); + + if (!file) { + fprintf(stderr, "cannot signal daemon, pidfile not found\n"); + return; + } + + buf = fgets (buf, 8, file); + fclose (file); + + pid = (pid_t) atol (buf); + free (buf); + + kill(pid, SIGHUP); +} + +static int +filepresent(char * run) { + struct stat buf; + + if(!stat(run, &buf)) + return 1; + return 0; +} + +static void +usage(char * progname) +{ + fprintf(stderr, VERSION_STRING); + fprintf(stderr, "Usage: %s [-v|-q] [-d] [-m max_devs]\n", + progname); + fprintf(stderr, " [-p failover|multibus|group_by_serial]\n" \ + " [device]\n" \ + "\n" \ + "\t-v\t\tverbose, print all paths and multipaths\n" \ + "\t-q\t\tquiet, no output at all\n" \ + "\t-d\t\tdry run, do not create or update devmaps\n" \ + "\t-m max_devs\tscan {max_devs} devices at most\n" \ + "\n" \ + "\t-p policy\tforce maps to specified policy :\n" \ + "\t failover\t\t- 1 path per priority group\n" \ + "\t multibus\t\t- all paths in 1 priority group\n" \ + "\t group_by_serial\t- 1 priority group per serial\n" \ + "\t group_by_tur\t\t- 1 priority group per TUR state\n" \ + "\n" \ + "\t-D maj min\tlimit scope to the device's multipath\n" \ + "\t\t\t(major minor device reference)\n" + "\tdevice\t\tlimit scope to the device's multipath\n" \ + "\t\t\t(hotplug-style $DEVPATH reference)\n" + ); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct multipath * mp; + struct path * all_paths; + struct scsi_dev * all_scsi_ids; + struct env conf; + int i, k, nmp; + int try = 0; + + /* Don't run in parallel */ + while (filepresent(RUN) && try++ < MAXTRY) + usleep(100000); + + if (filepresent(RUN)) { + fprintf(stderr, "waited for to long. exiting\n"); + exit (1); + } + + /* Our turn */ + if (!open(RUN, O_CREAT)) { + fprintf(stderr, "can't create runfile\n"); + exit (1); + } + + /* Default behaviour */ + conf.max_devs = MAX_DEVS; + conf.dry_run = 0; /* 1 == Do not Create/Update devmaps */ + conf.verbose = 0; /* 1 == Print all_paths and mp */ + conf.quiet = 0; /* 1 == Do not even print devmaps */ + conf.iopolicy = -1; /* Apply the defaults in get_unique_id() */ + conf.major = -1; + conf.minor = -1; + + for (i = 1; i < argc; ++i) { + if (0 == strcmp("-v", argv[i])) { + if (conf.quiet == 1) + usage(argv[0]); + conf.verbose = 1; + } else if (0 == strcmp("-m", argv[i])) { + conf.max_devs = atoi(argv[++i]); + if (conf.max_devs < 2) + usage(argv[0]); + } else if (0 == strcmp("-D", argv[i])) { + conf.major = atoi(argv[++i]); + conf.minor = atoi(argv[++i]); + } else if (0 == strcmp("-q", argv[i])) { + if (conf.verbose == 1) + usage(argv[0]); + conf.quiet = 1; + } else if (0 == strcmp("-d", argv[i])) + conf.dry_run = 1; + else if (0 == strcmp("-p", argv[i])) { + i++; + if (!strcmp(argv[i], "failover")) + conf.iopolicy = FAILOVER; + if (!strcmp(argv[i], "multibus")) + conf.iopolicy = MULTIBUS; + if (!strcmp(argv[i], "group_by_serial")) + conf.iopolicy = GROUP_BY_SERIAL; + if (!strcmp(argv[i], "group_by_tur")) + conf.iopolicy = GROUP_BY_TUR; + } else if (*argv[i] == '-') { + fprintf(stderr, "Unknown switch: %s\n", argv[i]); + usage(argv[0]); + } else + strncpy(conf.hotplugdev, argv[i], FILE_NAME_SIZE); + } + + /* dynamic allocations */ + mp = malloc(conf.max_devs * sizeof(struct multipath)); + all_paths = malloc(conf.max_devs * sizeof(struct path)); + all_scsi_ids = malloc(conf.max_devs * sizeof(struct scsi_dev)); + if (mp == NULL || all_paths == NULL || all_scsi_ids == NULL) + exit(1); + + if (sysfs_get_mnt_path(conf.sysfs_path, FILE_NAME_SIZE)) { + get_all_scsi_ids(&conf, all_scsi_ids); + get_all_paths_nosysfs(&conf, all_paths, all_scsi_ids); + } else { + get_all_paths_sysfs(&conf, all_paths); + } + + nmp = coalesce_paths(&conf, mp, all_paths); + + if (conf.verbose) { + print_all_path(&conf, all_paths); + fprintf(stdout, "\n"); + print_all_mp(all_paths, mp, nmp); + fprintf(stdout, "\n"); + } + + if (conf.dry_run) + exit(0); + + for (k=0; k<=nmp; k++) { + if (map_present(mp[k].wwid)) { + setup_map(&conf, all_paths, mp, k, DM_DEVICE_RELOAD); + } else { + setup_map(&conf, all_paths, mp, k, DM_DEVICE_CREATE); + } + } + + /* signal multipathd that new devmaps may have come up */ + signal_daemon(); + + /* free allocs */ + free(mp); + free(all_paths); + free(all_scsi_ids); + + /* release runfile */ + unlink(RUN); + + exit(0); +} diff --git a/extras/multipath-tools/multipath/main.h b/extras/multipath-tools/multipath/main.h new file mode 100644 index 00000000..ada7a739 --- /dev/null +++ b/extras/multipath-tools/multipath/main.h @@ -0,0 +1,131 @@ +/* + * Soft: Description here... + * + * Version: $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $ + * + * Author: Copyright (C) 2003 Christophe Varoqui + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _MAIN_H +#define _MAIN_H + +/* local includes */ +#include "sg_include.h" + +/* exerpt from "sg_err.h" */ +#define SCSI_CHECK_CONDITION 0x2 +#define SCSI_COMMAND_TERMINATED 0x22 +#define SG_ERR_DRIVER_SENSE 0x08 + +/* exerpt from "scsi.h" */ +#define SCSI_IOCTL_GET_IDLUN 0x5382 +#define SCSI_IOCTL_GET_BUS_NUMBER 0x5386 + +/* global defs */ +#define WWID_SIZE 33 +#define SERIAL_SIZE 14 +#define MAX_DEVS 128 +#define MAX_MP MAX_DEVS / 2 +#define MAX_MP_PATHS MAX_DEVS / 4 +#define FILE_NAME_SIZE 256 +#define DEF_TIMEOUT 60000 +#define EBUFF_SZ 256 +#define TUR_CMD_LEN 6 +#define DM_TARGET "multipath" +#define PIDFILE "/var/run/multipathd.pid" +#define RUN "/var/run/multipath.run" +#define MAXTRY 50 + +/* Storage controlers cpabilities */ +#define FAILOVER 0 +#define MULTIBUS 1 +#define GROUP_BY_SERIAL 2 +#define GROUP_BY_TUR 4 + +#define PINDEX(x,y) mp[(x)].pindex[(y)] + +/* global types */ +struct scsi_idlun { + int dev_id; + int host_unique_id; + int host_no; +}; + +struct sg_id { + int host_no; + int channel; + int scsi_id; + int lun; + int scsi_type; + short h_cmd_per_lun; + short d_queue_depth; + int unused1; + int unused2; +}; + +struct scsi_dev { + char dev[FILE_NAME_SIZE]; + struct scsi_idlun scsi_id; + int host_no; +}; + +struct path { + char dev[FILE_NAME_SIZE]; + char sg_dev[FILE_NAME_SIZE]; + struct scsi_idlun scsi_id; + struct sg_id sg_id; + char wwid[WWID_SIZE]; + char vendor_id[8]; + char product_id[16]; + char rev[4]; + char serial[SERIAL_SIZE]; + int iopolicy; + int tur; +}; + +struct multipath { + char wwid[WWID_SIZE]; + int npaths; + long size; + int pindex[MAX_MP_PATHS]; +}; + +struct env { + int max_devs; + int verbose; + int quiet; + int dry_run; + int iopolicy; + int with_sysfs; + int major; + int minor; + char sysfs_path[FILE_NAME_SIZE]; + char hotplugdev[FILE_NAME_SIZE]; +}; + +/* Build version */ +#define PROG "multipath" + +#define VERSION_CODE 0x000012 +#define DATE_CODE 0x021504 + +#define MULTIPATH_VERSION(version) \ + (version >> 16) & 0xFF, \ + (version >> 8) & 0xFF, \ + version & 0xFF + +#define VERSION_STRING PROG" v%d.%d.%d (%.2d/%.2d, 20%.2d)\n", \ + MULTIPATH_VERSION(VERSION_CODE), \ + MULTIPATH_VERSION(DATE_CODE) + +#endif diff --git a/extras/multipath-tools/multipath/multipath.8 b/extras/multipath-tools/multipath/multipath.8 new file mode 100644 index 00000000..0856ca8d --- /dev/null +++ b/extras/multipath-tools/multipath/multipath.8 @@ -0,0 +1,66 @@ +.TH MULTIPATH 8 "February 2004" "" "Linux Administrator's Manual" +.SH NAME +multipath \- Device mapper target autoconfig +.SH SYNOPSIS +.B multipath +.RB [\| \-v | \-q \|] +.RB [\| \-d \|] +.RB [\| \-i\ \c +.IR int \|] +.RB [\| \-m\ \c +.IR max_devs \|] +.RB [\| \-p\ \c +.BR failover | multibus | group_by_serial \|] +.RB [\| device \|] +.SH DESCRIPTION +.B multipath +is used to detect multiple paths to devices for fail-over or performance reasons and coalesces them. +.SH OPTIONS +.TP +.B \-v +verbose, print all paths and multipaths +.TP +.B \-q +quiet, no output at all +.TP +.B \-d +dry run, do not create or update devmaps +.TP +.BI \-i " int" +multipath target param: polling interval +.TP +.BI \-m " max_devs" +scan +.I max_devs +devices at most +.TP +.BI \-D " major minor" +update only the devmap the path pointed by +.I major minor +is in +.TP +.BI \-p " policy" +force maps to specified policy: +.RS 1.2i +.TP 1.2i +.B failover +1 path per priority group +.TP +.B multibus +all paths in 1 priority group +.TP +.B group_by_serial +1 priority group per serial +.RE +.TP +.BI device +update only the devmap the path pointed by +.I device +is in +.SH "SEE ALSO" +.BR udev (8), +.BR dmsetup (8) +.BR hotplug (8) +.SH AUTHORS +.B multipath +was developed by Christophe Varoqui, and others. diff --git a/extras/multipath-tools/multipath/multipath.hotplug b/extras/multipath-tools/multipath/multipath.hotplug new file mode 100644 index 00000000..5c411d1e --- /dev/null +++ b/extras/multipath-tools/multipath/multipath.hotplug @@ -0,0 +1,7 @@ +#!/bin/sh +. /etc/hotplug/hotplug.functions + +# wait for sysfs +sleep 1 + +mesg `/sbin/multipath $DEVPATH` diff --git a/extras/multipath-tools/multipath/sg_err.h b/extras/multipath-tools/multipath/sg_err.h new file mode 100644 index 00000000..ef57b5ce --- /dev/null +++ b/extras/multipath-tools/multipath/sg_err.h @@ -0,0 +1,162 @@ +#ifndef SG_ERR_H +#define SG_ERR_H + +/* Feel free to copy and modify this GPL-ed code into your applications. */ + +/* Version 0.90 (20030519) +*/ + + +/* Some of the following error/status codes are exchanged between the + various layers of the SCSI sub-system in Linux and should never + reach the user. They are placed here for completeness. What appears + here is copied from drivers/scsi/scsi.h which is not visible in + the user space. */ + +#ifndef SCSI_CHECK_CONDITION +/* Following are the "true" SCSI status codes. Linux has traditionally + used a 1 bit right and masked version of these. So now CHECK_CONDITION + and friends (in ) are deprecated. */ +#define SCSI_CHECK_CONDITION 0x2 +#define SCSI_CONDITION_MET 0x4 +#define SCSI_BUSY 0x8 +#define SCSI_IMMEDIATE 0x10 +#define SCSI_IMMEDIATE_CONDITION_MET 0x14 +#define SCSI_RESERVATION_CONFLICT 0x18 +#define SCSI_COMMAND_TERMINATED 0x22 +#define SCSI_TASK_SET_FULL 0x28 +#define SCSI_ACA_ACTIVE 0x30 +#define SCSI_TASK_ABORTED 0x40 +#endif + +/* The following are 'host_status' codes */ +#ifndef DID_OK +#define DID_OK 0x00 +#endif +#ifndef DID_NO_CONNECT +#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */ +#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */ +#define DID_TIME_OUT 0x03 /* Timed out for some other reason */ +#define DID_BAD_TARGET 0x04 /* Bad target (id?) */ +#define DID_ABORT 0x05 /* Told to abort for some other reason */ +#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */ +#define DID_ERROR 0x07 /* Internal error */ +#define DID_RESET 0x08 /* Reset by somebody */ +#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */ +#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */ +#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */ +#endif + +/* These defines are to isolate applictaions from kernel define changes */ +#define SG_ERR_DID_OK DID_OK +#define SG_ERR_DID_NO_CONNECT DID_NO_CONNECT +#define SG_ERR_DID_BUS_BUSY DID_BUS_BUSY +#define SG_ERR_DID_TIME_OUT DID_TIME_OUT +#define SG_ERR_DID_BAD_TARGET DID_BAD_TARGET +#define SG_ERR_DID_ABORT DID_ABORT +#define SG_ERR_DID_PARITY DID_PARITY +#define SG_ERR_DID_ERROR DID_ERROR +#define SG_ERR_DID_RESET DID_RESET +#define SG_ERR_DID_BAD_INTR DID_BAD_INTR +#define SG_ERR_DID_PASSTHROUGH DID_PASSTHROUGH +#define SG_ERR_DID_SOFT_ERROR DID_SOFT_ERROR + +/* The following are 'driver_status' codes */ +#ifndef DRIVER_OK +#define DRIVER_OK 0x00 +#endif +#ifndef DRIVER_BUSY +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */ + +/* Following "suggests" are "or-ed" with one of previous 8 entries */ +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff +#endif +#ifndef DRIVER_MASK +#define DRIVER_MASK 0x0f +#endif +#ifndef SUGGEST_MASK +#define SUGGEST_MASK 0xf0 +#endif + +/* These defines are to isolate applictaions from kernel define changes */ +#define SG_ERR_DRIVER_OK DRIVER_OK +#define SG_ERR_DRIVER_BUSY DRIVER_BUSY +#define SG_ERR_DRIVER_SOFT DRIVER_SOFT +#define SG_ERR_DRIVER_MEDIA DRIVER_MEDIA +#define SG_ERR_DRIVER_ERROR DRIVER_ERROR +#define SG_ERR_DRIVER_INVALID DRIVER_INVALID +#define SG_ERR_DRIVER_TIMEOUT DRIVER_TIMEOUT +#define SG_ERR_DRIVER_HARD DRIVER_HARD +#define SG_ERR_DRIVER_SENSE DRIVER_SENSE +#define SG_ERR_SUGGEST_RETRY SUGGEST_RETRY +#define SG_ERR_SUGGEST_ABORT SUGGEST_ABORT +#define SG_ERR_SUGGEST_REMAP SUGGEST_REMAP +#define SG_ERR_SUGGEST_DIE SUGGEST_DIE +#define SG_ERR_SUGGEST_SENSE SUGGEST_SENSE +#define SG_ERR_SUGGEST_IS_OK SUGGEST_IS_OK +#define SG_ERR_DRIVER_MASK DRIVER_MASK +#define SG_ERR_SUGGEST_MASK SUGGEST_MASK + + + +/* The following "print" functions send ACSII to stdout */ +extern void sg_print_command(const unsigned char * command); +extern void sg_print_sense(const char * leadin, + const unsigned char * sense_buffer, int sb_len); +extern void sg_print_status(int masked_status); +extern void sg_print_scsi_status(int scsi_status); +extern void sg_print_host_status(int host_status); +extern void sg_print_driver_status(int driver_status); + +/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings + else it prints to standard output and returns 0. */ +extern int sg_chk_n_print(const char * leadin, int masked_status, + int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len); + +/* The following function declaration is for the sg version 3 driver. + Only version 3 sg_err.c defines it. */ +struct sg_io_hdr; +extern int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp); + + +/* The following "category" function returns one of the following */ +#define SG_ERR_CAT_CLEAN 0 /* No errors or other information */ +#define SG_ERR_CAT_MEDIA_CHANGED 1 /* interpreted from sense buffer */ +#define SG_ERR_CAT_RESET 2 /* interpreted from sense buffer */ +#define SG_ERR_CAT_TIMEOUT 3 +#define SG_ERR_CAT_RECOVERED 4 /* Successful command after recovered err */ +#define SG_ERR_CAT_SENSE 98 /* Something else is in the sense buffer */ +#define SG_ERR_CAT_OTHER 99 /* Some other error/warning has occurred */ + +extern int sg_err_category(int masked_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len); + +extern int sg_err_category_new(int scsi_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len); + +/* The following function declaration is for the sg version 3 driver. + Only version 3 sg_err.c defines it. */ +extern int sg_err_category3(struct sg_io_hdr * hp); + +/* Returns length of SCSI command given the opcode (first byte) */ +extern int sg_get_command_size(unsigned char opcode); + +extern void sg_get_command_name(unsigned char opcode, int buff_len, + char * buff); + +#endif diff --git a/extras/multipath-tools/multipath/sg_include.h b/extras/multipath-tools/multipath/sg_include.h new file mode 100644 index 00000000..46050682 --- /dev/null +++ b/extras/multipath-tools/multipath/sg_include.h @@ -0,0 +1,43 @@ +#ifdef SG_KERNEL_INCLUDES + #define __user + typedef unsigned char u8; + #include "/usr/src/linux/include/scsi/sg.h" + #include "/usr/src/linux/include/scsi/scsi.h" +#else + #ifdef SG_TRICK_GNU_INCLUDES + #include + #include + #else + #include + #endif +#endif + +/* + Getting the correct include files for the sg interface can be an ordeal. + In a perfect world, one would just write: + #include + #include + This would include the files found in the /usr/include/scsi directory. + Those files are maintained with the GNU library which may or may not + agree with the kernel and version of sg driver that is running. Any + many cases this will not matter. However in some it might, for example + glibc 2.1's include files match the sg driver found in the lk 2.2 + series. Hence if glibc 2.1 is used with lk 2.4 then the additional + sg v3 interface will not be visible. + If this is a problem then defining SG_KERNEL_INCLUDES will access the + kernel supplied header files (assuming they are in the normal place). + The GNU library maintainers and various kernel people don't like + this approach (but it does work). + The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and + was used) prior to glibc 2.2 . Prior to that version /usr/include/linux + was a symbolic link to /usr/src/linux/include/linux . + + There are other approaches if this include "mixup" causes pain. These + would involve include files being copied or symbolic links being + introduced. + + Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES + nor SG_TRICK_GNU_INCLUDES is defined. + + dpg 20010415, 20030522 +*/ diff --git a/extras/multipath-tools/multipath/unused.c b/extras/multipath-tools/multipath/unused.c new file mode 100644 index 00000000..08144cf7 --- /dev/null +++ b/extras/multipath-tools/multipath/unused.c @@ -0,0 +1,95 @@ +static int +get_serial (int fd, char * str) +{ + char buff[MX_ALLOC_LEN + 1]; + int len; + + if (0 == do_inq(fd, 0, 1, 0x80, buff, MX_ALLOC_LEN, 0)) { + len = buff[3]; + if (len > 0) { + memcpy(str, buff + 4, len); + buff[len] = '\0'; + } + return 1; + } + return 0; +} + +static int +do_tur(int fd) +{ + unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; + struct sg_io_hdr io_hdr; + unsigned char sense_buffer[32]; + + memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof (turCmdBlk); + io_hdr.mx_sb_len = sizeof (sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = turCmdBlk; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; + io_hdr.pack_id = 0; + if (ioctl(fd, SG_IO, &io_hdr) < 0) { + close(fd); + return 0; + } + if (io_hdr.info & SG_INFO_OK_MASK) { + return 0; + } + return 1; +} + +static int +del_map(char * str) { + struct dm_task *dmt; + + if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) + return 0; + if (!dm_task_set_name(dmt, str)) + goto delout; + if (!dm_task_run(dmt)) + goto delout; + + printf("Deleted device map : %s\n", str); + + delout: + dm_task_destroy(dmt); + return 1; +} + +get_table(const char * str) +{ + int r = 0; + struct dm_task *dmt; + void *next = NULL; + uint64_t start, length; + char *target_type = NULL; + char *params; + + if (!(dmt = dm_task_create(DM_DEVICE_TABLE))) + return 0; + + if (!dm_task_set_name(dmt, str)) + goto out; + + if (!dm_task_run(dmt)) + goto out; + + do { + next = dm_get_next_target(dmt, next, &start, &length, + &target_type, ¶ms); + if (target_type) { + printf("%" PRIu64 " %" PRIu64 " %s %s\n", + start, length, target_type, params); + } + } while (next); + + r = 1; + + out: + dm_task_destroy(dmt); + return r; + +} diff --git a/extras/multipath-tools/multipathd/Makefile b/extras/multipath-tools/multipathd/Makefile new file mode 100644 index 00000000..5bfd58a8 --- /dev/null +++ b/extras/multipath-tools/multipathd/Makefile @@ -0,0 +1,31 @@ +EXEC = multipathd + +CC = gcc +GZIP = /bin/gzip -9 -c + +bindir = /usr/bin +mandir = /usr/share/man/man8 +rcdir = /etc/init.d + +CFLAGS = -pipe -g -O2 -Wall -Wunused -Wstrict-prototypes -DDEBUG=1 +LDFLAGS = -lpthread -ldevmapper -lsysfs + +OBJS = main.o devinfo.o checkers.o + +$(EXEC): $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) -o $(EXEC) + strip $(EXEC) + +install: + install -d $(bindir) + install -m 755 $(EXEC) $(bindir) + install -d $(rcdir) + install -m 755 multipathd.init $(rcdir)/$(EXEC) + +uninstall: + rm -f $(bindir)/$(EXEC) + rm -f $(rcdir)/$(EXEC) + +clean: + rm -f core *.o $(EXEC) *.gz + diff --git a/extras/multipath-tools/multipathd/checkers.c b/extras/multipath-tools/multipathd/checkers.c new file mode 100644 index 00000000..7ad32b04 --- /dev/null +++ b/extras/multipath-tools/multipathd/checkers.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "sg_include.h" + +#define TUR_CMD_LEN 6 + +/* + * test IO functions : add yours here + * + * returns 0 : path gone valid + * returns 1 : path still failed + */ + +int readsector0 (char *devnode) +{ + int fd, r; + char buf; + + fd = open (devnode, O_RDONLY); + if (read (fd, &buf, 1) != 1) + r = 0; + else + r = 1; + + close (fd); + + return r; +} + +int tur(char *devnode) +{ + unsigned char turCmdBlk[TUR_CMD_LEN] = { 0x00, 0, 0, 0, 0, 0 }; + struct sg_io_hdr io_hdr; + unsigned char sense_buffer[32]; + int fd; + + fd = open (devnode, O_RDONLY); + + memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof (turCmdBlk); + io_hdr.mx_sb_len = sizeof (sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = turCmdBlk; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; + io_hdr.pack_id = 0; + if (ioctl(fd, SG_IO, &io_hdr) < 0) { + close(fd); + return 0; + } + if (io_hdr.info & SG_INFO_OK_MASK) { + return 0; + } + return 1; +} diff --git a/extras/multipath-tools/multipathd/checkers.h b/extras/multipath-tools/multipathd/checkers.h new file mode 100644 index 00000000..057c319d --- /dev/null +++ b/extras/multipath-tools/multipathd/checkers.h @@ -0,0 +1,2 @@ +int readsector0 (char *); +int tur (char *); diff --git a/extras/multipath-tools/multipathd/devinfo.c b/extras/multipath-tools/multipathd/devinfo.c new file mode 100644 index 00000000..46505470 --- /dev/null +++ b/extras/multipath-tools/multipathd/devinfo.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include "devinfo.h" +#include "sg_include.h" + +#define FILE_NAME_SIZE 255 + +static int +do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, + void *resp, int mx_resp_len, int noisy) +{ + unsigned char inqCmdBlk[INQUIRY_CMDLEN] = + { INQUIRY_CMD, 0, 0, 0, 0, 0 }; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + + if (cmddt) + inqCmdBlk[1] |= 2; + if (evpd) + inqCmdBlk[1] |= 1; + inqCmdBlk[2] = (unsigned char) pg_op; + inqCmdBlk[4] = (unsigned char) mx_resp_len; + memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof (inqCmdBlk); + io_hdr.mx_sb_len = sizeof (sense_b); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = mx_resp_len; + io_hdr.dxferp = resp; + io_hdr.cmdp = inqCmdBlk; + io_hdr.sbp = sense_b; + io_hdr.timeout = DEF_TIMEOUT; + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("SG_IO (inquiry) error"); + return -1; + } + + /* treat SG_ERR here to get rid of sg_err.[ch] */ + io_hdr.status &= 0x7e; + if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && + (0 == io_hdr.driver_status)) + return 0; + if ((SCSI_CHECK_CONDITION == io_hdr.status) || + (SCSI_COMMAND_TERMINATED == io_hdr.status) || + (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { + if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { + int sense_key; + unsigned char * sense_buffer = io_hdr.sbp; + if (sense_buffer[0] & 0x2) + sense_key = sense_buffer[1] & 0xf; + else + sense_key = sense_buffer[2] & 0xf; + if(RECOVERED_ERROR == sense_key) + return 0; + } + } + return -1; +} + +int +get_lun_strings(char * vendor_id, char * product_id, char * rev, char * devname) +{ + int fd; + char buff[36]; + + /* ioctl style */ + if ((fd = open(devname, O_RDONLY)) < 0) + return 1; + if (0 != do_inq(fd, 0, 0, 0, buff, 36, 1)) + return 1; + memcpy(vendor_id, &buff[8], 8); + memcpy(product_id, &buff[16], 16); + memcpy(rev, &buff[32], 4); + close(fd); + + return 0; +} diff --git a/extras/multipath-tools/multipathd/devinfo.h b/extras/multipath-tools/multipathd/devinfo.h new file mode 100644 index 00000000..46b28940 --- /dev/null +++ b/extras/multipath-tools/multipathd/devinfo.h @@ -0,0 +1,15 @@ +#define INQUIRY_CMDLEN 6 +#define INQUIRY_CMD 0x12 +#define SENSE_BUFF_LEN 32 +#define DEF_TIMEOUT 60000 +#define RECOVERED_ERROR 0x01 +#define MX_ALLOC_LEN 255 +#define WWID_SIZE 33 +#define BLKGETSIZE _IO(0x12,96) + +/* exerpt from "sg_err.h" */ +#define SCSI_CHECK_CONDITION 0x2 +#define SCSI_COMMAND_TERMINATED 0x22 +#define SG_ERR_DRIVER_SENSE 0x08 + +int get_lun_strings (char *, char *, char *, char *); diff --git a/extras/multipath-tools/multipathd/main.c b/extras/multipath-tools/multipathd/main.c new file mode 100644 index 00000000..7b5ccfca --- /dev/null +++ b/extras/multipath-tools/multipathd/main.c @@ -0,0 +1,674 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devinfo.h" +#include "checkers.h" + +#define CHECKINT 5 +#define MAXPATHS 2048 +#define FILENAMESIZE 256 +#define MAPNAMESIZE 64 +#define TARGETTYPESIZE 16 +#define PARAMSSIZE 2048 +#define MAXMAPS 512 + +#define MULTIPATH "/sbin/multipath" +#define PIDFILE "/var/run/multipathd.pid" + +#ifndef DEBUG +#define DEBUG 1 +#endif +#define LOG(x, y, z...) if (DEBUG >= x) syslog(x, y, ##z) + +struct path +{ + int major; + int minor; + char mapname[MAPNAMESIZE]; + int (*checkfn) (char *); +}; + +struct paths +{ + pthread_mutex_t *lock; + struct path *paths_h; +}; + +struct event_thread +{ + pthread_t *thread; + pthread_mutex_t *waiter_lock; + char mapname[MAPNAMESIZE]; +}; + +struct devmap +{ + char mapname[MAPNAMESIZE]; +}; + +/* global var */ +pthread_mutex_t *event_lock; +pthread_cond_t *event; + +int makenode (char *devnode, int major, int minor) +{ + dev_t dev; + + dev = makedev (major, minor); + unlink (devnode); + + return mknod(devnode, S_IFBLK | S_IRUSR | S_IWUSR, dev); +} + +int select_checkfn(struct path *path_p) +{ + char devnode[FILENAMESIZE]; + char vendor[8]; + char product[16]; + char rev[4]; + int i, r; + + /* default checkfn */ + path_p->checkfn = &readsector0; + + sprintf (devnode, "/tmp/.select.%i.%i", path_p->major, path_p->minor); + + r = makenode (devnode, path_p->major, path_p->minor); + + if (r < 0) { + LOG(2, "[select_checkfn] can not make node %s", devnode); + return r; + } + + r = get_lun_strings(vendor, product, rev, devnode); + + if (r) { + LOG(2, "[select_checkfn] can not get strings"); + return r; + } + + r = unlink (devnode); + + if (r < 0) { + LOG(2, "[select_checkfn] can not unlink %s", devnode); + return r; + } + + static struct { + char * vendor; + char * product; + int (*checkfn) (char *); + } wlist[] = { + {"COMPAQ ", "HSV110 (C)COMPAQ", &tur}, + {"COMPAQ ", "MSA1000 ", &tur}, + {"COMPAQ ", "MSA1000 VOLUME ", &tur}, + {"DEC ", "HSG80 ", &tur}, + {"HP ", "HSV100 ", &readsector0}, + {NULL, NULL, NULL}, + }; + + for (i = 0; wlist[i].vendor; i++) { + if (strncmp(vendor, wlist[i].vendor, 8) == 0 && + strncmp(product, wlist[i].product, 16) == 0) { + path_p->checkfn = wlist[i].checkfn; + } + } + + return 0; +} + +int get_devmaps (struct devmap *devmaps) +{ + struct devmap *devmaps_p; + struct dm_task *dmt, *dmt1; + struct dm_names *names = NULL; + unsigned next = 0; + void *nexttgt; + int r = 0; + long long start, length; + char *target_type = NULL; + char *params; + + memset (devmaps, 0, MAXMAPS * sizeof (struct devmap)); + + if (!(dmt = dm_task_create(DM_DEVICE_LIST))) { + r = 1; + goto out; + } + + if (!dm_task_run(dmt)) { + r = 1; + goto out; + } + + if (!(names = dm_task_get_names(dmt))) { + r = 1; + goto out; + } + + if (!names->dev) { + LOG (1, "[get_devmaps] no devmap found"); + goto out; + } + + devmaps_p = devmaps; + + do { + /* keep only multipath maps */ + + names = (void *) names + next; + nexttgt = NULL; + LOG (3, "[get_devmaps] iterate on devmap names : %s", names->name); + + LOG (3, "[get_devmaps] dm_task_create(DM_DEVICE_STATUS)"); + if (!(dmt1 = dm_task_create(DM_DEVICE_STATUS))) + goto out1; + + LOG (3, "[get_devmaps] dm_task_set_name(dmt1, names->name)"); + if (!dm_task_set_name(dmt1, names->name)) + goto out1; + + LOG (3, "[get_devmaps] dm_task_run(dmt1)"); + if (!dm_task_run(dmt1)) + goto out1; + LOG (3, "[get_devmaps] DM_DEVICE_STATUS ioctl done"); + do { + LOG (3, "[get_devmaps] iterate on devmap's targets"); + nexttgt = dm_get_next_target(dmt1, nexttgt, + &start, + &length, + &target_type, + ¶ms); + + + LOG (3, "[get_devmaps] test target_type existence"); + if (!target_type) + goto out1; + + LOG (3, "[get_devmaps] test target_type is multipath"); + if (!strncmp (target_type, "multipath", 9)) { + strcpy (devmaps_p->mapname, names->name); + devmaps_p++; + + /* test vector overflow */ + if (devmaps_p - devmaps >= MAXMAPS * sizeof (struct devmap)) { + LOG (1, "[get_devmaps] devmaps overflow"); + dm_task_destroy(dmt1); + r = 1; + goto out; + } + } + + } while (nexttgt); + +out1: + dm_task_destroy(dmt1); + next = names->next; + + } while (next); + +out: + dm_task_destroy(dmt); + + LOG (3, "[get_devmaps] done"); + return r; +} + +int checkpath (struct path *path_p) +{ + char devnode[FILENAMESIZE]; + int r; + + LOG (2, "[checkpath] checking path %i:%i", path_p->major, path_p->minor); + sprintf (devnode, "/tmp/.checker.%i.%i", path_p->major, path_p->minor); + + if (path_p->checkfn == NULL) { + LOG (1, "[checkpath] test function not set for path %i:%i", + path_p->major, path_p->minor); + return 1; + } + + r = makenode (devnode, path_p->major, path_p->minor); + + if (r < 0) { + LOG (2, "[checkpath] can not make node for %s", devnode); + return r; + } + + r = path_p->checkfn(devnode); + unlink (devnode); + + return r; +} + +int updatepaths (struct devmap *devmaps, struct paths *failedpaths) +{ + struct path *path_p; + struct devmap *devmaps_p; + void *next; + struct dm_task *dmt; + long long start, length; + char *target_type = NULL; + char *params, *p1, *p2; + char word[6]; + int i; + + path_p = failedpaths->paths_h; + + pthread_mutex_lock (failedpaths->lock); + memset (failedpaths->paths_h, 0, MAXPATHS * sizeof (struct path)); + + /* first pass */ + /* ask DM the failed path list */ + + devmaps_p = devmaps; + + while (*devmaps_p->mapname != 0x0) { + next = NULL; + + if (!(dmt = dm_task_create(DM_DEVICE_STATUS))) + break; + + if (!dm_task_set_name(dmt, devmaps_p->mapname)) + goto out; + + if (!dm_task_run(dmt)) + goto out; + + do { + next = dm_get_next_target(dmt, next, &start, &length, + &target_type, ¶ms); + + /* begin ugly parser */ + p1 = params; + p2 = params; + while (*p1) { + /* p2 lags at the begining of the word p1 parses */ + while (*p1 != ' ') { + /* if the current word is a path */ + if (*p1 == ':') { + /* p1 jumps to path state */ + while (*p1 != 'A' && *p1 != 'F') + p1++; + + /* store path info */ + + path_p->checkfn = NULL; + + i = 0; + memset (&word, 'O', 6 * sizeof (char)); + while (*p2 != ':') { + word[i++] = *p2; + p2++; + } + path_p->major = atoi (word); + + p2++; + i = 0; + memset (&word, 'O', 6 * sizeof (char)); + while (*p2 != ' ') { + word[i++] = *p2; + p2++; + } + path_p->minor = atoi (word); + + strcpy (path_p->mapname, devmaps_p->mapname); + + /* + * discard active paths + * don't trust the A status flag : double check + */ + if (*p1 == 'A' && + !select_checkfn (path_p) && + checkpath (path_p)) { + LOG(2, "[updatepaths] discard %i:%i as valid path", + path_p->major, path_p->minor); + p1++; + memset (path_p, 0, sizeof(struct path)); + continue; + } + + path_p++; + + /* test vector overflow */ + if (path_p - failedpaths->paths_h >= MAXPATHS * sizeof (struct path)) { + LOG (1, "[updatepaths] path_h overflow"); + pthread_mutex_unlock (failedpaths->lock); + return 1; + } + } + p1++; + } + p2 = p1; + p1++; + } + } while (next); + +out: + dm_task_destroy(dmt); + devmaps_p++; + + } + + pthread_mutex_unlock (failedpaths->lock); + return 0; +} + +int geteventnr (char *name) +{ + struct dm_task *dmt; + struct dm_info info; + + if (!(dmt = dm_task_create(DM_DEVICE_INFO))) + return 0; + + if (!dm_task_set_name(dmt, name)) + goto out; + + if (!dm_task_run(dmt)) + goto out; + + if (!dm_task_get_info(dmt, &info)) + return 0; + + if (!info.exists) { + LOG(1, "Device %s does not exist", name); + return 0; + } + +out: + dm_task_destroy(dmt); + + return info.event_nr; +} + +void *waitevent (void * et) +{ + int event_nr; + struct event_thread *waiter; + + waiter = (struct event_thread *)et; + pthread_mutex_lock (waiter->waiter_lock); + + event_nr = geteventnr (waiter->mapname); + + struct dm_task *dmt; + + if (!(dmt = dm_task_create(DM_DEVICE_WAITEVENT))) + return 0; + + if (!dm_task_set_name(dmt, waiter->mapname)) + goto out; + + if (event_nr && !dm_task_set_event_nr(dmt, event_nr)) + goto out; + + dm_task_run(dmt); + +out: + dm_task_destroy(dmt); + + /* tell waiterloop we have an event */ + pthread_mutex_lock (event_lock); + pthread_cond_signal(event); + pthread_mutex_unlock (event_lock); + + /* release waiter_lock so that waiterloop knows we are gone */ + pthread_mutex_unlock (waiter->waiter_lock); + pthread_exit(waiter->thread); + + return (NULL); +} + +void *waiterloop (void *ap) +{ + struct paths *failedpaths; + struct devmap *devmaps, *devmaps_p; + struct event_thread *waiters, *waiters_p; + int r; + + /* inits */ + failedpaths = (struct paths *)ap; + devmaps = malloc (MAXMAPS * sizeof (struct devmap)); + waiters = malloc (MAXMAPS * sizeof (struct event_thread)); + memset (waiters, 0, MAXMAPS * sizeof (struct event_thread)); + + while (1) { + + /* update devmap list */ + LOG (1, "[event thread] refresh devmaps list"); + get_devmaps (devmaps); + + /* update failed paths list */ + LOG (1, "[event thread] refresh failpaths list"); + updatepaths (devmaps, failedpaths); + + /* start waiters on all devmaps */ + LOG (1, "[event thread] start up event loops"); + waiters_p = waiters; + devmaps_p = devmaps; + + while (*devmaps_p->mapname != 0x0) { + + /* find out if devmap already has a running waiter thread */ + while (*waiters_p->mapname != 0x0) { + if (!strcmp (waiters_p->mapname, devmaps_p->mapname)) + break; + waiters_p++; + } + + /* no event_thread struct : init it */ + if (*waiters_p->mapname == 0x0) { + strcpy (waiters_p->mapname, devmaps_p->mapname); + waiters_p->thread = malloc (sizeof (pthread_t)); + waiters_p->waiter_lock = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init (waiters_p->waiter_lock, NULL); + } + + /* event_thread struct found */ + if (*waiters_p->mapname != 0x0) { + r = pthread_mutex_trylock (waiters_p->waiter_lock); + /* thread already running : out */ + + if (r) + goto out; + + pthread_mutex_unlock (waiters_p->waiter_lock); + } + + LOG (1, "[event thread] create event thread for %s", waiters_p->mapname); + pthread_create (waiters_p->thread, NULL, waitevent, waiters_p); +out: + waiters_p = waiters; + devmaps_p++; + } + + /* wait event condition */ + pthread_mutex_lock (event_lock); + pthread_cond_wait(event, event_lock); + pthread_mutex_unlock (event_lock); + + LOG (1, "[event thread] event caught"); + } + + return (NULL); +} + +void *checkerloop (void *ap) +{ + struct paths *failedpaths; + struct path *path_p; + char *cmdargs[4] = {MULTIPATH, "-D", NULL, NULL}; + char major[5]; + char minor[5]; + int status; + + failedpaths = (struct paths *)ap; + + LOG (1, "[checker thread] path checkers start up"); + + while (1) { + path_p = failedpaths->paths_h; + pthread_mutex_lock (failedpaths->lock); + LOG (2, "[checker thread] checking paths"); + while (path_p->major != 0) { + + if (checkpath (path_p)) { + LOG (1, "[checker thread] reconfigure %s\n", path_p->mapname); + snprintf (major, 5, "%i", path_p->major); + snprintf (minor, 5, "%i", path_p->minor); + cmdargs[2] = major; + cmdargs[3] = minor; + if (fork () == 0) + execve (cmdargs[0], cmdargs, NULL); + + wait (&status); + /* MULTIPATH will ask for failedpaths refresh (SIGHUP) */ + } + + path_p++; + + /* test vector overflow */ + if (path_p - failedpaths->paths_h >= MAXPATHS * sizeof (struct path)) { + LOG (1, "[checker thread] path_h overflow"); + pthread_mutex_unlock (failedpaths->lock); + return (NULL); + } + } + pthread_mutex_unlock (failedpaths->lock); + sleep(CHECKINT); + } + + return (NULL); +} + +struct paths *initpaths (void) +{ + struct paths *failedpaths; + + failedpaths = malloc (sizeof (struct paths)); + failedpaths->paths_h = malloc (MAXPATHS * sizeof (struct path)); + failedpaths->lock = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init (failedpaths->lock, NULL); + event = (pthread_cond_t *) malloc (sizeof (pthread_cond_t)); + pthread_cond_init (event, NULL); + event_lock = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init (event_lock, NULL); + + return (failedpaths); +} + +void pidfile (pid_t pid) +{ + FILE *file; + struct stat *buf; + + buf = malloc (sizeof (struct stat)); + + if (!stat (PIDFILE, buf)) { + LOG(1, "[master thread] already running : out"); + free (buf); + exit (1); + } + + umask (022); + pid = setsid (); + + if (pid < -1) { + LOG(1, "[master thread] setsid() error"); + exit (1); + } + + file = fopen (PIDFILE, "w"); + fprintf (file, "%d\n", pid); + fclose (file); + free (buf); +} + +void * +signal_set(int signo, void (*func) (int)) +{ + int r; + struct sigaction sig; + struct sigaction osig; + + sig.sa_handler = func; + sigemptyset(&sig.sa_mask); + sig.sa_flags = 0; + + r = sigaction(signo, &sig, &osig); + + if (r < 0) + return (SIG_ERR); + else + return (osig.sa_handler); +} + +void sighup (int sig) +{ + LOG (1, "[master thread] SIGHUP caught : refresh devmap list"); + + /* ask for failedpaths refresh */ + pthread_mutex_lock (event_lock); + pthread_cond_signal(event); + pthread_mutex_unlock (event_lock); +} + +void sigend (int sig) +{ + LOG (1, "[master thread] unlink pidfile"); + unlink (PIDFILE); + LOG (1, "[master thread] --------shut down-------"); + exit (0); +} + +void signal_init(void) +{ + signal_set(SIGHUP, sighup); + signal_set(SIGINT, sigend); + signal_set(SIGTERM, sigend); + signal_set(SIGKILL, sigend); +} + +int main (int argc, char *argv[]) +{ + pthread_t wait, check; + struct paths *failedpaths; + pid_t pid; + + pid = fork (); + + /* can't fork */ + if (pid < 0) + exit (1); + + /* let the parent die happy */ + if (pid > 0) + exit (0); + + /* child's play */ + openlog (argv[0], 0, LOG_DAEMON); + LOG (1, "[master thread] --------start up--------"); + + pidfile (pid); + signal_init (); + + failedpaths = initpaths (); + + pthread_create (&wait, NULL, waiterloop, failedpaths); + pthread_create (&check, NULL, checkerloop, failedpaths); + pthread_join (wait, NULL); + pthread_join (check, NULL); + + return 0; +} diff --git a/extras/multipath-tools/multipathd/multipathd.init b/extras/multipath-tools/multipathd/multipathd.init new file mode 100644 index 00000000..860b2b11 --- /dev/null +++ b/extras/multipath-tools/multipathd/multipathd.init @@ -0,0 +1,42 @@ +#!/bin/sh + +PATH=/bin:/usr/bin:/sbin:/usr/sbin +DAEMON=/usr/bin/multipathd +PIDFILE=/var/run/multipathd.pid + +test -x $DAEMON || exit 0 + +case "$1" in + start) + echo -n "Starting multipath daemon: multipathd" + if start-stop-daemon --quiet --stop --signal 0 --pidfile $PIDFILE --name multipathd + then + echo " already running." + exit + fi + /sbin/start-stop-daemon --start --quiet --exec $DAEMON + echo "." + ;; + stop) + echo -n "Stopping multipath daemon: multipathd" + if start-stop-daemon --quiet --stop --signal 0 --pidfile $PIDFILE --name multipathd + then + PID=`cat $PIDFILE` + start-stop-daemon --quiet --stop --exec $DAEMON --pidfile $PIDFILE --name multipathd + # Now we wait for it to die + while kill -0 $PID 2>/dev/null; do sleep 1; done + echo "." + else + echo " not running."; + fi + ;; + force-reload|restart) + $0 stop + $0 start + ;; + *) + echo "Usage: /etc/init.d/multipathd {start|stop|restart|force-reload}" + exit 1 +esac + +exit 0 diff --git a/extras/multipath-tools/multipathd/sg_include.h b/extras/multipath-tools/multipathd/sg_include.h new file mode 100644 index 00000000..46050682 --- /dev/null +++ b/extras/multipath-tools/multipathd/sg_include.h @@ -0,0 +1,43 @@ +#ifdef SG_KERNEL_INCLUDES + #define __user + typedef unsigned char u8; + #include "/usr/src/linux/include/scsi/sg.h" + #include "/usr/src/linux/include/scsi/scsi.h" +#else + #ifdef SG_TRICK_GNU_INCLUDES + #include + #include + #else + #include + #endif +#endif + +/* + Getting the correct include files for the sg interface can be an ordeal. + In a perfect world, one would just write: + #include + #include + This would include the files found in the /usr/include/scsi directory. + Those files are maintained with the GNU library which may or may not + agree with the kernel and version of sg driver that is running. Any + many cases this will not matter. However in some it might, for example + glibc 2.1's include files match the sg driver found in the lk 2.2 + series. Hence if glibc 2.1 is used with lk 2.4 then the additional + sg v3 interface will not be visible. + If this is a problem then defining SG_KERNEL_INCLUDES will access the + kernel supplied header files (assuming they are in the normal place). + The GNU library maintainers and various kernel people don't like + this approach (but it does work). + The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and + was used) prior to glibc 2.2 . Prior to that version /usr/include/linux + was a symbolic link to /usr/src/linux/include/linux . + + There are other approaches if this include "mixup" causes pain. These + would involve include files being copied or symbolic links being + introduced. + + Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES + nor SG_TRICK_GNU_INCLUDES is defined. + + dpg 20010415, 20030522 +*/